API Guides Contacts Provider (三)
Contacts Provider Access
這一章節講述了如何從Contacts Provider訪問數據。主要關注以下幾點:
- Entity queries.實體查詢
- Batch modification.批量修改
- Retrieval and modification with intents.使用intents檢索和修改
- Data integrity.數據完整性
Querying entities
由于Contacts Provider的數據表是層次結構組織的,所以經常會需要查詢一行和關聯到這一行的子數據行。例如,要顯示一個聯系人的所有信息,你需要去查詢多個ContactsContract.RawContacts關聯到ContactsContract.Contacts的數據,或者所有管理到?ContactsContract.RawContacts的ContactsContract.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 initializenull, // Arguments for the loader (in this case, none)this); // The context of the activity// Creates a new cursor adapter to attach to the list viewmCursorAdapter = new SimpleCursorAdapter(this, // the context of the activityR.layout.detail_list_item, // the view item containing the detail widgetsmCursor, // the backing cursormFromColumns, // the columns in the cursor that provide the datamToViews, // the views in the view item that display the data0); // 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 contextmContactUri, // The entity content URI for a single contactprojection, // The columns to retrievenull, // 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 operationsops.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 operationsops.add(op.build());// Inserts the specified phone number and type as a Phone data rowop =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 operationsops.add(op.build());// Inserts the specified email and type as a Phone data rowop =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 operationsops.add(op.build());最后一個代碼片段調用?applyBatch()去插入一個新的raw ?contact 和它的data數據行。
// Ask the Contacts Provider to create a new contactLog.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 warningContext ctx = getApplicationContext();CharSequence txt = getString(R.string.contactCreationFailure);int duration = Toast.LENGTH_SHORT;Toast toast = Toast.makeText(ctx, txt, duration);toast.show();// Log exceptionLog.e(TAG, "Exception encountered while inserting contact: " + e);} }批量操作允許實現一個優化多線程控制(? optimistic concurrency control),一個無需鎖定底層數據庫的方法。使用這個方法,在調用transaction 的時候,檢查一下是否有其他的修改會同時發生。如果發現同時修改,就取消這次修改。
Optimistic concurrency control 對手機設備很有用,手機設備只有一個用戶的,并且很少同時訪問數據庫。因為數據庫沒有加鎖,就不用花費時間去加鎖和解鎖。
以下是使用ContactsContract.RawContacts?的步驟:
如果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 valuesmRawContactID = 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}
總結
以上是生活随笔為你收集整理的API Guides Contacts Provider (三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个比CAM350好用的看GERBER软
- 下一篇: PHP 100 个最常用的函数