1. 批处理访问:你能够用ContentProviderOperation类中的方法创建一个访问的批处理调用,然后把这个批处理调用应用于ContentResolver.applyBatch()方法;
2. 异步查询:你应该在一个独立的线程中做查询的动作,使用CursorLoader对象是异步方法之一。在“装载器”指南中的例子演示怎样使用这个对象;
3. 通过Intent访问数据:虽然你不能直接给提供器发送一个Intent对象,但是你可以给提供的应用程序发送一个Intent对象,这通常是修改提供器数据的最好方式。
批处理访问和通过Intent对象的修改会在以下章节中来描述。
批处理访问
对于提供器的批处理访问对用同一个方法向一个或多个表中插入大量的数据,或是执行一般的跨进程的原子化的事务性操作是有益的。
要用批处理模式访问一个提供器,就需要创建一个ContentProviderOperation对象的数组,然后把这个数组分发给内容提供器的ContentResolver.applyBatch()方法。你要把这个内容提供器的权限传递给这个方法,而不是特定的内容URI,因为它允许数组中每个ContentProviderOperation对象针对不同的表来工作。一个ContentResolver.applyBatch()方法返回一个结果数组。
以下示例代码(ContactAdder.java)演示批处理插入:
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.contactmanager;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
import android.accounts.OnAccountsUpdateListener;
import android.app.Activity;
import android.content.ContentProviderOperation;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
import java.util.ArrayList;
import java.util.Iterator;
public final class ContactAdder extends Activity implements OnAccountsUpdateListener
{
public static final String TAG = "ContactsAdder";
public static final String ACCOUNT_NAME =
"com.example.android.contactmanager.ContactsAdder.ACCOUNT_NAME";
public static final String ACCOUNT_TYPE =
"com.example.android.contactmanager.ContactsAdder.ACCOUNT_TYPE";
private ArrayList
private AccountAdapter mAccountAdapter;
private Spinner mAccountSpinner;
private EditText mContactEmailEditText;
private ArrayList
private Spinner mContactEmailTypeSpinner;
private EditText mContactNameEditText;
private EditText mContactPhoneEditText;
private ArrayList
private Spinner mContactPhoneTypeSpinner;
private Button mContactSaveButton;
private AccountData mSelectedAccount;
/**
* Called when the activity is first created. Responsible for initializing the UI.
*/
@Override
public void onCreate(Bundle savedInstanceState)
{
Log.v(TAG, "Activity State: onCreate()");
super.onCreate(savedInstanceState);
setContentView(R.layout.contact_adder);
// Obtain handles to UI objects
mAccountSpinner = (Spinner) findViewById(R.id.accountSpinner);
mContactNameEditText = (EditText) findViewById(R.id.contactNameEditText);
mContactPhoneEditText = (EditText) findViewById(R.id.contactPhoneEditText);
mContactEmailEditText = (EditText) findViewById(R.id.contactEmailEditText);
mContactPhoneTypeSpinner = (Spinner) findViewById(R.id.contactPhoneTypeSpinner);
mContactEmailTypeSpinner = (Spinner) findViewById(R.id.contactEmailTypeSpinner);
mContactSaveButton = (Button) findViewById(R.id.contactSaveButton);
// Prepare list of supported account types
// Note: Other types are available in ContactsContract.CommonDataKinds
// Also, be aware that type IDs differ between Phone and Email, and MUST be computed
// separately.
mContactPhoneTypes = new ArrayList
mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_HOME);
mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_OTHER);
mContactEmailTypes = new ArrayList
mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_HOME);
mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_WORK);
mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_MOBILE);
mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_OTHER);
// Prepare model for account spinner
mAccounts = new ArrayList
mAccountAdapter = new AccountAdapter(this, mAccounts);
mAccountSpinner.setAdapter(mAccountAdapter);
// Populate list of account types for phone
ArrayAdapter
adapter = new ArrayAdapter
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Iterator
iter = mContactPhoneTypes.iterator();
while (iter.hasNext()) {
adapter.add(ContactsContract.CommonDataKinds.Phone.getTypeLabel(
this.getResources(),
iter.next(),
getString(R.string.undefinedTypeLabel)).toString());
}
mContactPhoneTypeSpinner.setAdapter(adapter);
mContactPhoneTypeSpinner.setPrompt(getString(R.string.selectLabel));
// Populate list of account types for email
adapter = new ArrayAdapter
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
iter = mContactEmailTypes.iterator();
while (iter.hasNext()) {
adapter.add(ContactsContract.CommonDataKinds.Email.getTypeLabel(
this.getResources(),
iter.next(),
getString(R.string.undefinedTypeLabel)).toString());
}
mContactEmailTypeSpinner.setAdapter(adapter);
mContactEmailTypeSpinner.setPrompt(getString(R.string.selectLabel));
// Prepare the system account manager. On registering the listener below, we also ask for
// an initial callback to pre-populate the account list.
AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
// Register handlers for UI elements
mAccountSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView parent, View view, int position, long i) {
updateAccountSelection();
}
public void onNothingSelected(AdapterView parent) {
// We don't need to worry about nothing being selected, since Spinners don't allow
// this.
}
});
mContactSaveButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
onSaveButtonClicked();
}
});
}
/**
* Actions for when the Save button is clicked. Creates a contact entry and terminates the
* activity.
*/
private void onSaveButtonClicked() {
Log.v(TAG, "Save button clicked");
createContactEntry();
finish();
}
/**
* Creates a contact entry from the current UI values in the account named by mSelectedAccount.
*/
protected void createContactEntry() {
// Get values from 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());;
// Prepare contact creation request
//
// Note: We use RawContacts because this data must be associated with a particular account.
// The system will aggregate this with any other data for this contact and create a
// coresponding entry in the ContactsContract.Contacts provider for us.
ArrayList
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName())
.build());
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)
.build());
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)
.build());
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.DATA, email)
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)
.build());
// Ask the Contact provider to create a new contact
Log.i(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
mSelectedAccount.getType() + ")");
Log.i(TAG,"Creating contact: " + name);
try {
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
// Display 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, "Exceptoin encoutered while inserting contact: " + e);
}
}
/**
* Called when this activity is about to be destroyed by the system.
*/
@Override
public void onDestroy() {
// Remove AccountManager callback
AccountManager.get(this).removeOnAccountsUpdatedListener(this);
super.onDestroy();
}
/**
* Updates account list spinner when the list of Accounts on the system changes. Satisfies
* OnAccountsUpdateListener implementation.
*/
public void onAccountsUpdated(Account[] a) {
Log.i(TAG, "Account list update detected");
// Clear out any old data to prevent duplicates
mAccounts.clear();
// Get account data from system
AuthenticatorDescription[] accountTypes = AccountManager.get(this).getAuthenticatorTypes();
// Populate tables
for (int i = 0; i < a.length; i++) {
// The user may have multiple accounts with the same name, so we need to construct a
// meaningful display name for each.
String systemAccountType = a[i].type;
AuthenticatorDescription ad = getAuthenticatorDescription(systemAccountType,
accountTypes);
AccountData data = new AccountData(a[i].name, ad);
mAccounts.add(data);
}
// Update the account spinner
mAccountAdapter.notifyDataSetChanged();
}
/**
* Obtain the AuthenticatorDescription for a given account type.
* @param type The account type to locate.
* @param dictionary An array of AuthenticatorDescriptions, as returned by AccountManager.
* @return The description for the specified account type.
*/
private static AuthenticatorDescription getAuthenticatorDescription(String type,
AuthenticatorDescription[] dictionary) {
for (int i = 0; i < dictionary.length; i++) {
if (dictionary[i].type.equals(type)) {
return dictionary[i];
}
}
// No match found
throw new RuntimeException("Unable to find matching authenticator");
}
/**
* Update account selection. If NO_ACCOUNT is selected, then we prohibit inserting new contacts.
*/
private void updateAccountSelection() {
// Read current account selection
mSelectedAccount = (AccountData) mAccountSpinner.getSelectedItem();
}
/**
* A container class used to repreresent all known information about an account.
*/
private class AccountData {
private String mName;
private String mType;
private CharSequence mTypeLabel;
private Drawable mIcon;
/**
* @param name The name of the account. This is usually the user's email address or
* username.
* @param description The description for this account. This will be dictated by the
* type of account returned, and can be obtained from the system AccountManager.
*/
public AccountData(String name, AuthenticatorDescription description) {
mName = name;
if (description != null) {
mType = description.type;
// The type string is stored in a resource, so we need to convert it into something
// human readable.
String packageName = description.packageName;
PackageManager pm = getPackageManager();
if (description.labelId != 0) {
mTypeLabel = pm.getText(packageName, description.labelId, null);
if (mTypeLabel == null) {
throw new IllegalArgumentException("LabelID provided, but label not found");
}
} else {
mTypeLabel = "";
}
if (description.iconId != 0) {
mIcon = pm.getDrawable(packageName, description.iconId, null);
if (mIcon == null) {
throw new IllegalArgumentException("IconID provided, but drawable not " +
"found");
}
} else {
mIcon = getResources().getDrawable(android.R.drawable.sym_def_app_icon);
}
}
}
public String getName() {
return mName;
}
public String getType() {
return mType;
}
public CharSequence getTypeLabel() {
return mTypeLabel;
}
public Drawable getIcon() {
return mIcon;
}
public String toString() {
return mName;
}
}
/**
* Custom adapter used to display account icons and descriptions in the account spinner.
*/
private class AccountAdapter extends ArrayAdapter
public AccountAdapter(Context context, ArrayList
super(context, android.R.layout.simple_spinner_item, accountData);
setDropDownViewResource(R.layout.account_entry);
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
// Inflate a view template
if (convertView == null) {
LayoutInflater layoutInflater = getLayoutInflater();
convertView = layoutInflater.inflate(R.layout.account_entry, parent, false);
}
TextView firstAccountLine = (TextView) convertView.findViewById(R.id.firstAccountLine);
TextView secondAccountLine = (TextView) convertView.findViewById(R.id.secondAccountLine);
ImageView accountIcon = (ImageView) convertView.findViewById(R.id.accountIcon);
// Populate template
AccountData data = getItem(position);
firstAccountLine.setText(data.getName());
secondAccountLine.setText(data.getTypeLabel());
Drawable icon = data.getIcon();
if (icon == null) {
icon = getResources().getDrawable(android.R.drawable.ic_menu_search);
}
accountIcon.setImageDrawable(icon);
return convertView;
}
}
}
通过Intent访问数据
Intent对象能够提供对内容提供器进行间接访问的能力。即使你的应用程序没有访问一个内容提供器的权限,你既可以通过从有权限的应用程序中返回的Intent的对象中获得一个结果,也可以通过激活有权限的应用程序并且让用户在这个应用程序中工作,从而达到允许用户访问提供器中数据的要求。
用临时权限获得访问能力
即使你没有适当访问权限,你也能够通过给有访问权限的应用程序发送一个Intent对象和接受包含的“URI”权限的Intent的返回对象来访问一个内容提供器中的数据。这些针对特定的内容URI的权限会一直维持到接受它们的Activity被退出。有持久权限的应用程序通过在返回结果的Intent对象中设置以下标识来批准临时权限:
1. 读权限:FLAG_GRANT_READ_URI_PERMISSION
2. 写权限:FLAG_GRANT_WRITE_URI_PERMISSION
注意:这些标识不给出访问提供器一般的读写权限,它只包含了对内容URI所指向的数据的授权,只能访问URI自己。
提供器在它的清单文件中使用
例如,即使你没有READ_CONTACTS权限,你也能够获取通信录提供器中的一条通信录数据。你可能想用一个应用程序在他或她的生日时发送一个祝福,因此你希望用户通过应用程序来控制要使用的通信录,而不是申请READ_CONTACTS权限,让它给你访问所有的用户通信录和他们的所有信息。使用以下过程实现这个想法。
1. 使用startActivityForResult()方法发送一个Intent对象,这个对象中包含了ACTION_PICK动作和CONTENT_ITEM_TYPE的通信录MIME类型;
2. 因为这个Intent对象跟People应用程序的“selection”Activity的Intent过滤器匹配,那么这个Activity将会显示到前台;
3. 在这个“selection”的Activity中,用户选择一个要更新的通信录,这时,“selection”Activity会调用setResult(resultcode, intent)方法来设置一个返回给你的应用程序的Intent对象。这个Intent对象包含了被用户选中的通信录的内容资源标识(URI),并且附加了FLAG_GRANT_READ_URI_PERMISSION标识。这个标识批准了你的应用程序读取由内容资源标识(URI)指定的通讯录的数据的URI权限,然后“selection”Activity调用finish()方法返回来控制你的应用程序;
4. 你的应用程序返回到前台,并且系统会调用你的Activity的onActivityResult()方法,这个方法会接收由People应用程序中“selection”Activity创建的Intent对象;
5. 使用来自结果Intent对象的内容资源标识(URI),你能够从通信录提供器中读取通信录的数据,即使你没有在清单文件中申请持久的对这个提供器的访问权限。然后你就能够获得通信录中他或她的生日、电子邮件地址等信息,然后发送祝福。
使用另外的应用程序
对于没有访问权限的应用程序要允许用户修改数据的一个简单方法时激活一个有访问权限的应用程序,并且让用户在这个应用程序中工作。
例如,Calendar应用程序接收ACTION_INSERT动作Intent对象,这个对象允许你激活这个应用程序的插入事件界面,你能够在这个Intent对象中传递附加的数据。因为周期性事件有复杂的语法,把事件插入日历提供器的首选方法是用ACTION_INSERT动作激活Calendar应用,然后让用户在那儿来插入事件。