天天看點

ContentProvider的批量操作優化多餘的資料庫監聽批量操作優化總結

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造的好。