注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。
原文链接: http://developer.android.com/training/contacts-provider/retrieve-details.html
这节课将会展示如何获取一个联系人的详细数据,比如电子邮件地址,电话号码,等等。当用户获得一个联系人后,他会想要查看他的详细信息。你可以展示给他们所有的信息,或者只展示某一特定类型的信息,比如电子邮件地址。
这节课中,我们假设你已经有了用户选择的一行 ContactsContract.Contacts 联系人数据。在上一节课中(博客链接: http://www.cnblogs.com/jdneo/p/3674830.html )我们已经讲解了如何获取联系人列表的知识。
一). 获取一个联系人的详细信息
要获得一个联系人的所有信息,需要搜索 ContactsContract.Data 表的每一行,看是否包含有联系人的 LOOKUP_KEY 。这一列可在 ContactsContract.Data 中获得,因为Contacts Provider在 ContactsContract.Contacts 表和 ContactsContract.Data 表之间执行了一个隐式的连接。列 LOOKUP_KEY 的详细信息在上一节课中 (博客链接: http://www.cnblogs.com/jdneo/p/3674830.html ) 已经讲授过了。
Note:
获取某一个联系人的所有信息会降低设备的性能,因为它需要获取 ContactsContract.Data 表中所有列的数据。在你使用这一方法前要考虑到它对性能的影响。
请求权限
要从Contacts Provider中读取数据,你的应用必须具有 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 列来寻找联系人,例如:
// Defines the selection clause private static final String SELECTION = Data.LOOKUP_KEY + " = ?" ; // Defines the array to hold the search criteria private String[] mSelectionArgs = { "" }; /* * Defines a variable to contain the selection value. Once you * have the Cursor from the Contacts table, and you've selected * the desired row, move the row's LOOKUP_KEY value into this * variable. */ private String mLookupKey;
在你的选择语句中使用“?”作为占位符,能保证搜索语句是由绑定生成的而不是编译生成的。这可以防止恶意的SQL注入攻击。
定义排列顺序
为结果 Cursor 定义你期望的排列顺序。要让某一类型数据的所有行根据 Data.MIMETYPE 来排列。这一语句会让所有email的行放在一起,所有电话号码的行放在一起,等等。例如:
/* * Defines a string that specifies a sort order of MIME type */ private static final String SORT_ORDER = Data.MIMETYPE;
Note:
一些数据类型不使用子类,所以你不能用子类型来排序。相反的,你需要再返回的 Cursor 中进行迭代,确定当前行的数据类型,然后将使用子类型的行的数据保存起来。当你完成读cursor后,之后你就可以通过子类型排序并显示结果。
初始化加载器
通常,我们在后台线程从Contacts Provider(以及其它所有的内容提供器)获取数据。使用由 LoaderManager 类,以及 LoaderManager.LoaderCallbacks 接口所定义的加载器框架来执行后台的获取操作。
当你准备好获取行后,通过调用 initLoader() 初始化加载器框架。将一个整形标识传递给该方法;这一标志会传递给 LoaderManager.LoaderCallbacks 方法。它可以帮助你区分这些加载器,从而在一个应用中使用多个加载器。
下面的代码样例展示了如何初始化这个加载器框架:
public class DetailsFragment extends Fragment implements LoaderManager.LoaderCallbacks <Cursor> { ... // Defines a constant that identifies the loader DETAILS_QUERY_ID = 0 ; ... /* * Invoked when the parent Activity is instantiated * and the Fragment's UI is ready. Put final initialization * steps here. */ @Override onActivityCreated(Bundle savedInstanceState) { ... // Initializes the loader framework getLoaderManager().initLoader(DETAILS_QUERY_ID, null , this );
实现 onCreateLoader()
实现 onCreateLoader() 方法,它会在你调用了 initLoader() 后被加载器框架调用。该方法返回一个 CursorLoader 。因为你在搜索 ContactsContract.Data 表,所以使用 Data.CONTENT_URI 常量作为内容URI。例如:
@Override public Loader<Cursor> onCreateLoader( int loaderId, Bundle args) { // Choose the proper action switch (loaderId) { case DETAILS_QUERY_ID: // Assigns the selection parameter mSelectionArgs[0] = mLookupKey; // Starts the query CursorLoader mLoader = new CursorLoader( getActivity(), Data.CONTENT_URI, PROJECTION, SELECTION, mSelectionArgs, SORT_ORDER ); ... }
实现onLoadFinished()和onLoaderReset()
实现 onLoadFinished() 方法。当Contacts Provider将查询结果返回后,加载器框架会调用 onLoadFinished() 。例如:
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { switch (loader.getId()) { case DETAILS_QUERY_ID: /* * Process the resulting Cursor here. */ } break ; ... } }
当加载器检测到结果中的数据发生了变化,那么 onLoaderReset() 方法会被调用,此时,将所有存在的 Cursor 引用设置成null来移除它们。如果你不这么做,加载器框架不会销毁老的 Cursor ,这样会导致内存泄露,例如:
@Override public void onLoaderReset(Loader<Cursor> loader) { switch (loader.getId()) { case DETAILS_QUERY_ID: /* * If you have current references to the Cursor, * remove them here. */ } break ; }
二). 获取一个联系人的特定数据
要获取一个联系人的特定数据,比如email,和检索所有的详细数据类似,你需要做的改变仅仅是实现课程 Retrieve All Details for a Contact (博客链接: http://www.cnblogs.com/jdneo/p/3674830.html ) 中列出的代码:
投影
修改你的投影,使得列对应于特定的要搜索的数据类型。同时修改投影,使用在 ContactsContract.CommonDataKinds 子类中对应于数据类型的列常量名。
选择
修改选择语句,搜索对应于你的数据类型的 MIMETYPE 值。
排列顺序
因为你值使用单一的详细数据类型,所以不需要根据 Data.MIMETYPE 将返回的 Cursor 分类。
这些修改在下列章节中描述。
定义一个投影
定义你要获取的列,使用 ContactsContract.CommonDataKinds 子类中对应于数据类型的列常量名。如果你想要将 Cursor 绑定到一个 ListView 上,要确保获取“ _ID ”列。例如,要获取email数据,定义下列投影:
private static final String[] PROJECTION = { Email._ID, Email.ADDRESS, Email.TYPE, Email.LABEL };
注意这一投影使用 ContactsContract.CommonDataKinds.Email 类中定义的列名,而不是在类 ContactsContract.Data 中定义的列名。使用email的特定列名可以增加代码的可读性。
在投影中,你也可以使用在 ContactsContract.CommonDataKinds 子类中定义的其它列。
定义选择标准
定义一个搜索表达式,它获取你想要的特定联系人的 LOOKUP_KEY 和 Data.MIMETYPE 行数据。对于 MIMETYPE 的值对应的常量,用单引号(')将它括起来;否则,提供器会把常量认为是一个变量名而不是一个字符串值。你不需要为它使用一个占位符,因为你使用的是一个常量而不是一个用户提供的值。例如:
/* * Defines the selection clause. Search for a lookup key * and the Email MIME type */ private static final String SELECTION = Data.LOOKUP_KEY + " = ?" + " AND " + Data.MIMETYPE + " = " + "'" + Email.CONTENT_ITEM_TYPE + "'" ; // Defines the array to hold the search criteria private String[] mSelectionArgs = { "" };
定义排列顺序
为返回的 Cursor 定义一个排列顺序。因为你获得的是一个特定的数据类型,所以略过对 MIMETYPE 排序。另外,如果你正在搜索的详细数据包含有一个子类型,可以对他排序。例如,对于Email数据你可以根据 Email.TYPE 来排序:
private static final String SORT_ORDER = Email.TYPE + " ASC ";