前面一片文章中用到了ContentProviderOperation,那么我们就来看看ContentProviderOperation到底是怎么工作的。
1. ContentProviderOperation实现了Parcelable,构造器是私有的,因此不能直接创建该对象,代码如下:
1 public class ContentProviderOperation implements Parcelable { 2 /** @hide exposed for unit tests */ 3 public final static int TYPE_INSERT = 1; 4 /** @hide exposed for unit tests */ 5 public final static int TYPE_UPDATE = 2; 6 /** @hide exposed for unit tests */ 7 public final static int TYPE_DELETE = 3; 8 /** @hide exposed for unit tests */ 9 public final static int TYPE_ASSERT = 4; 10 11 private final int mType; 12 private final Uri mUri; 13 private final String mSelection; 14 private final String[] mSelectionArgs; 15 private final ContentValues mValues; 16 private final Integer mExpectedCount; 17 private final ContentValues mValuesBackReferences; 18 private final Map<Integer, Integer> mSelectionArgsBackReferences; 19 private final boolean mYieldAllowed; 20 21 private final static String TAG = "ContentProviderOperation"; 22 private ContentProviderOperation(Builder builder) { 23 mType = builder.mType; 24 mUri = builder.mUri; 25 mValues = builder.mValues; 26 mSelection = builder.mSelection; 27 mSelectionArgs = builder.mSelectionArgs; 28 mExpectedCount = builder.mExpectedCount; 29 mSelectionArgsBackReferences = builder.mSelectionArgsBackReferences; 30 mValuesBackReferences = builder.mValuesBackReferences; 31 mYieldAllowed = builder.mYieldAllowed; 32 }
提供了一些方法来进行RUID操作,代码如下:
1 public static Builder newInsert(Uri uri) { 2 return new Builder(TYPE_INSERT, uri); 3 } 4 5 /** 6 * Create a {@link Builder} suitable for building an update 7 * {@link ContentProviderOperation}. 8 * @param uri The {@link Uri} that is the target of the update. 9 * @return a {@link Builder} 10 */ 11 public static Builder newUpdate(Uri uri) { 12 return new Builder(TYPE_UPDATE, uri); 13 } 14 15 /** 16 * Create a {@link Builder} suitable for building a delete 17 * {@link ContentProviderOperation}. 18 * @param uri The {@link Uri} that is the target of the delete. 19 * @return a {@link Builder} 20 */ 21 public static Builder newDelete(Uri uri) { 22 return new Builder(TYPE_DELETE, uri); 23 } 24 25 /** 26 * Create a {@link Builder} suitable for building a 27 * {@link ContentProviderOperation} to assert a set of values as provided 28 * through {@link Builder#withValues(ContentValues)}. 29 */ 30 public static Builder newAssertQuery(Uri uri) { 31 return new Builder(TYPE_ASSERT, uri); 32 }
其中比较难理解的是newAssertQuery方法,该方法并不是用来查询数据的,可以理解为断点查询,也就是查询有没有符合条件的数据,如果没有,会抛出一个OperationApplicationException异常。
1 public void onClick(View v) { 2 final ArrayList<ContentProviderOperation> operationList = 3 new ArrayList<ContentProviderOperation>(); 4 ContentProviderOperation.Builder builder = null; 5 int rawContactIndex = 0; 6 7 builder = ContentProviderOperation 8 .newAssertQuery(RawContacts.CONTENT_URI); 9 builder.withSelection("version=?", new String[]{String.valueOf(3)}) 10 .withExpectedCount(3); 11 operationList.add(builder.build()); 12 13 try { 14 ContentProviderResult[] res = getContentResolver(). 15 applyBatch(ContactsContract.AUTHORITY, operationList); 16 Log.e(TAG, "res.length = " + res.length); 17 for (int i = 0; i < res.length; i++) { 18 Log.e(TAG, i + "res.toString() = " + res[i].toString()); 19 Log.e(TAG, i + "res.uri = " + res[i].uri); 20 } 21 } catch (RemoteException e) { 22 } catch (OperationApplicationException e) { 23 } 24 }
以上这个onClick()方法的作用是查询version==3的数据是否有三条uri对应的表里面,如果为真,res != null,反之会抛出OperationApplicationException异常,其他使用情形可以参照:http://blog.sina.com.cn/s/blog_a387d6d2010114mp.html
2. 我们在使用ContentProviderOperation时,通常是采用这样的方式
builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI); 然后我们可以给这个builder添加一些条件,最后调用他的build()方法返回一个ContentProviderOperation对象。看它的
build()方法, 代码如下:
1 /** Create a ContentProviderOperation from this {@link Builder}. */ 2 public ContentProviderOperation build() { 3 if (mType == TYPE_UPDATE) { 4 if ((mValues == null || mValues.size() == 0) && 5 (mValuesBackReferences == null || 6 mValuesBackReferences.size() == 0)) { 7 throw new IllegalArgumentException("Empty values"); 8 } 9 } 10 if (mType == TYPE_ASSERT) { 11 if ((mValues == null || mValues.size() == 0) && 12 (mValuesBackReferences == null || 13 mValuesBackReferences.size() == 0) && 14 (mExpectedCount == null)) { 15 throw new IllegalArgumentException("Empty values"); 16 } 17 } 18 return new ContentProviderOperation(this); 19 }
可以看到new ContentProviderOperation()方法里做了赋值的操作。
3. 最后调用 getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList) 进行批处理操作,可以这样理解,之前构造ContentProviderOperation的过程就像是在组合SQL语句,而applyBatch相当于执行SQL语句。具体流程是: ContentResolver.java(applyBatch())--> ContentProviderClient.java(applyBatch()) --> ContentProvider.java, ContentProvider的applyBatch()方法如下:
1 public ContentProviderResult[] applyBatch( 2 ArrayList<ContentProviderOperation> operations) 3 throws OperationApplicationException { 4 final int numOperations = operations.size(); 5 final ContentProviderResult[] results = 6 new ContentProviderResult[numOperations]; 7 for (int i = 0; i < numOperations; i++) { 8 results[i] = operations.get(i).apply(this, results, i); 9 } 10 return results; 11 }
可以看到,用了一个循环,但最终调用的还是ContentProviderOperation的apply方法:
1 public ContentProviderResult apply(ContentProvider provider, 2 ContentProviderResult[] backRefs, int numBackRefs) throws OperationApplicationException { 3 ContentValues values = 4 resolveValueBackReferences(backRefs, numBackRefs);
首先调用了resolveValueBackReferences()方法, 他会返回mValues或者加工后的mValues:
1 public ContentValues resolveValueBackReferences(ContentProviderResult[] backRefs, int numBackRefs) { 2 if () { 3 return mValues; 4 } 5 final ContentValues values; 6 if (mValues == null) { 7 values = new ContentValues(); 8 } else { 9 values = new ContentValues(mValues); 10 } 11 for (Map.Entry<String, Object> entry : 12 mValuesBackReferences.valueSet()) { 13 String key = entry.getKey(); 14 Integer backRefIndex = mValuesBackReferences.getAsInteger(key); 15 if (backRefIndex == null) { 16 Log.e(TAG, this.toString()); 17 throw new IllegalArgumentException(" 18 values backref " + key + " is not an integer"); 19 } 20 values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex)); 21 } 22 return values; 23 }
为了理解mValuesBackReferences == null这个判断,我们先得知道一个问题,在前面一篇文章中,从第二个ContentProviderOperation开始使用了builder.withValueBackReference(Email.RAW_CONTACT_ID, rawContactIndex),其实就是给mValuesBackReferences赋了值,代码如下:
1 public Builder withValueBackReference(String key, int previousResult) { 2 if (mType != TYPE_INSERT && mType != TYPE_UPDATE 3 && mType != TYPE_ASSERT) { 4 throw new IllegalArgumentException("only inserts, updates, and asserts can have value back-references"); 5 } 6 if (mValuesBackReferences == null) { 7 mValuesBackReferences = new ContentValues(); 8 } 9 mValuesBackReferences.put(key, previousResult); 10 return this; 11 }
可以看到这个方法不能用于TYPE_DELETE。
继续看resolveValueBackReferences方法,如果mValuesBackReferences == null,直接返回mValues,mValues就是通过withValues()方法填进去的值所组装的ContentValue对象,比如要更新或要插入的值。如果mValuesBackReferences != null,那么会将前一个ContentProviderResult的uri里面的id取出来赋给Email.RAW_CONTACT_ID,就是builder.withValueBackReference(Email.RAW_CONTACT_ID, rawContactIndex)的第一个变量,现在withValueBackReference方法的作用我们也该清楚了。具体的可以参照:http://blog.sina.com.cn/s/blog_a387d6d2010114mp.html
继续看apply()方法:
1 public ContentProviderResult apply(ContentProvider provider, ContentProviderResult[] backRefs, 2 int numBackRefs) throws OperationApplicationException { 3 ContentValues values = resolveValueBackReferences(backRefs, numBackRefs); 4 String[] selectionArgs = 5 resolveSelectionArgsBackReferences(backRefs, numBackRefs); 6 7 if (mType == TYPE_INSERT) { 8 Uri newUri = provider.insert(mUri, values); 9 if (newUri == null) { 10 throw new OperationApplicationException("insert failed"); 11 } 12 return new ContentProviderResult(newUri); 13 } 14 15 int numRows; 16 if (mType == TYPE_DELETE) { 17 numRows = provider.delete(mUri, mSelection, selectionArgs); 18 } else if (mType == TYPE_UPDATE) { 19 numRows = provider.update(mUri, values, mSelection, selectionArgs); 20 } else if (mType == TYPE_ASSERT) { 21 // Assert that all rows match expected values 22 String[] projection = null; 23 if (values != null) { 24 // Build projection map from expected values 25 final ArrayList<String> projectionList = new ArrayList<String>(); 26 for (Map.Entry<String, Object> entry : values.valueSet()) { 27 projectionList.add(entry.getKey()); 28 } 29 projection = projectionList.toArray(new String[projectionList.size()]); 30 } 31 final Cursor cursor = provider.query(mUri, projection, mSelection, selectionArgs, null); 32 try { 33 numRows = cursor.getCount(); 34 if (projection != null) { 35 while (cursor.moveToNext()) { 36 for (int i = 0; i < projection.length; i++) { 37 final String cursorValue = cursor.getString(i); 38 final String expectedValue = values.getAsString(projection[i]); 39 if (!TextUtils.equals(cursorValue, expectedValue)) { 40 // Throw exception when expected values don't match 41 Log.e(TAG, this.toString()); 42 throw new OperationApplicationException("Found value " + cursorValue 43 + " when expected " + expectedValue + " for column " 44 + projection[i]); 45 } 46 } 47 } 48 } 49 } finally { 50 cursor.close(); 51 } 52 } else { 53 Log.e(TAG, this.toString()); 54 throw new IllegalStateException("bad type, " + mType); 55 } 56 57 if (mExpectedCount != null && mExpectedCount != numRows) { 58 Log.e(TAG, this.toString()); 59 throw new OperationApplicationException("wrong number of rows: " + numRows); 60 } 61 62 return new ContentProviderResult(numRows); 63 }
我们可以发现,ContentProviderOperation的apply()方法才是真正的执行RUID操作的地方。
同时我们在上面的代码中并未发现使用事务,如果我们要求操作失败时需回滚,那么就应该添加事务,经过上面的分析,可以发现一个比较好的思路就是在我们自己的ContentProvider里面重写appltBatch()方法,并在其中添加事务,后面会有专门分析ContactsProvider文章,我们会看到,其实联系人的ContactsProcider中采用的就是这个思路。