一、雜談
本文結構:
1、雜談。
2、上代碼,閹割版的demo。
3、總結一下封裝的思想。
本文旨在回味幾年前的技術,同時對封裝功底進行夯實。畢竟最近一段時間都沒有寫代碼。封裝的思路,要清晰,明白,明白的是這個思路,這個想法,而不是照抄。
好久沒更新CSDN了,因為最近有點懵懵的,周末也不知道在幹啥,沒打錯字,不是萌萌的,是真懵懵的。
之前找工作階段的時候,面試過一家公司,叫xx拼車,名字也是蹭滴滴的熱度,整了個滴答。O(∩_∩)O哈哈~。人事是個挺漂亮的年輕小姐姐,是以說這家公司不錯。
面試這家公司的時候,原定是組長面我,因為我和另一個人一起來的,他先答完了題(by phone),是以經理去面他了,然後找了個人來面我。這個人是個員工,一個有強烈競争感的員工。
問了我上家公司采用的一些技術,其中涉及到資料庫部分的,我答,用的是ContentProvider。接下來他的反應令我懵逼了。
它以一種嘲笑的語氣,半笑着問我,ContentProvider不是用來資料共享的嗎,怎麼能操作資料庫呢?
我(⊙o⊙)…了一下。然後回答,ContentProvider資料共享的時候,不也是操作了資料庫嗎?那麼他一定可以操作自己的資料庫啊。
他很執着的說,ContentProvider是用來共享資料的,不是操作資料的啊。
我(⊙o⊙)…了一下。然後說可以的,是可以的,我可以給你說說。
然後他又重複的說了上一句,ContentProvider是用來共享資料的,不是操作資料的啊。
我心裡想,尼瑪,這麼一根筋嗎,你就當做共享了自己的資料行不?我說,ContentProvider可以共享,也可以操作資料庫,插入查找删除啥的都有,都可以的。
我剛想給他詳細的說一下,代碼怎麼實作,但這時候突然間,他好像魔怔了一般,選擇性失聰并且複讀。一直在重複着那一句話,“ContentProvider怎麼操作資料庫,ContentProvider怎麼操作資料庫,ContentProvider怎麼操作資料庫”,令我無從插嘴。恨自己交流能力太弱。
這時候我想到了,在山的那邊,雪的那邊,有一個男精靈,他名字叫歐陽鋒,他執着又聰明。
一個練功練魔怔了的男人,他倆此時的狀态居然像極,因為對于心法中有地方想不明白,就走火入魔了。
最後我還是簡單說了一下ContentProvider操作資料的過程,然後我們進行了下一話題。
面試完畢之後,我覺得我很有戲,因為我說到了一個他不會的知識點,虐了一下面試官,一般這種情況,面試官都會錄取你的。
故事的結局很意外。他們說我們在考慮一下之後,就一直考慮到了現在,大概已經有三四個月了吧,而我早就入職别的公司了。
我到現在還挺愧疚的,我要不要告訴他們我已經入職了啊,要不然他們還在考慮可怎麼辦啊。
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈。
之前公司資料庫操作用的SQLiteOpenHelper和ContentProvider,現在入職的公司,是OrmLite和ContentProvider一起用的。是的都是比較老的架構。
畢竟這兩個項目,一個六年了,一個N(N<=4)年了。
(6年前的Android項目你見過嗎?古董)
簡單說一下吧,ContentProvider操作資料庫的好處,那就是定義統一的接口,以接口的形式通路表啥的。不管你表結構怎麼改,我這不用改,挺友善是吧。但是,據我個人所知,現在類似與GreenDao這種架構的流行,對資料操作的優化以及程式設計時也不用自己寫多少,是以ContentProvider沒有多少公司再用了。
有用的,要麼是項目很久都沒有被重構,要麼是做項目的程式員所掌握的技能很久都沒有被重構。
牛逼吹完了,上上代碼,這裡以SQLiteOpenHelper為例,回顧一下幾年前主流的資料庫操作。(老程式員可以哭了,現在還有菜鳥學你們之前的用法)
卧槽,寫到這發現有點不對啊。别人都是學新的技術,新技術一出,就像瘋狗一樣去學,生怕屎涼了,哦不,是新技術過時了(沒辦法,Android技術更新太快),為啥我越學越往回學了啊?
二、SQLiteOpenHelper
SQLiteOpenHelper略。
打我啊。
三、BaseContentProvider
先寫個ContentProvider,實作了先。内部定義SQLIteOpenHelper。
/**
* ContentProvider基礎類.
*
*/
public abstract class BaseContentProvider extends ContentProvider {
//略一堆常量
private SQLiteOpenHelper mOpenHelper;
/**
* 子類應該通過本方法設定SQLiteOpenHelper
*
* @param helper
*/
public void setSQLiteOpenHelper(SQLiteOpenHelper helper) {
mOpenHelper = helper;
}
//未完。。。。。。
然後重寫COntentProvider中的一些方法,例如增删改查的方法。這裡用到一個類,SqlArguments ,這個是自己封裝的,Android SDK沒有,别問我你怎麼找不到。
就是根據傳來的uri以及其他參數,來傳回不同的參數。。。對參數的一個封裝,就是查詢時需要的表名,以及其他條件。
重寫getType:
//就是根據uri傳回個表名
@Override
public String getType(Uri uri) {
SqlArguments args = new SqlArguments(uri, null, null);
if (TextUtils.isEmpty(args.where)) {
return "xx.xx.xx/" + args.table;
} else {
return "yy.yy.yy/" + args.table;
}
}
重寫查詢,這裡有個setNotificationUri的方法,這個方法就是在資料庫改了之後,通知我們注冊的觀察者,這個觀察這是在cursorAdapter中注冊的,想詳細了解這裡的話,自行百度吧。這裡上連結會處于稽核中,而一直不過關的。有病。
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(args.table);
SQLiteDatabase db = null;
Cursor result = null;
try {
db = mOpenHelper.getWritableDatabase();// .getReadableDatabase();
result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
if (result != null) {
result.setNotificationUri(getContext().getContentResolver(), uri);
}
} catch (SQLiteDiskIOException e) {
Log.i(TAG, "query : " + e.toString());
if (result != null) {
result.close();
result = null;
}
}
return result;
}
再寫個插入,批量插入。這裡用到了資料庫的事務,要插一起插,完事的先等着,沒完事的插完了一起拔。(o)/~
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
SqlArguments args = new SqlArguments(uri);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
int numValues = values.length;
for (int i = 0; i < numValues; i++) {
if (db.insert(args.table, null, values[i]) < 0)
return 0;
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
sendNotify(uri);
return values.length;
}
//通知一下子觀察者,資料庫變了。
private void sendNotify(Uri uri) {
String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
if (notify == null || "true".equals(notify)) {
getContext().getContentResolver().notifyChange(uri, null);
}
}
更新和删除就不寫了,和插入差不多。傳回int類型,更新了幾條,删除了幾條。
這樣,一個BaseContentProvider就寫好了。然後寫一個類,繼承我們的BaseContentProvider就好了。
四、封裝思想及過程
上學那時候,學什麼課程都要做個學生資訊管理系統。後來我膩了,老子來學計算機是打算做遊戲的,天天做個破管理系統幹毛啊?
後來老師一想,恩,就讓我做了個跟遊戲有關的玩家資訊管理系統。。。
OMG,後來拷貝代碼的時候,沒改名,玩家有一個表叫做課程表。交作業的時候,老師問我,玩家為什麼要有課程?
我答,這是本遊戲的特色,玩家除了玩遊戲以外還可以學習。我天貓的是一本正經的那種表情和語氣當着全班同學的面回答的啊。回答完自己都憋不住笑。同學們都說我是個弱智。
是以這裡建立的表還是Course
然後說一下封裝的思想。封裝,就是将一系列操作,打包,暴露給外部使用的時候,十分簡單。而正因為這種封裝的封閉性,是以我們封裝的一定要嚴謹,一定要不能出錯。層層判斷是必須的,現在你拿出任何一個知名的APP出來,你看他的源代碼,都是判斷的十分嚴謹,恨不得所有地方都寫上if。對資料的來源,不信任,不關心,這是Java的一個封裝最基本的思想,哪怕你知道資料的來源可靠。如果代碼裡這種判斷都沒有,那麼這個代碼一定是個垃圾。
劃重點:對資料的來源,不信任且不關心,哪怕你知道這資料是從哪來的,并且傳的是什麼,那也要加上判斷。
CourseContentProvider:
/**
* Created by WenCh on 2017/11/5.
*/
public class CourseContentProvider extends BaseContentProvider {
public static final int DATABASE_VERSION = Constants.DATABASE_VERSION;
public static final String DATABASE_NAME = "netease_vopen.db";
public static final String AUTHORITY = Constants.DATABASE_AUTHORITY;
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
public static final String TABLE_All_DTTA = "t_vopen_all_data";
@Override
public boolean onCreate() {
setSQLiteOpenHelper(new com.contentprovidertest.CourseHelper(getContext()));
return true;
}
public static class CourseHelper {
/**===================資料庫字段==================*/
/**課程id*/
public static final String COURSE_ID = "course_id";
/**課程名稱*/
public static final String COURSE_NAME = "course_name";
/**課程tag*/
public static final String COURSE_TAG = "course_tag";
/**===================資料庫字段==================*/
public static Uri getUri() {
return Uri.parse("content://" + CourseContentProvider.AUTHORITY + "/"
+ TABLE_All_DTTA);
}
}
}
//這裡也可以寫上其他表的Helper,我就不寫了,略了,略略略
DBApi
/**
* Created by WenCh on 2017/11/5.
*/
public class DBApi {
public static final int DB_OPERATION_FAILED = -1;
/*************************************************************
* table name :TABLE_All_DTTA = "t_vopen_all_data"
*************************************************************/
//該表資料字段
public static class DBCourseInfo {
public String mCourseId;
public String mCourseName;
public String mCourseTag;
}
/**
* 向表所有資料表中插入一項
*
* @param context 不瞎都知道
* @param course 不傻都知道
* @return 不是沙比都知道
* @deprecated ^_^
*/
public static Uri insertCourse(Context context, DBCourseInfo course) {
if (null == course) {
return null;
}
ContentValues initialValues = new ContentValues();
if (!("判斷一下相應的條件," +
"比如說資料庫裡有其他字段,這個字段是辨別的type," +
"因為資料庫裡有很多表,是以根據這個type來判斷是哪個表,否可能插入錯誤," +
"這裡是demo,就不寫了").isEmpty())
initialValues.put(CourseContentProvider.CourseHelper.COURSE_TAG, course.mCourseTag);
return context.getContentResolver().insert(CourseContentProvider.CourseHelper.getUri(), initialValues);
}
}
//這裡也可以像其他表插入,比如插入學生。。。。
//insertStudent,可以這樣命名,然後調用的CourseContentProvider中
//的别的Helper,比如StudentHelper。懂了嗎?
懂了
DBUtils
這個類裡,可以寫一些對對條件的判斷,對資料庫的操作,調用DBApi。來實作更強大的功能。
/**
* Created by WenCh on 2017/11/5.
* <p>
* 該類用來處理資料庫操作後的封裝
*/
public class DBUtils {
private static final String TAG = "DBUtils";
/**
* 查詢與某一門課程相似的課程清單。
*
* @param context
* @param info
* @return
*/
public static Cursor getRelativeCourses(Context context, BeanCourse info) {
String[] projection = new String[]{CourseContentProvider.CourseHelper.COURSE_CONTENT, "_id"};
StringBuilder selectionBuilder = new StringBuilder();
List<String> selectionArgsList = new ArrayList<String>();
//不包括自己
selectionBuilder.append(CourseContentProvider.CourseHelper.COURSE_ID + "<> ?");
selectionArgsList.add(info.plid);
//有相同的tag
String tags = info.tags;
if (!TextUtils.isEmpty(tags)) {
selectionBuilder.append(" AND ");
String[] tagTokens = tags.split(",");
selectionBuilder.append("(");
for (int i = 0; i < tagTokens.length; i++) {
String tag = tagTokens[i];
selectionBuilder.append(CourseContentProvider.CourseHelper.COURSE_TAG + " LIKE ? " + " OR ");
selectionArgsList.add("%" + tag + "%");
}
selectionBuilder.setLength(selectionBuilder.length() - 4);
selectionBuilder.append(")");
}
String selection = null;
if (selectionBuilder.length() > 0) {
selection = selectionBuilder.toString();
}
String[] selectionArgs = null;
if (selectionArgsList.size() > 0) {
selectionArgs = selectionArgsList.toArray(new String[0]);
}
String sortOrder = CourseContentProvider.CourseHelper.COURSE_NAME + " DESC";
return DBApi.queryCourse(context, projection, selection, selectionArgs, sortOrder);
}
}
//這裡也可以加上其他方法,例如,模糊插入,判斷表裡有沒有,有就更新,沒有就插入等等。
怎麼樣,經過這一層層封裝,是不是可以完成很強大的功能?而邏輯還是清晰的?如果你什麼都寫一個類裡,那麼你一定特别亂。
五、封裝思想總結
BaseContentProvider完成一些ContentProvider的操作。實作。
CourseContentProvider繼承自BaseContentProvider,内部含有很多表的Helper,Helper包含一些字段,uri等。
DBApi是完成對相應的表的資料庫操作,注意,是相應的表的。可以insertStudent,也可以insertCourse。調用CourseContentProvider以及其各種Helper。
DBUtils則是通過DBApi來完成更強大的功能,模糊查詢,模糊插入等。當然了,最基本的插入啥的一定會有的。
周末愉快。卧槽,寫完這部落格已經0:12了。。。。。。
周一愉快。