ContentProvider 的批处理操作
来源:互联网 发布:河北邢台招聘程序员 编辑:程序博客网 时间:2024/06/05 06:05
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 对象:
这个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):
关于”向后引用”可以参考官网的一些说明
最后通过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添加联系人
- ContentProvider 的批处理操作
- ContentProvider的数据批处理
- 操作通讯记录的ContentProvider
- Android--操作联系人的ContentProvider
- ContentProvider的批量操作优化
- 操作注册表的批处理代码
- Android数据库的批处理操作
- JDBC操作数据库的批处理
- ContentProvider 操作其他应用的数据库
- 批处理操作
- JDBC中进行批处理操作的例子
- OpenGL渲染字体的批处理操作
- Bat批处理一些常见的操作命令
- JDBC的批处理操作三种方式
- JDBC的批处理操作三种方式
- OpenGL渲染字体的批处理操作
- OpenGL: 渲染字体的批处理操作
- JDBC的批处理操作三种方式
- 8.CSS之盒子模型
- 如何方便的在Rails中使用Octicons符号字符
- HDU2222【AC自动机(基础·模板)】
- android活动的生存期
- 粗解Vue.js的render方法
- ContentProvider 的批处理操作
- 关于Node.js后端架构的一点后知后觉
- OD用栈回溯法找程序流程点
- opencv读取手机摄像头
- Mac下csv文件乱码
- raspberrypi搭建vsftpd
- lintcode python— 最长公共字符串
- 将获得数据存储在txt文件上
- 软件开发过程中的三层架构的理解