【翻译】通讯录数据的存取(一)——获取通讯录列表

通讯录数据的存取

有选择性的翻译自:https://developer.android.com/training/contacts-provider/index.html
Contacts Provider是用户通信信息仓库,包含通讯录应用程序和社交网络应用程序的数据。我们可以通过直接调用ContactsResolver的方法或直接发送调用通讯录应用程序的intent来获取Contacts Provider提供的信息。

目录

获取通讯录列表

根据以下三种类型匹配获取列表:
- 匹配通信人姓名
- 匹配某类型数据,如电话号码
- 匹配任意数据

在使用之前需要申请如下权限:

<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
        );
    }

至此,匹配任意类型数据的搜索实现已结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值