
这篇文档说明的是,如何获取某个联系人的详细信息,例如邮件地址、电话号码等等。用户在选中某个联系人的时候,就是为了看详细信息的。妳可以显示出某个联系人的全部详情,或者只显示特定类型的详情,例如邮件地址。
本教程中,假设妳已经获得了一行 ContactsContract.Contacts 数据,它对应着用户刚刚选择的一条联系人信息。 获取联系人名字 那篇教程中,说明了,如何获取一组联系人信息。
要想获取某条联系人信息中的全部详情,则,在 ContactsContract.Data 表中搜索所有包含了该联系人信息的 LOOKUP_KEY 的行。这一列可以在 ContactsContract.Data 表中访问到,因为, 内容提供者 (Contacts Provider)会在 ContactsContract.Contacts 表和 ContactsContract.Data 表之间做一次隐式连接(join)。在 获取联系人的名字 教程中详细说明了 LOOKUP_KEY 列。
注意 : 获取某条联系人信息中的全部详情,会降低设备的性能,因为,它需要获取 ContactsContract.Data 表中的所有列。在妳使用这种技巧之前,要考虑一下性能上的损失。
要想从内容提供者读取信息的话,妳的应用必须拥有 READ_CONTACTS 权限。要想请求获取这个权限的话,就在清单文件的 <manifest> 元素 中加入以下子代元素:
<uses-permission android:name="android.permission.READ_CONTACTS" />
取决于某一行中包含的数据类型,它可能仅仅拥有几列数据,也可能拥有狠多列的数据。并且,取决于数据类型的不同,各个数据也可能会位于不同的列中。为了确保妳获取到针对所有可能的数据类型的所有可能的列,妳需要将所有的列名字都加入到妳的查询计划中。如果妳要将查询结果中的 Cursor 绑定到一个 ListView 中,那么,一定要获取 Data._ID 这一列;否则,绑定不起作用。另外,注意要获取 Data.MIMETYPE 这一列,这样,妳才能识别所获取到的每一行的数据类型。例如:
private static final String PROJECTION =
{
Data._ID,
Data.MIMETYPE,
Data.DATA1,
Data.DATA2,
Data.DATA3,
Data.DATA4,
Data.DATA5,
Data.DATA6,
Data.DATA7,
Data.DATA8,
Data.DATA9,
Data.DATA10,
Data.DATA11,
Data.DATA12,
Data.DATA13,
Data.DATA14,
Data.DATA15
};
这个查询计划,会使用 ContactsContract.Data 类中定义的那些列名来获取 ContactsContract.Data 表中某一行的全部列。
妳还可以使用那些在 ContactsContract.Data 类或其继承类中定义的列名常量。但是要注意,从 SYNC1 到 SYNC4 的那几列,是设计来被同步适配器使用的,所以其中的数据是无用的。
定义以下东西:用于表示妳的选择语句的常量;一个用于保存选择语句参数的数组;一个用于保存选择语句参数值的变量。使用 Contacts.LOOKUP_KEY 列来找到对应的联系人信息。例如:
// 定义选择语句
private static final String SELECTION = Data.LOOKUP_KEY + " = ?";
// 定义一个数组,用于保存查询条件
private String[] mSelectionArgs = { "" };
/*
* 定义一个变量,其中包含着选择语句参数值。一旦妳从Contacts 表中获取到了一个Cursor,并且查询得到了预期的那一行数据,就将那一行的LOOKUP_KEY值复制到这个变量中。
*/
private String mLookupKey;
在妳的选择语句中使用"?"作为占位符,可以确保所形成的查询对象是通过绑定方式来生成的,而不是通 过SQL 编译来生成的。这种方式能够防止恶意的SQL 注入攻击。
定义好妳在结果 Cursor 中想要使用的排序条件。要想将所有具有某个特定数据类型的行集中到一起的话,则按照 Data.MIMETYPE 来排序。这个查询参数会将所有的邮件地址信息组织到一起,所有的电话号码信息组织到一起,如此类推。例如:
/*
* 定义一个字符串,表示按照多媒体类型来排序
*/
private static final String SORT_ORDER = Data.MIMETYPE;
注意 : 某些数据类型不提供子类型(subtype),所以妳无法按照子类型来排序。妳需要对返回的 Cursor 进行遍历,确定当前行的数据类型,并且针对那些提供了子类型的行储存好它们的数据。当妳对该游标遍历完毕之后,就可以针对每种数据类型按照其子类型来排序,并且显示出结果。
一定要在一个后台线程中从联系人提供者(以及其它的内容提供者)处获取数据。使用 LoaderManager 类定义的载入器框架及 LoaderManager.LoaderCallbacks 接口来进行后台的数据获取动作。
当妳准备好要获取数据行的时候,就调用 initLoader() 来初始化载入器框架。向该方法中传入一个整数识别符;这个标识符会在日后被传递给那些 LoaderManager.LoaderCallbacks 方法。这个标识符使得妳能够在同一个应用中使用多个载入器,因为妳可以区分它们。
以下代码片断,展示了如何初始化载入器框架:
public class DetailsFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor> {
...
// 定义一个常量,用于标识这个载入器
DETAILS_QUERY_ID = 0;
...
/*
* 当亲代Activity 被实例化,并且这个Fragment 的界面已经准备好的时候,会调用此函数。
* 把最终的初始化步骤写到这里。
*/
@Override
onActivityCreated(Bundle savedInstanceState) {
...
// 初始化载入器框架
getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);
实现 onCreateLoader() 方法,在妳调用了 initLoader() 之后,载入器框架会立即调用该回调方法。妳应当从这个方法中返回一个 CursorLoader 。因为妳是在搜索 ContactsContract.Data 表,所以,应当使用 Data.CONTENT_URI 常量作为内容的统一资 源标识符。例如:
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
// 选择适当的动作
switch (loaderId) {
case DETAILS_QUERY_ID:
// 给选择参数赋值
mSelectionArgs[0] = mLookupKey;
// 开始查询
CursorLoader mLoader =
new CursorLoader(
getActivity(),
Data.CONTENT_URI,
PROJECTION,
SELECTION,
mSelectionArgs,
SORT_ORDER
);
...
}
实现 onLoadFinished() 方法。当联系人提供者返回了查询结果时,载入器框架就会调用 onLoadFinished() 。例如:
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
switch (loader.getId()) {
case DETAILS_QUERY_ID:
/*
* 在这里处理结果集的游标。
*/
}
break;
...
}
}
当载入器框架检测到与结果集 Cursor 对应的数据发生改变时,会调用 onLoaderReset() 方法。在这个时候,应当去除掉对于该 Cursor 的所有引用,具体做法就是将它们设置为空(null)。如果妳不这么做的话,载入器框架就不会销毁旧的 Cursor ,于是妳就引起了内存泄露了。例如:
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch (loader.getId()) {
case DETAILS_QUERY_ID:
/*
* 如果妳还有任何变量引用到了该Cursor,则在这里删除它们。
*/
}
break;
}
获取某条联系人信息中特定类型的数据,例如所有的邮件地址,这个过程与获取全部详情是类似的。与 获取某条联系人信息中的全部详情 中列出的代码相比,妳仅仅需要做出以下改变: :
查询计划
修改查询计划,只获取那些与目标数据类型相关的数据列。另外,还要修改查询计划,使用 ContactsContract.CommonDataKinds 子类中定义的与目标数据类型对应的数据列名字常量。
选择语句
修改选择语句的内容,仅仅搜索那些与妳的目标数据类型相关的 MIMETYPE 值。
排序条件
因为妳仅仅是查询单种的详情类型,所以,不要将返回的 Cursor 按照 Data.MIMETYPE 排序。
在以下小节中将说明这些修改。
使用 ContactsContract.CommonDataKinds 子类中对应着目标数据类型的那些数据列名字常量来定义好妳想要获取的数据列。如果妳准备将查询得到的 Cursor 绑定到一个 ListView ,那么,一定要获取 _ID 列。例如,要想获取邮件地址数据,则,定义以下这样的查询计划:
private static final String[] PROJECTION =
{
Email._ID,
Email.ADDRESS,
Email.TYPE,
Email.LABEL
};
注意,这个查询计划使用的是 ContactsContract.CommonDataKinds.Email 类中定义的列名,而不是 ContactsContract.Data 类中定义的列名。使用与邮件地址相关的列名,使得代码更具有可读性。
妳还可以在该查询计划中使用那些在 ContactsContract.CommonDataKinds 子类中定义的列名。
定义一个搜索表达式,用于获取与特定联系人的 LOOKUP_KEY 对应的数据行。并且定义好对应于妳想要查询的详情的 Data.MIMETYPE 。在 MIMETYPE 常量的开头和结尾处插入一个" ' "(单引号)字符,以将它的值以单引号包含起来;否则,提供者会将该常量解释成一个变量名,而不是解释成一个字符串值。妳不需要为这个值使用一个占位符,因为妳在这里使用的是一个常量,而不是由用户提供的输入值。例如:
/*
* 定义好选择语句。查询一个搜索键和邮件多媒体类型
*/
private static final String SELECTION =
Data.LOOKUP_KEY + " = ?" +
" AND " +
Data.MIMETYPE + " = " +
"'" + Email.CONTENT_ITEM_TYPE + "'";
// 定义一个数组,用于保存搜索条件
private String[] mSelectionArgs = { "" };
定义一个针对所返回 Cursor 的排序条件。因为妳是在查询某种特定的数据类型,所以,省略掉针对 MIMETYPE 的排序条件。如果妳搜索的这种详情数据中包含有子类型的话,那么妳可以按照子类型来排序。例如,对于邮件地址数据,妳可以按照 Email.TYPE 来排序:
private static final String SORT_ORDER = Email.TYPE + " ASC ";
HxLauncher: Launch Android applications by voice commands