• 简单探索ContentProviderOperation


    前面一片文章中用到了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中采用的就是这个思路。

  • 相关阅读:
    六、显式锁和AQS
    五、原子操作(CAS)
    四、线程的并发工具类
    BZOJ 2176 Strange string ——最小表示法
    BZOJ 2882 工艺 ——后缀自动机 最小表示法
    Codeforces Round #401 (Div. 2)
    BZOJ 2331 [SCOI2011]地板 ——插头DP
    BZOJ 2005 [Noi2010]能量采集 ——Dirichlet积
    BZOJ 1087 [SCOI2005]互不侵犯King ——状压DP
    BZOJ 1072 [SCOI2007]排列perm ——状压DP
  • 原文地址:https://www.cnblogs.com/wlrhnh/p/3477252.html
Copyright © 2020-2023  润新知