通讯录数据的存取
有选择性的翻译自:https://developer.android.com/training/contacts-provider/index.html
Contacts Provider是用户通信信息仓库,包含通讯录应用程序和社交网络应用程序的数据。我们可以通过直接调用ContactsResolver的方法或直接发送调用通讯录应用程序的intent来获取Contacts Provider提供的信息。
目录
- 通讯录数据的存取
-
- 目录
- 获取通讯录列表
- 匹配通信人姓名
- 定义ListView的布局
- 定义Fragment展示通信人列表
- 定义全局变量
- 初始化Fragment
- 给ListView设置CursorAdapter
- 设置点击通讯录列表的监听器
- 定义一个规划projection
- 定义Cursor列的索引值常量
- 定义选择标准
- 定义onItemClick方法
- 初始化CursorLoader
- 实现onCreateLoader
- 实现onLoadFinished和onLoadReset
- 匹配某类型数据
- 选择数据类型和表
- 定义一个规划projection
- 定义选择标准
- 实现onCreateLoader
- 匹配任意数据
- 去除选择标准
- 实现onCreateLoader
- 匹配通信人姓名
-
获取通讯录列表
根据以下三种类型匹配获取列表:
- 匹配通信人姓名
- 匹配某类型数据,如电话号码
- 匹配任意数据
在使用之前需要申请如下权限:
<uses-permission android:name="android.permission.READ_CONTACTS" />
匹配通信人姓名
实现方案是将字符串与通讯录提供者(Contact Provider)的ContactsContract.Contacts表的一个或多个通信人的姓名进行匹配。
这里以ListView来展示结果为例介绍整个过程。
定义ListView的布局
创建整体布局文件: res/layout/contacts_list_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
该文件使用内置的Android ListView组件android:id/list.
然后定义每一项的布局文件 contacts_list_item.xml :
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"/>
该文件使用内置的Android TextView组件:android:text1.
接下来定义使用上述UI展示通信人列表的代码。
定义Fragment展示通信人列表
为了更好的帮助开发者查询Contacts Provider,Android框架提供了名为ContactsContract的协议类,它定义了用来存取provider的常量和方法。使用该类,你无需定义URI,表名或列名等常量。
我们使用CursorLoader 从provider获取数据, 因此必须实现接口:LoaderManager.LoaderCallbacks. 另外,实现AdapterView.OnItemClickListener 以获取用户在搜索列表中选择的联系人信息。相应代码如下:
...
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.widget.AdapterView;
...
public class ContactsFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor>,
AdapterView.OnItemClickListener {
定义全局变量
...
/*
* Defines an array that contains column names to move from
* the Cursor to the ListView.
*/
@SuppressLint("InlinedApi")
private final static String[] FROM_COLUMNS = {
Build.VERSION.SDK_INT
>= Build.VERSION_CODES.HONEYCOMB ?
Contacts.DISPLAY_NAME_PRIMARY :
Contacts.DISPLAY_NAME
};
/*
* Defines an array that contains resource ids for the layout views
* that get the Cursor column contents. The id is pre-defined in
* the Android framework, so it is prefaced with "android.R.id"
*/
private final static int[] TO_IDS = {
android.R.id.text1
};
// Define global mutable variables
// Define a ListView object
ListView mContactsList;
// Define variables for the contact the user selects
// The contact‘s _ID value
long mContactId;
// The contact‘s LOOKUP_KEY
String mContactKey;
// A content URI for the selected contact
Uri mContactUri;
// An adapter that binds the result Cursor to the ListView
private SimpleCursorAdapter mCursorAdapter;
...
初始化Fragment
添加空的结构体,然后在onCreateView()中加载Fragment对象的UI。如下:
// Empty public constructor, required by the system
public ContactsFragment() {}
// A UI Fragment must inflate its View
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the fragment layout
return inflater.inflate(R.layout.contact_list_fragment,
container, false);
}
给ListView设置CursorAdapter
使用SimpleCursorAdapter 将搜索结果与ListView关联。如下:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
// Gets the ListView from the View list of the parent activity
mContactsList =
(ListView) getActivity().findViewById(R.layout.contact_list_view);
// Gets a CursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
getActivity(),
R.layout.contact_list_item,
null,
FROM_COLUMNS, TO_IDS,
0);
// Sets the adapter for the ListView
mContactsList.setAdapter(mCursorAdapter);
}
设置点击通讯录列表的监听器
当展示了搜索列表后,我们会允许用户点击某一联系人以做进一步的操作。如,当用户点击联系人后,在地图上显示联系人的地址。为做到这一点,首先定义一个实现了AdapterView.OnItemClickListener 接口的Fragment,就像在定义Fragment展示通信人列表*这一节中所讲的。
然后在onActivityCreated()中调用setOnItemClickListener()将监听器与ListView做关联。如:
public void onActivityCreated(Bundle savedInstanceState) {
...
// Set the item click listener to be the current fragment.
mContactsList.setOnItemClickListener(this);
...
}
定义一个规划(projection)
定义一个常量,包含所有你打算返回的列名。如:
...
@SuppressLint("InlinedApi")
private static final String[] PROJECTION =
{
Contacts._ID,
Contacts.LOOKUP_KEY,
Build.VERSION.SDK_INT
>= Build.VERSION_CODES.HONEYCOMB ?
Contacts.DISPLAY_NAME_PRIMARY :
Contacts.DISPLAY_NAME
};
其中,列名Contacts._ID会在SimpleCursorAdapter 绑定过程中用到。Contacts._ID和 LOOKUP_KEY 一起用来构造用户选取的联系人的内容URI。 而ListView在展示联系人列表是会用到联系人姓名。该列在Android 3.0(API 11)及以后,列名叫Contacts.DISPLAY_NAME_PRIMARY,之前叫Contacts.DISPLAY_NAME。
定义Cursor列的索引值常量
获取某cursor某列的数据时,需要该列在Cursor中的列索引值。因为该索引值使用的是上一节定义的规划中的列的顺序,因此可以把列的索引值定义为常量。
// The column index for the _ID column
private static final int CONTACT_ID_INDEX = 0;
// The column index for the LOOKUP_KEY column
private static final int LOOKUP_KEY_INDEX = 1;
定义选择标准
使用文字表达式指出要搜索的数据列,尽管该表达式可以包含参数值,推荐使用占位符“?”来代替参数值。使用“?”可以保证搜索通过连接而不是SQL编译来生成,避免恶意SQL注入的可能性。
使用变量来表示待搜索的内容,即SQL中替换“?”的内容。代码如下:
// Defines the text expression
@SuppressLint("InlinedApi")
private static final String SELECTION =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
Contacts.DISPLAY_NAME + " LIKE ?";
// Defines a variable for the search string
private String mSearchString;
// Defines the array to hold values that replace the ?
private String[] mSelectionArgs = { mSearchString };
定义onItemClick()方法
实现上一节的监听器,如下:
@Override
public void onItemClick(
AdapterView<?> parent, View item, int position, long rowID) {
// Get the Cursor
Cursor cursor = parent.getAdapter().getCursor();
// Move to the selected contact
cursor.moveToPosition(position);
// Get the _ID value
mContactId = getLong(CONTACT_ID_INDEX);
// Get the selected LOOKUP KEY
mContactKey = getString(CONTACT_KEY_INDEX);
// Create the contact‘s content Uri
mContactUri = Contacts.getLookupUri(mContactId, mContactKey);
/*
* You can use mContactUri as the content URI for retrieving
* the details for a contact.
*/
}
初始化CursorLoader
既然使用CursorLoader 来获取数据,必须初始化控制异步获取数据的后台线程和其他变量。在onActivityCreated()方法中做初始化,该函数会在Fragment的UI出现之前被调用。如下:
public class ContactsFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor> {
...
// Called just before the Fragment displays its UI
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// Always call the super method first
super.onActivityCreated(savedInstanceState);
...
// Initializes the loader
getLoaderManager().initLoader(0, null, this);
实现onCreateLoader()
该方法会在我们调用initLoader()后立即被系统调用。
在onCreateLoader()中,设置搜索串模式。字符串中插入:“%”表示0或更多个字符串,“_”代表一个字符。如,模式”%Jefferson%”会匹配“Thomas Jefferson”和”Jefferson Davis”. 至于内容URI,使用Contacts.CONTENT_URI, 它指向整个表。代码如下:
...
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
/*
* Makes search string into pattern and
* stores it in the selection array
*/
mSelectionArgs[0] = "%" + mSearchString + "%";
// Starts the query
return new CursorLoader(
getActivity(),
Contacts.CONTENT_URI,
PROJECTION,
SELECTION,
mSelectionArgs,
null
);
}
实现onLoadFinished()和onLoadReset()
Loader框架在Contacts Provider返回搜索结果时调用onLoadFinished().在该方法中,将结果Cursor放入SimpleCursorAdapter中,这样会自动使用搜索结果更新ListView:
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Put the result Cursor in the adapter for the ListView
mCursorAdapter.swapCursor(cursor);
}
onLoaderReset()方法会在loader框架发现Cursor结果包含旧数据时被调用。在该方法中,需要清除SimpleCursorAdapter对已有Cursor的引用,否则loader框架不会回收该Cursor,会引起内地泄漏。
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Delete the reference to the existing Cursor
mCursorAdapter.swapCursor(null);
}
至此,已经讲完了搜索通信人姓名并将搜索结果在ListView中展现的所有关键点。
匹配某类型数据
实现这一类型的数据获取,首先需要像上一节一样实现以下代码:
- 申请度通讯录的权限
- 定义ListView的布局
- 定义Fragment展示通信人列表
- 定义全局变量
- 初始化Fragment
- 给ListView设置CursorAdapter
- 设置点击通讯录列表的监听器
- 定义Cursor列的索引值常量
- 定义onItemClick方法
- 初始化CursorLoader
- 实现onLoadFinished和onLoadReset
除上述步骤外,需要额外的代码来获取匹配某些类型数据的通讯录信息。接下来是与上一节实现方面有不同的步骤。
选择数据类型和表
搜索某类型的具体数据,需要知道该类型对应的MIME类型。所有的数据类型都有一个唯一的MIME类型值,该值是ContactsContract.CommonDataKinds 中与数据类型匹配的子类中定义的常量CONTENT_ITEM_TYPE。如,Email数据对应的子类是ContactsContract.CommonDataKinds.Email, 而它的MIME类型是Email.CONTENT_ITEM_TYPE常量.
选择完数据类型后,选择该使用哪个表。使用ContactsContract.Data表,该表中定义或继承了所有需要查询的列名或如何排序等常量。
定义一个规划(projection)
可以选择ContactsContract.Data或它继承的类中的一个或多个列。Contacts Provider在返回结果前会对ContactsContract.Data表和其他表做一个隐式的连接。如下:
@SuppressLint("InlinedApi")
private static final String[] PROJECTION =
{
/*
* The detail data row ID. To make a ListView work,
* this column is required.
*/
Data._ID,
// The primary display name
Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
Data.DISPLAY_NAME_PRIMARY :
Data.DISPLAY_NAME,
// The contact‘s _ID, to construct a content URI
Data.CONTACT_ID,
// The contact‘s LOOKUP_KEY, to construct a content URI
Data.LOOKUP_KEY
};
定义选择标准
使用如下数据构造一个搜索语句:
- 包含待搜索字符串信息的列的名字 根据搜索类型的不同而变化,因此需要先找出与搜索类型对应的ContactsContract.CommonDataKinds 的子类,然后选择该子类的列名。如,使用列名Email.ADDRESS来搜索email地址。
- 搜索字符串 在搜索语句中使用“?”来代替。
- 包含MIME类型的列名 该值是固定值:Data.MIMETYPE.
- 搜索类型对应的MIME值 ContactsContract.CommonDataKinds 中与数据类型匹配的子类中定义的常量CONTENT_ITEM_TYPE。如,Email数据对应的子类是ContactsContract.CommonDataKinds.Email, 而它的MIME类型是Email.CONTENT_ITEM_TYPE常量. 需要使用单引号将该常量括起来,否则provider会把它当作变量名而不是常量。
代码如下:
/*
* Constructs search criteria from the search string
* and email MIME type
*/
private static final String SELECTION =
/*
* Searches for an email address
* that matches the search string
*/
Email.ADDRESS + " LIKE ? " + "AND " +
/*
* Searches for a MIME type that matches
* the value of the constant
* Email.CONTENT_ITEM_TYPE. Note the
* single quotes surrounding Email.CONTENT_ITEM_TYPE.
*/
Data.MIMETYPE + " = ‘" + Email.CONTENT_ITEM_TYPE + "‘";
接下来定义包含搜索参数的变量:
String mSearchString;
String[] mSelectionArgs = { "" };
实现onCreateLoader
与上一节不同的是,这里的内容URI,使用Data.CONTENT_URI.如下:
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
// OPTIONAL: Makes search string into pattern
mSearchString = "%" + mSearchString + "%";
// Puts the search string into the selection criteria
mSelectionArgs[0] = mSearchString;
// Starts the query
return new CursorLoader(
getActivity(),
Data.CONTENT_URI,
PROJECTION,
SELECTION,
mSelectionArgs,
null
);
}
至此,匹配某类型数据的搜索实现已结束。
匹配任意数据
返回匹配某数据的联系人列表,而不管该数据是名字,Email地址,通信地址或电话号码等。
实现这一类型的数据获取,首先需要像上一节一样实现以下代码:
- 申请度通讯录的权限
- 定义ListView的布局
- 定义Fragment展示通信人列表
- 定义全局变量
- 初始化Fragment
- 给ListView设置CursorAdapter
- 设置点击通讯录列表的监听器
- 定义一个规划(projection)
- 定义Cursor列的索引值常量
- 定义onItemClick方法
- 初始化CursorLoader
- 实现onLoadFinished和onLoadReset
接下来是与上一节实现方面有不同的步骤。
去除选择标准
不用定义SELECTION常量或mSelectionArgs变量。
实现onCreateLoader
不再需要将字符串转换为模式,因为Contacts Provider会自动为你做。使用Contacts.CONTENT_FILTER_URI作为基础URI, 通过调用Uri.withAppendedPath()将待搜索字符串追加到上述URI. 使用处理后的URI来搜索任意类型的数据。代码如下:
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
/*
* Appends the search string to the base URI. Always
* encode search strings to ensure they‘re in proper
* format.
*/
Uri contentUri = Uri.withAppendedPath(
Contacts.CONTENT_FILTER_URI,
Uri.encode(mSearchString));
// Starts the query
return new CursorLoader(
getActivity(),
contentUri,
PROJECTION,
null,
null,
null
);
}
至此,匹配任意类型数据的搜索实现已结束。