API Guides > Contacts Provider (三)

本文介绍了ContactsProvider中数据的查询与批量修改方法,包括实体查询、批量操作、使用yield points进行性能优化等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Contacts Provider Access


这一章节讲述了如何从Contacts Provider访问数据。主要关注以下几点:

  • Entity queries.实体查询
  • Batch modification.批量修改
  • Retrieval and modification with intents.使用intents检索和修改
  • Data integrity.数据完整性
Contacts Provider Sync Adapters中有更多关于使用sync adapter修改数据的详细说明。

Querying entities

由于Contacts Provider的数据表是层次结构组织的,所以经常会需要查询一行和关联到这一行的子数据行。例如,要显示一个联系人的所有信息,你需要去查询多个 ContactsContract.RawContacts关联到ContactsContract.Contacts的数据,或者所有管理到 ContactsContract.RawContactsContactsContract.CommonDataKinds.Email数据。为了便于实现这个功能,Contacts Provider 提供了实体(entity)的数据结构,它像一个连接各个数据表的数据结构。
一个实体(entity)像一个数据表,它包含从一个父表和它的子表中的多个列。当你查询一个实体(entity),需要提供查询列和查询条件。它返回的结果是一个包含各个子表的Cursor。例如,如果要查询一个人的名字和他所有的邮件地址,使用 ContactsContract.Contacts.Entity会得到一个包含所有emai的Cursor。
实体(entities)方便了查询。使用实体(entity)查询,可以一次性得到联系人的所有Data数据和raw contact数据。不需要先查附表得到一个ID,再由这个ID区查询字表。Contacts Provider在一个transaction处理entity的查询,这样能保持数据检索的内部一致性。

注意:一个实体通常没有包含父表和子表所有的列。如果访问到实体没有的列,就会得到一个异常。

下面的代码片段展示了如何检索一个contact的所有raw contact。这个代码片段是一个拥有两个activity的大程序的一部分,这两个activity是“主要”和“详细信息”的activity。“主要”的activity展示一列联系人的数据,当用户选择一个联系人时,把它的ID传给“详细信息”的activity。详细信息的activity使用ContactsContract.Contacts.Entity 展示了改contact的所有Data数据。

下面的代码是“详细信息”activity的代码片段:

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    mContactUri = Uri.withAppendedPath(
            mContactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);

    // Initializes the loader identified by LOADER_ID.
    getLoaderManager().initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this);      // The context of the activity

    // Creates a new cursor adapter to attach to the list view
    mCursorAdapter = new SimpleCursorAdapter(
            this,                        // the context of the activity
            R.layout.detail_list_item,   // the view item containing the detail widgets
            mCursor,                     // the backing cursor
            mFromColumns,                // the columns in the cursor that provide the data
            mToViews,                    // the views in the view item that display the data
            0);                          // flags

    // Sets the ListView's backing adapter.
    mRawContactList.setAdapter(mCursorAdapter);
...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    String[] projection =
        {
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
        };

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    String sortOrder =
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
            " ASC";

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return new CursorLoader(
            getApplicationContext(),  // The activity's context
            mContactUri,              // The entity content URI for a single contact
            projection,               // The columns to retrieve
            null,                     // Retrieve all the raw contacts and their data rows.
            null,                     //
            sortOrder);               // Sort by the raw contact ID.
}

数据查询完成后,  LoaderManager 回调 onLoadFinished()。查询的结果会由一个Cursor参数传递。你可以从这个Cursor获取数据用来显示或其他操作。

Batch modification

尽可能使用批量修改来插入、更新和删除Contacts Provider中的数据。通过创建存储ContentProviderOperation的 ArrayList,再调用applyBatch()的方法。因为Contacts Provider在一个transaction中执行applyBatch()的所有操作,所以能够保持数据的一致性。批量操作也可以方便插入一个联系人数据及其详细数据。

注意:如果只是要改一行raw contact的数据,首先考虑用发intent给联系人应用的方式,而不是自己去操作数据。使用intent的方式在Retrieval and modification with intents有更详细的说明。

Yield points
批量修改包含大量的操作可能会阻塞进程,导致用户体验不好。为了把修改操作放在尽可能少的操作列表里面,并且要防止阻塞进程,可以为一个或多个操作设置yield points。 yield point 就是 isYieldAllowed()为true的 ContentProviderOperation对象。当Contacts Provider执行到yield point的时候,它暂停操作,让其他进程先执行,并关闭当前的操作。当provider重新开始工作后,它继续之前的工作,从操作列表中取出下一个操作,开启一个新的transaction。

Yield point导致调用applyBatch()的时候,会有多个transaction。yield point要设置在一系列相关行操作的最后一个操作。例如,在添加联系人的时候,yield point要设置在添加raw contact和它的data 的最后一个操作,或者添加在一系列跟同一个联系人相关的操作中的最后一个操作。

yield point也是一个原子操作。所有在两个yield point直接操作,在一次操作中要么全部成功,要不全部无效。如果没有设置yield point,整个批量处理就是一个最小的原子操作。如果设置了原子操作,你避免降低系统性能作,而在同一时间,确保操作的子集是原子。

Modification back references

添加一个新的raw contact和它的data数据,使用一系列的ContentProviderOperation对象,你必须把data数据的RAW_CONTACT_ID设置成新添加的raw contact的ID。然而,当创建data数据行的时候,raw contact的ID还没生成。因为这一系列的ContentProviderOperation对象是一个原子操作,还不能得到创建raw contact的结果。为了解决这个问题,ContentProviderOperation.Builder有一个withValueBackReference()的方法。这个方法可以让你使用前面操作的结果来插入数据。

withValueBackReference()有两个参数:  

key    key-value键值对的键。值应该是所修改的表的列名。 previousResult     (简单来说,就是操作的索引值)     applyBatch()返回值ContentProviderResult数组的索引。开始批量操作后,每个操作的结果都存在一个数组里。previousResult操作结果数组的一个索引,通过key值检索和存储。这样子就可以在插入一个新的raw contact联系人后,得到它的_ID,然后再你插入Data数据的时候,就可以通过“后引用”去使用这个ID。     在调用applyBatch()的时候,整个结果数组就创建好了,结果数组的大小和ContentProviderOperation的数组大小一样。但是,结果数组的初始值都是null,如果在操作结束前,通过“后引用”来使用操作的结果,就会抛出异常。   下面的代码片段显示如何使用batch插入一个raw contact和数据。这些代码包括使用yield points和“后引用”。这个代码片段是createContacEntry()方法的扩展,是Contact Manager  例子源码中的ContactAdder的一部分。 第一个源码片在UI中获取联系人的data数据。用户已经选择了用来添加联系人的账户:
// Creates a contact entry from the current UI values, using the currently-selected account.
protected void createContactEntry() {
    /*
     * Gets values from the UI
     */
    String name = mContactNameEditText.getText().toString();
    String phone = mContactPhoneEditText.getText().toString();
    String email = mContactEmailEditText.getText().toString();

    int phoneType = mContactPhoneTypes.get(
            mContactPhoneTypeSpinner.getSelectedItemPosition());

    int emailType = mContactEmailTypes.get(
            mContactEmailTypeSpinner.getSelectedItemPosition());

下一个源码片段创建一个operation去插入一个raw contact数据到 ContactsContract.RawContacts
  /*
     * 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());
接下来的代码,创建了显示名字,电话号码和邮箱的data数据。 每一个操作使用  withValueBackReference() 去获取 RAW_CONTACT_ID。这个引用指向 第一个操作的 ContentProviderResult,第一个操作是创建raw contact并返回它的_ID。这样,每个新建的data数据,就自动连接它的  RAW_CONTACT_ID到新插入的 ContactsContract.RawContacts 。

 ContentProviderOperation.Builder在添加email的时候,使用 withYieldAllowed()添加了一个yield point:

 // Creates the display name for the new raw contact, as a StructuredName data row.
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified phone number and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified email and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

最后一个代码片段调用 applyBatch()去插入一个新的raw  contact 和它的data数据行。

// Ask the Contacts Provider to create a new contact
    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
            mSelectedAccount.getType() + ")");
    Log.d(TAG,"Creating contact: " + name);

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {

            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
    } catch (Exception e) {

            // Display a warning
            Context ctx = getApplicationContext();

            CharSequence txt = getString(R.string.contactCreationFailure);
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(ctx, txt, duration);
            toast.show();

            // Log exception
            Log.e(TAG, "Exception encountered while inserting contact: " + e);
    }
}

批量操作允许实现一个优化多线程控制(  optimistic concurrency control),一个无需锁定底层数据库的方法。使用这个方法,在调用transaction 的时候,检查一下是否有其他的修改会同时发生。如果发现同时修改,就取消这次修改。

Optimistic concurrency control 对手机设备很有用,手机设备只有一个用户的,并且很少同时访问数据库。因为数据库没有加锁,就不用花费时间去加锁和解锁。

以下是使用ContactsContract.RawContacts 的步骤:

  1. 在检索数据的时候,同时检索raw contact的VERSION列。
  2. 创建一个ContentProviderOperation.Builder,调用newAssertQuery(Uri)方法,content URI是RawContacts.CONTENT_URI 和raw contact的_ID.
  3. ContentProviderOperation.Builder调用 withValue()的方法去对比刚刚检索到的 VERSION
  4. 在同一个 ContentProviderOperation.Builder中调用 withExpectedCount() 去确保在这次断言中只有一个修改。
  5. 调用Call build()创建 ContentProviderOperation,然后把这个ContentProviderOperation加入到调用 applyBatch()操作的第一个操作。
  6. 调用批量操作。

如果raw contact在你读取数据,然后去修改它的期间被更新, ContentProviderOperation 将会失败,然后整个力量操作都会失效。这是需要重新尝试这次操作。

下面的代码片段展示如何在使用CursorLoader查询一个raw contact后,创建一个断言ContentProviderOperation 

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {

    // Gets the raw contact's _ID and VERSION values
    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
}

...

// Sets up a Uri for the assert operation
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);

// Creates a builder for the assert operation
ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);

// Adds the assertions to the assert operation: checks the version and count of rows tested
assertOp.withValue(SyncColumns.VERSION, mVersion);
assertOp.withExpectedCount(1);

// Creates an ArrayList to hold the ContentProviderOperation objects
ArrayList ops = new ArrayList<ContentProviderOperationg>;

ops.add(assertOp.build());

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try
    {
        ContentProviderResult[] results =
                getContentResolver().applyBatch(AUTHORITY, ops);

    } catch (OperationApplicationException e) {

        // Actions you want to take if the assert operation fails go here
    }




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值