ContentProvider中實作了常見的insert,query,delete和update的方法就能完成基本操作了,可是遇到批量的操作怎麼辦?可以用循環一條條的插入,當然也可以使用ContentProvider的如下兩個方法:
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
final int numOperations = operations.size();
final ContentProviderResult[] results = new ContentProviderResult[numOperations];
for (int i = 0; i < numOperations; i++) {
results[i] = operations.get(i).apply(this, results, i);
}
return results;
}
public int bulkInsert(Uri uri, ContentValues[] values) {
int numValues = values.length;
for (int i = 0; i < numValues; i++) {
insert(uri, values[i]);
}
return numValues;
}
上述是預設實作,可以看出實際上還是循環實作,和你自己寫循環沒啥差別。那麼批量操作如何優化效率,從最近遇到的一個bug講起。
最近一個app批量操作問題相當嚴重,插入500多條資料要耗時86秒,真是跌破眼鏡。開始嘗試使用applyBatch方法,可是如上分析是不會有什麼效果的,日志分析發現多次引發資料庫監聽的回調和查詢。
原因是insert,delete和update标準實作中,一般都會在操作完畢後發送通知,例如:
getContext().getContentResolver().notifyChange(AUTHORITY_URI, null,
false);
這樣app層次會得到通知重新整理頁面。那麼在循環中的每次操作中都會引發資料的重查,而且由于不專業的代碼重查會觸發多次:
多餘的資料庫監聽
ContentObserver
使用了ContentObserver資料庫監聽,得到通知會發生回調,觸發查詢
手動的查詢
在資料庫操作完畢後,加入重查的代碼
CursorAdapter的自動重新整理
實際上CursorAdapter是預設會自動重新整理的,隻要ContentProvider中代碼中有notifyChange。
三種方式代碼中都有,即插一條資料會觸發三次重查,太恐怖了。保留三種中的一個就可以了,是個listview清單頁面的話推薦用最後一種方式。
批量操作優化
notifyChange的優化
減少了兩次重查後發現,最後一次還是多餘的,應該是批量插入後重查一次即可,而不是循環中的每一次都觸發重查。
要想實作這個,那麼必須繼承修改bulkInsert和applyBatch方法,使得notify減少到最少。
事務優化
批量操作加入事務,隻寫一次資料庫,避免了多次資料庫操作,見網上的諸多使用事務後的效率時間對比,使用事務可以大幅度的提高效率。預設的ContentProvider是沒有事務的,要自己加上。下面是個模闆代碼,和業務無關,任何ContentProvider稍微改下都可以用。
private boolean mIsInTransaction = false;
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
db.beginTransaction();//開始事務
mIsInTransaction = true;
try{
ContentProviderResult[] results = super.applyBatch(operations);
db.setTransactionSuccessful();//設定事務标記為successful
return results;
} finally {
mIsInTransaction = false;
db.endTransaction();//結束事務
notifyChange();
}
}
protected void notifyChange() {
if(!mIsInTransaction) {
getContext().getContentResolver().notifyChange(AUTHORITY_URI, null,
false);
}
}
總結
這個問題中,實作ContentProvider的代碼中沒有加入notifyChange,導緻app側有同學為了解決bug到處加入手動查詢的代碼,後續有人接手後又加入了資料庫監聽的代碼,再往後ContentProvider加入了notifyChange的代碼。而且app側沒有使用批量方法,資料庫也沒有實作批量方法。
歸根到底是對資料庫基本知識和Android源碼的了解不足,很多問題都是開發自己造出來的。
很多問題Android本身現有的架構都可以解決的,不用自己造輪子,同時造輪子的99%以上的不會比google造的好。