国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

ContentProvider 的批處理操作

2019-11-09 14:25:16
字體:
來源:轉載
供稿:網友

Overview

ContentPRovider是android 系統核心組件之一,其封裝了數據的訪問接口,其底層數據一般都是保存在數據庫中或者保存在云端。

大多數情況下其實我們用不到ContentProvider,如果自己的應用程序與別的應用什么交互,直接使用SQLite數據庫即可(想使用ContentProvider之前先看下官方文檔,再決定是否真的需要)。不過自從有了一些ORM的開源庫,我們甚至很少自己來操作數據庫了。

ContentProvider和ContentResolver為我們提供了基礎的接口,能滿足大部分需求。但是當處理的數據量比較大的時候,我們可以選擇調用多次ContentResolver的對應函數 或者 使用批處理操作。當然 后者性能會比較好些。

bulkInsert

如果只是涉及到單表的批量插入,我們可以直接使用 bulkInsert(Uri uri, ContentValues[] values) 進行批量插入即可。

ContentProviderOperation

為了使批量更新、插入、刪除數據更加方便,android系統引入了 ContentProviderOperation 類。 在官方開發文檔中推薦使用ContentProviderOperations,有一下原因: 1. 所有的操作都在一個事務中執行,這樣可以保證數據完整性 2. 由于批量操作在一個事務中執行,只需要打開和關閉一個事務,比多次打開關閉多個事務性能要好些 3. 使用批量操作和多次單個操作相比,減少了應用和content provider之間的上下文切換,這樣也會提升應用的性能,并且減少占用CPU的時間,當然也會減少電量的消耗。

ContentProviderOperation.Builder

要創建ContentProviderOperation對象,則需要使用 ContentProviderOperation.Builder類,通過調用下面幾個靜態函數來獲取一個Builder 對象:

函數 用途
newInsert 創建一個用于執行插入操作的Builder(支持多表)
newUpdate 創建一個用于執行更新操作的Builder
newDelete 創建一個用于執行刪除操作的Builder
newAssertQuery 可以理解為斷點查詢,也就是查詢有沒有符合條件的數據,如果沒有,會拋出一個OperationapplicationException異常

這個Buidler對象使用了著名的Builder設計模式,由于Builder對象的函數都返回了自己,所以通過一系列的函數鏈式調用即可生成最終的ContentProviderOperation對象。

/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()); // Builds the operation and adds it to the array of operations ops.add(op.build());

當然 你還可以使用熟悉的ContentValues對象,對應的函數為withValues(values)。

Builder的核心函數

Builder對象核心函數的介紹(可以直接查看API):

函數 用途
withSelection (String selection, String[] selectionArgs) 指定需要操作的數據條件。只能用于update, delete, or assert。
withSelectionBackReference(int selectionArgIndex, int previousResult) 添加一個“向后引用” 作為查詢條件。之前通過withSelection(String, String[])指定的selectionArgIndex位置的值會被覆蓋掉。只能用于update, delete, or assert。
withValue (String key, Object value) 定義一列的數據值,類似于向ConetentValues中加入一條數據。只能用于 insert, update, or assert。
withValues (ContentValues values) 定義多列的數據值。 只能用于 insert, update, or assert
withValueBackReference(String key, int previousResult) 添加一個“向后引用” 。使用“向后引用” 中的值來設置指定“key”列的值,所謂向后引用其實就是一組Operation中第previousResult個ContentProviderOperation完成之后返回的ContentProviderResult,如果是insert操作則會使用ContentProviderOperation返回的uri中的ID,如果是update或者assert就是使用返回的count。這個值會覆蓋之前withValues(ContentValues)設置的值。只能用于 insert, update, or assert。
withValueBackReferences(ContentValues backReferences) 添加一個“向后引用” 。使用ContentValues來完成多次withValueBackReference操作。ContentValues 中的key和value就對應于”列名”和”previousResult的索引”,參考withValueBackReference的參數。value會被作為String來添加。這個值會覆蓋之前withValues(ContentValues)設置的值。只能用于 insert, update, or assert。
withExpectedCount(int count) 驗證影響的行數,如果跟count不相同,會拋出OperationApplicationException 。只能用于update, delete, or assert操作。

關于”向后引用”可以參考官網的一些說明

最后通過ContentResolver 的applyBatch()函數來應用批量操作:

try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);} catch (RemoteException e) { // do s.th.} catch (OperationApplicationException e) { // do s.th.}

工作原理

從ContentProviderOperation.Builder的build()方法開始,可以看到構造出了一個新的 ContentProviderOperation()。

/** Create a ContentProviderOperation from this {@link Builder}. */ public ContentProviderOperation build() { if (mType == TYPE_UPDATE) { if ((mValues == null || mValues.size() == 0) && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)) { throw new IllegalArgumentException("Empty values"); } } if (mType == TYPE_ASSERT) { if ((mValues == null || mValues.size() == 0) && (mValuesBackReferences == null || mValuesBackReferences.size() == 0) && (mExpectedCount == null)) { throw new IllegalArgumentException("Empty values"); } } return new ContentProviderOperation(this); }

批量操作是從getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList)開始, 調用最終會走到 ContentProvider.applyBatch(),這個方法中做了兩件事情: 1. 定義了一個ContentProviderResult[]數組,數組的大小等于operations的大小。ContentProviderResult用于保存每個ContentProviderOperation的執行結果。ContentProviderResult會有兩種類型,一種具體的“uri”,另一種是此次操作影響到的“count”行數,最后會在“向后引用”中派上用場。 2. 遍歷operations,并且調用相應的ContentProviderOperation.apply操作,把結果返回到對應的ContentProviderResult[]數組中保存起來。

public @NonNull ContentProviderResult[] applyBatch( @NonNull 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; }

在ContentProviderOperation.apply方法中,有以下幾個重要的步驟: 1. 先調用resolveValueBackReferences(),處理”向后引用”的ContentValue。 2. 再調用resolveSelectionArgsBackReferences,處理”向后引用”的查詢參數。 3. 如果是insert操作就直接調用了 provider.insert,并insert返回的uri賦值給new ContentProviderResult(newUri)。 4. 同樣的,如果是delelte、update,也是直接調用provider.delete、provider.update,并把返回numRows賦值給new ContentProviderResult(numRows) 5. 相對于AssertQuery,直接調用provider.query把查詢出來的數據與期望的Values進行比較,如果一樣就返回對應行數的new ContentProviderResult(numRows);如果不一樣就報Exception。 6. 如果mExpectedCount不為空(表示設置了withExpectedCount(int count)),會與numRows進行比較,判斷期望值是否與實際操作值一樣,不一樣就報OperationApplicationException。

public ContentProviderResult apply(ContentProvider provider, ContentProviderResult[] backRefs, int numBackRefs) throws OperationApplicationException { ContentValues values = resolveValueBackReferences(backRefs, numBackRefs); String[] selectionArgs = resolveSelectionArgsBackReferences(backRefs, numBackRefs); if (mType == TYPE_INSERT) { Uri newUri = provider.insert(mUri, values); if (newUri == null) { throw new OperationApplicationException("insert failed"); } return new ContentProviderResult(newUri); } int numRows; if (mType == TYPE_DELETE) { numRows = provider.delete(mUri, mSelection, selectionArgs); } else if (mType == TYPE_UPDATE) { numRows = provider.update(mUri, values, mSelection, selectionArgs); } else if (mType == TYPE_ASSERT) { // Assert that all rows match expected values String[] projection = null; if (values != null) { // Build projection map from expected values final ArrayList<String> projectionList = new ArrayList<String>(); for (Map.Entry<String, Object> entry : values.valueSet()) { projectionList.add(entry.getKey()); } projection = projectionList.toArray(new String[projectionList.size()]); } final Cursor cursor = provider.query(mUri, projection, mSelection, selectionArgs, null); try { numRows = cursor.getCount(); if (projection != null) { while (cursor.moveToNext()) { for (int i = 0; i < projection.length; i++) { final String cursorValue = cursor.getString(i); final String expectedValue = values.getAsString(projection[i]); if (!TextUtils.equals(cursorValue, expectedValue)) { // Throw exception when expected values don't match Log.e(TAG, this.toString()); throw new OperationApplicationException("Found value " + cursorValue + " when expected " + expectedValue + " for column " + projection[i]); } } } } } finally { cursor.close(); } } else { Log.e(TAG, this.toString()); throw new IllegalStateException("bad type, " + mType); } if (mExpectedCount != null && mExpectedCount != numRows) { Log.e(TAG, this.toString()); throw new OperationApplicationException("wrong number of rows: " + numRows); } return new ContentProviderResult(numRows); }

在resolveValueBackReferences方法中會先判斷mValuesBackReferences 是否為空,如果為空就直接返回mValues,mValues就是通過withValue 或者 withValues方法填進去的值所組裝的ContentValue對象,比如要更新或要插入的值。如果mValuesBackReferences != null(使用了withValueBackReference或withValueBackReferences),就需要處理”向后引用”的值,其實就是找出第“previousResult”個已經完成的ContentProviderOperation所返回的ContentProviderResult的值并與對應的key(列名)綁定起來。查找“向后引用”是在backRefToValue函數中實現,繼續往下看。

resolveSelectionArgsBackReferences函數也是類似的作用。

public ContentValues resolveValueBackReferences( ContentProviderResult[] backRefs, int numBackRefs) { if (mValuesBackReferences == null) { return mValues; } final ContentValues values; if (mValues == null) { values = new ContentValues(); } else { values = new ContentValues(mValues); } for (Map.Entry<String, Object> entry : mValuesBackReferences.valueSet()) { String key = entry.getKey(); Integer backRefIndex = mValuesBackReferences.getAsInteger(key); if (backRefIndex == null) { Log.e(TAG, this.toString()); throw new IllegalArgumentException("values backref " + key + " is not an integer"); } values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex)); } return values; }

在backRefToValue中處理了兩種情況,如果ContentProviderResult中uri不為空,就返回uri對應的ID;如果為空就返回count值。所以,從上面的apply函數可以看出,insert對應的是ID;而delete、update、assertQuery則會返回count。

private long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs, Integer backRefIndex) { if (backRefIndex >= numBackRefs) { Log.e(TAG, this.toString()); throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex + " but there are only " + numBackRefs + " back refs"); } ContentProviderResult backRef = backRefs[backRefIndex]; long backRefValue; if (backRef.uri != null) { backRefValue = ContentUris.parseId(backRef.uri); } else { backRefValue = backRef.count; } return backRefValue; }

使用事務

參考MediaProvider.java的實現,在applyBatch中使用事務:

@NonNull @Override public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); db.beginTransaction(); try { ContentProviderResult[] results = super.applyBatch(operations); db.setTransactionSuccessful(); return results; } finally { db.endTransaction(); } }

參考

Android Developer 中關于ContentProvider批量操作的介紹 – 聯系人提供程序 Android 聯系人提供程序同步適配器 Gibhub sample code Stackoverflow 上關于withValueBackReference 的解答 Android’s ContentProviderOperation: “withBackReference” explained Android利用ContentProviderOperation添加聯系人


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 西峡县| 吉首市| 龙口市| 沙洋县| 万源市| 金昌市| 南雄市| 江口县| 察隅县| 陇南市| 图木舒克市| 巫溪县| 通辽市| 行唐县| 东乡| 扎鲁特旗| 台北县| 灯塔市| 扎赉特旗| 宣恩县| 巫溪县| 澎湖县| 邵阳县| 民乐县| 中阳县| 夹江县| 忻城县| 锡林郭勒盟| 衡山县| 安西县| 垦利县| 开鲁县| 林甸县| 九寨沟县| 屏东县| 巧家县| 苍山县| 新余市| 鄂伦春自治旗| 哈密市| 南阳市|