Contacts數(shù)據(jù)的訪問

正如ContentProvider提供了數(shù)據(jù)跨進(jìn)程訪問的標(biāo)準(zhǔn)接口,Contacts Provider提供了通訊錄app和社交app的用戶信息和數(shù)據(jù)的標(biāo)準(zhǔn)分訪問接口,你可以在你的app中通過調(diào)用ContentResolver的相關(guān)方法來訪問這些信息.

注: 本文主要關(guān)注如何使用intent(實際上好像不怎么focus,只占了1/4)來獲取,展示,和修改聯(lián)系人信息,比較簡單介紹整個處理的流程,當(dāng)然每一項都可以延伸下去以滿足更復(fù)雜的需求,具體可參考下列文章:

1. 檢索聯(lián)系人列表

要檢索出滿足一定條件的聯(lián)系人,要使用下面的機(jī)制:

  • 匹配聯(lián)系人名字.
  • 匹配某個具體的數(shù)據(jù)類型,如手機(jī)號.
  • 匹配任意的數(shù)據(jù)類型.

1.1 請求Provider讀取權(quán)限

要對Contacts Provider進(jìn)行任何形式的查詢查找,都需要有READ_CONTACTS權(quán)限:

<uses-permission android:name="android.permission.READ_CONTACTS" />

1.2 顯示名字匹配結(jié)果

下面進(jìn)行的匹配操作是針對于ContactsContract.Contacts.

1.2.1 定義ListView和item的layout文件

目錄為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"/>

// 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"/>

1.2.2 定義一個Fragment來展示數(shù)據(jù)

...
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.widget.AdapterView;

// 方便常量獲取
import android.provider.ContactsContract.Contacts;
...
public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {

1.2.3 聲明全局變量

    ...
    /*
     * 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;
    ...

注: Contacts.DISPLAY_NAME_PRIMARY這個是在Android 3.0(API 11)引入的,如果你的項目的minSdkVersion設(shè)置的小于11,則Android Lint會產(chǎn)生警告,為了關(guān)掉對此的警告,就在FROM_COLUMNS添加了@SuppressLint("InlinedApi")注解.

1.2.4 初始化Fragment

    // 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
        View layout= inflater.inflate(R.layout.contacts_list_view,
                container, false);
        // Gets the ListView
        mContactsList= (ListView) layout.findViewById(R.id.list);
        return layout;
    }

1.2.5 設(shè)置CursorAdapter

    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        // Gets a CursorAdapter
        mCursorAdapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.contacts_list_item,
                null,
                FROM_COLUMNS, TO_IDS,
                0);
        // Sets the adapter for the ListView
        mContactsList.setAdapter(mCursorAdapter);
    }

1.2.6 設(shè)置item點擊事件

    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Set the item click listener to be the current fragment.
        mContactsList.setOnItemClickListener(this);
        ...
    }

1.2.7 定義一個projection

也就是想要獲取的數(shù)據(jù)列.

...
@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

        };

1.2.8 定義Cursor列索引常數(shù)

要從Cursor中獲取數(shù)據(jù),需要傳入列的索引值,該索引值是根據(jù)projection中的順序從0開始增加的,這里我們先定義:

// 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;

1.2.9 設(shè)置篩選條件

    // 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,default as blank
    private String mSearchString="";
    // Defines the array to hold values that replace the ?
    private String[] mSelectionArgs = { mSearchString };

1.2.10 重寫onItemClick()方法.

在1.2.6中調(diào)用了mContactsList.setOnItemClickListener(this)方法將事件設(shè)置給類this,也就是我們這個Fragment,就要去實現(xiàn)這個Listener的抽象方法:

    @Override
    public void onItemClick(
            AdapterView<?> parent, View item, int position, long rowID) {
        // Get the Cursor
        Cursor cursor = ((SimpleCursorAdapter) parent.getAdapter()).getCursor();
        // Move to the selected contact
        cursor.moveToPosition(position);
        // Get the _ID value
        mContactId = cursor.getLong(CONTACT_ID_INDEX);
        // Get the selected LOOKUP KEY
        mContactKey = cursor.getString(LOOKUP_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.
         */
    }

1.2.11 初始化loader

    // Initializes the loader
    getLoaderManager().initLoader(0, null, this);

1.2.12 實現(xiàn)LoaderManager.LoaderCallbacks的抽象方法.

    @Override
    public Loader<Cursor> onCreateLoader(int id, 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
        );
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        // Put the result Cursor in the adapter for the ListView
        mCursorAdapter.swapCursor(cursor);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Delete the reference to the existing Cursor
        mCursorAdapter.swapCursor(null);
    }

注: 在SQL中,"%"是通配符,只能與LIKE運(yùn)算符一起使用,表示替代一個或多個字符.

在CursorLoader的構(gòu)造參數(shù)中,URI還可以調(diào)用Uri.withAppendedPath()拼接Contacts.CONTENT_FILTER_URI和關(guān)鍵字來實現(xiàn)名字匹配,如下:

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

2. 獲取聯(lián)系人詳情

在獲取了聯(lián)系人列表之后,你可能還需要查看詳情信息,比如手機(jī)號,地址等等.

2.1 獲取聯(lián)系人的所有詳情信息

聯(lián)系人詳情信息存放在ContactsContract.Data這個表中,可以用聯(lián)系人的LOOKUP_KEY來獲取詳情.因為LOOKUP_KEYContactsContract.Contacts表中的一列,而ContactsContract.Contacts又與ContactsContract.Data是關(guān)聯(lián)關(guān)系.

注意: 獲取聯(lián)系人的所有詳情信息會影響設(shè)備的性能,因為這需要去ContactsContract.Data獲取所有列,你在是否獲取聯(lián)系人全部詳情的時候要注意考慮這個性能問題.

2.1.1 請求權(quán)限

<uses-permission android:name="android.permission.READ_CONTACTS" />

2.1.2 設(shè)置一個projection

projection的設(shè)置視你要獲取的信息而定,這里我們要獲取所有的列,所以把所有的列都列出來.如果你要將Cursor綁定到ListView中則你要記得獲取Data._ID這個列,同時也要獲取Data.MIMETYPE這列以便識別改行數(shù)據(jù)類型.如下示例:

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

2.1.3 設(shè)置檢索條件

    // Defines the selection clause
    private static final String SELECTION = ContactsContract.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;

2.1.4 設(shè)置排序順序

    /*
     * Defines a string that specifies a sort order of MIME type
     */
    private static final String SORT_ORDER = Data.MIMETYPE;

2.1.5 Loader相關(guān)方法設(shè)置

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    ...
        // 從前面的列表中拿到lookup key
        mLookupKey = getIntent().getStringExtra("lookup_key");

        // Initializes the loader framework
        getSupportLoaderManager().initLoader(0, null, this);
    }
    ...
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        // Assigns the selection parameter
        mSelectionArgs[0] = mLookupKey;

        // Starts the query
        CursorLoader mLoader =
                new CursorLoader(
                        this,
                        ContactsContract.Data.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        mSelectionArgs,
                        SORT_ORDER
                );
        return mLoader;
    }
        @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
                    /*
                     * Process the resulting Cursor here.
                     */
    }
        @Override
    public void onLoaderReset(Loader<Cursor> loader) {
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
    }

上述設(shè)置之后即可拿到聯(lián)系人詳細(xì)信息

2.2 獲取聯(lián)系人詳情的某些信息

要獲取聯(lián)系人的某些信息,只需要按需調(diào)整一下參數(shù)即可.例如要獲取郵箱信息,可以使用CommonDataKinds這個類,這里面定義ContactsContract.Data表中使用的數(shù)據(jù)的類型,比如下面要用到的Email類.如下示例,代碼中你只要做下面一些改變:

  • Projection
  • Selection
  • Sort order
    private static final String[] PROJECTION =
            {
                Email._ID,
                Email.ADDRESS,
                Email.TYPE,
                Email.LABEL
            };
    /*
     * 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 = { "" };
    
    // Define a sort order
    private static final String SORT_ORDER = Email.TYPE + " ASC ";

3. 使用Intent修改聯(lián)系人信息

下面介紹如何使用Intent來插入或修改聯(lián)系人信息,而不是直接操作Contacts Provider,同時這種方式是你要優(yōu)先考慮的,有三點原因:

  • 你無需寫聯(lián)系人處理相關(guān)的UI和其他處理代碼,省時省力.
  • 避免了修改時違背Contact Provider的規(guī)則而導(dǎo)致的錯誤.
  • 減少了權(quán)限的請求.

3.1 使用Intent插入聯(lián)系人信息

很多時候你需要允許用戶在獲取到新數(shù)據(jù)的時候插入新的聯(lián)系人信息.比如,一個酒店評價app就可以允許用戶在瀏覽該酒店信息時將其添加到通訊錄中.
下面將介紹調(diào)用聯(lián)系人app插入新將數(shù)據(jù)到raw contact(ContactsContract.RawContacts)這個表中,具體看Retrieval and modification with intents.

新建Intent時使用的是action是Intents.Insert.ACTION,MIME類型是RawContacts.CONTENT_TYPE,如下示例:

...
// Creates a new Intent to insert a contact
Intent intent = new Intent(Intents.Insert.ACTION);
// Sets the MIME type to match the Contacts Provider
intent.setType(ContactsContract.RawContacts.CONTENT_TYPE);

如果你已經(jīng)有了一些信息也可直接放在Intent對象中,而存放的時候要注意key要從Intents.Insert獲取,如下示例:


    ...
    private EditText mEmailAddress;
    private EditText mPhoneNumber;
    ...
        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        /* Assumes EditText fields in your UI contain an email address
         * and a phone number.
         *
         */
        mEmailAddress = (EditText) findViewById(R.id.email);
        mPhoneNumber = (EditText) findViewById(R.id.phone);
    }
    public void submit(View v) {
        // Creates a new Intent to insert a contact
        Intent intent = new Intent(Intents.Insert.ACTION);
        // Sets the MIME type to match the Contacts Provider
        intent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
        /*
         * Inserts new data into the Intent. This data is passed to the
         * contacts app's Insert screen
         */
        // Inserts an email address
        intent.putExtra(Intents.Insert.EMAIL, mEmailAddress.getText())
                /*
                 * In this example, sets the email type to be a work email.
                 * You can set other email types as necessary.
                 */
                .putExtra(Intents.Insert.EMAIL_TYPE, CommonDataKinds.Email.TYPE_WORK)
                // Inserts a phone number
                .putExtra(Intents.Insert.PHONE, mPhoneNumber.getText())
                /*
                 * In this example, sets the phone type to be a work phone.
                 * You can set other phone types as necessary.
                 */
                .putExtra(Intents.Insert.PHONE_TYPE, CommonDataKinds.Phone.TYPE_WORK);

        /* Sends the Intent
         */
        startActivity(intent);
    }

啟動之后會打開聯(lián)系人的新建聯(lián)系人頁面,就可以進(jìn)行相應(yīng)操作.

3.2 使用Intent編輯聯(lián)系人信息

編輯聯(lián)系人信息的流程和插入類型,但是要添加聯(lián)系人的Contacts.CONTENT_LOOKUP_URI和MIME類型Contacts.CONTENT_ITEM_TYPE到intent中,當(dāng)然你還可以將已有數(shù)據(jù)傳到intent中,然后啟動intent即可.

注意: 有一些列通過是無法通過intent修改的,具體在ContactsContract.Contacts中的"Update"標(biāo)題下可以查看.

如下示例:

    // The Cursor that contains the Contact row
    public Cursor mCursor;
    // The index of the lookup key column in the cursor
    public int mLookupKeyIndex;
    // The index of the contact's _ID value
    public int mIdIndex;
    // The lookup key from the Cursor
    public String mCurrentLookupKey;
    // The _ID value from the Cursor
    public long mCurrentId;
    // A content URI pointing to the contact
    Uri mSelectedContactUri;
    ...
    /*
     * Once the user has selected a contact to edit,
     * this gets the contact's lookup key and _ID values from the
     * cursor and creates the necessary URI.
     */
    // Gets the lookup key column index
    mLookupKeyIndex = mCursor.getColumnIndex(Contacts.LOOKUP_KEY);
    // Gets the lookup key value
    mCurrentLookupKey = mCursor.getString(mLookupKeyIndex);
    // Gets the _ID column index
    mIdIndex = mCursor.getColumnIndex(Contacts._ID);
    mCurrentId = mCursor.getLong(mIdIndex);
    mSelectedContactUri =
            Contacts.getLookupUri(mCurrentId, mCurrentLookupKey);
    ...
    // Creates a new Intent to edit a contact
    Intent editIntent = new Intent(Intent.ACTION_EDIT);
    /*
     * Sets the contact URI to edit, and the data type that the
     * Intent must match
     */
    editIntent.setDataAndType(mSelectedContactUri,Contacts.CONTENT_ITEM_TYPE);

注意:在Android 4.0(API 14)及以上,使用帶edit action的intent啟動聯(lián)系人app之后,當(dāng)用戶點完成之后無法正確返回到自己的app中,解決方案是添加一行代碼,如下:

    // Sets the special extended data for navigation
    editIntent.putExtra("finishActivityOnSaveCompleted", true);

3.3 使用Intent讓用戶選擇插入還是編輯

除了直接插入和編輯之外,你還可以讓用戶選擇要哪種操作,使用ACTION_INSERT_OR_EDIT為action,如下示例:

    // Creates a new Intent to insert or edit a contact
    Intent intentInsertEdit = new Intent(Intent.ACTION_INSERT_OR_EDIT);
    // Sets the MIME type
    intentInsertEdit.setType(Contacts.CONTENT_ITEM_TYPE);
    // Add code here to insert extended data, if desired
    ...
    // Sends the Intent with an request ID
    startActivity(intentInsertEdit);

4. 聯(lián)系人Badge的展示

本節(jié)將展示如何添加QuickContactBadge到UI并且綁定數(shù)據(jù). QuickContactBadge是一個用于展示縮略圖的控件,雖然你可以給它使用任意的Bitmap,但是通常你還是應(yīng)該給它設(shè)置縮略圖.
QuickContactBadge表現(xiàn)的像一個控制器,當(dāng)用戶點擊的時候,它會展開成一個dialog,這個dialog包含以下信息:

  • 一個大圖. 這個大圖與聯(lián)系人綁定在一起,如果該聯(lián)系人無圖片,則顯示默認(rèn)圖片.
  • App icons. 每一條數(shù)據(jù)的app icon都可以被內(nèi)置的app所處理. 比如, 如果聯(lián)系人詳情中有一個或多個email地址,則email icon就會出現(xiàn),當(dāng)用戶點擊這個icon的時候,該條聯(lián)系人的所有的email地址都會出現(xiàn),當(dāng)用戶點擊email地址時,就會跳轉(zhuǎn)到email app來處理.

QuickContactBadge提供了一個快速訪問聯(lián)系人詳情的方法,用戶因此就可以省去查找,復(fù)制和粘貼聯(lián)系人信息到其他地方,取而代之的時選擇合適的方法來直接發(fā)送聯(lián)系人信息.

4.1 添加一個QuickContactBadge View

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
...
    <QuickContactBadge
               android:id=@+id/quickbadge
               android:layout_height="wrap_content"
               android:layout_width="wrap_content"
               android:scaleType="centerCrop"/>
    ...
</RelativeLayout>

4.2 獲取provider數(shù)據(jù)

為了在QuickContactBadge中展示聯(lián)系人信息,你需要給它提供一個content URI和一個Bitmap,而這兩個可以從Contacts Provider中獲取,要獲取這些數(shù)據(jù)你的projection需要這樣設(shè)置:

a. Android 3.0(API 11)及以后:

b. Android 2.3.3(API 10)及以前:

4.3 設(shè)置Contact URI和縮略圖

4.3.1 設(shè)置Contact URI

首先通過getLookupUri(id,lookupKey)方法獲取到一個CONTENT_LOOKUP_URI,然后調(diào)用assignContactUri()設(shè)置contact uri,如下示例:

    // The Cursor that contains contact rows
    Cursor mCursor;
    // The index of the _ID column in the Cursor
    int mIdColumn;
    // The index of the LOOKUP_KEY column in the Cursor
    int mLookupKeyColumn;
    // A content URI for the desired contact
    Uri mContactUri;
    // A handle to the QuickContactBadge view
    QuickContactBadge mBadge;
    ...
    mBadge = (QuickContactBadge) findViewById(R.id.quickbadge);
    /*
     * Insert code here to move to the desired cursor row
     */
    // Gets the _ID column index
    mIdColumn = mCursor.getColumnIndex(Contacts._ID);
    // Gets the LOOKUP_KEY index
    mLookupKeyColumn = mCursor.getColumnIndex(Contacts.LOOKUP_KEY);
    // Gets a content URI for the contact
    mContactUri =
            Contacts.getLookupUri(
                mCursor.getLong(mIdColumn),
                mCursor.getString(mLookupKeyColumn)
            );
    mBadge.assignContactUri(mContactUri);

4.3.2 設(shè)置縮略圖

給QuickContactBadge設(shè)置了contact URI之后不會自動加載聯(lián)系人頭像縮略圖,要手動加載.先從聯(lián)系人的Cursor中拿到頭像的URI,然后使用它來打開這個文件,接著將該文件讀入Bitmap中.

注意: PHOTO_THUMBNAIL_URI這一列在Android 3.0之前沒有,所以你必須要從Contacts.Photo這個子表中獲取.

首先,設(shè)置變量:

    // The column in which to find the thumbnail ID
    int mThumbnailColumn;
    /*
     * The thumbnail URI, expressed as a String.
     * Contacts Provider stores URIs as String values.
     */
    String mThumbnailUri;
    ...
    /*
     * Gets the photo thumbnail column index if
     * platform version >= Honeycomb
     */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        mThumbnailColumn =
                mCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI);
    // Otherwise, sets the thumbnail column to the _ID column
    } else {
        mThumbnailColumn = mIdColumn;
    }
    /*
     * Assuming the current Cursor position is the contact you want,
     * gets the thumbnail ID
     */
    mThumbnailUri = mCursor.getString(mThumbnailColumn);
    ...

然后,定義一個方法來獲取聯(lián)系人頭像相關(guān)信息,并返回一個Bitmap:

    /**
     * Load a contact photo thumbnail and return it as a Bitmap,
     * resizing the image to the provided image dimensions as needed.
     * @param photoData photo ID Prior to Honeycomb, the contact's _ID value.
     * For Honeycomb and later, the value of PHOTO_THUMBNAIL_URI.
     * @return A thumbnail Bitmap, sized to the provided width and height.
     * Returns null if the thumbnail is not found.
     */
    private Bitmap loadContactPhotoThumbnail(String photoData) {
        // Creates an asset file descriptor for the thumbnail file.
        AssetFileDescriptor afd = null;
        // try-catch block for file not found
        try {
            // Creates a holder for the URI.
            Uri thumbUri;
            // If Android 3.0 or later
            if (Build.VERSION.SDK_INT
                    >=
                Build.VERSION_CODES.HONEYCOMB) {
                // Sets the URI from the incoming PHOTO_THUMBNAIL_URI
                thumbUri = Uri.parse(photoData);
            } else {
            // Prior to Android 3.0, constructs a photo Uri using _ID
                /*
                 * Creates a contact URI from the Contacts content URI
                 * incoming photoData (_ID)
                 */
                final Uri contactUri = Uri.withAppendedPath(
                        Contacts.CONTENT_URI, photoData);
                /*
                 * Creates a photo URI by appending the content URI of
                 * Contacts.Photo.
                 */
                thumbUri =
                        Uri.withAppendedPath(
                                contactUri, Photo.CONTENT_DIRECTORY);
            }
    
        /*
         * Retrieves an AssetFileDescriptor object for the thumbnail
         * URI
         * using ContentResolver.openAssetFileDescriptor
         */
        afd = getActivity().getContentResolver().
                openAssetFileDescriptor(thumbUri, "r");
        /*
         * Gets a file descriptor from the asset file descriptor.
         * This object can be used across processes.
         */
        FileDescriptor fileDescriptor = afd.getFileDescriptor();
        // Decode the photo file and return the result as a Bitmap
        // If the file descriptor is valid
        if (fileDescriptor != null) {
            // Decodes the bitmap
            return BitmapFactory.decodeFileDescriptor(
                    fileDescriptor, null, null);
            }
        // If the file isn't found
        } catch (FileNotFoundException e) {
            /*
             * Handle file not found errors
             */
        // In all cases, close the asset file descriptor
        } finally {
            if (afd != null) {
                try {
                    afd.close();
                } catch (IOException e) {}
            }
        }
        return null;
    }

最后,調(diào)用上述方法來獲取一個Bitmap并設(shè)置到QuickContactBadge中:

    ...
    /*
     * Decodes the thumbnail file to a Bitmap.
     */
    Bitmap mThumbnail =
            loadContactPhotoThumbnail(mThumbnailUri);
    /*
     * Sets the image in the QuickContactBadge
     * QuickContactBadge inherits from ImageView, so
     */
    mBadge.setImageBitmap(mThumbnail);
Add a QuickContactBad

4.4 添加QuickContactBadge到ListView

4.4.1 設(shè)置ListView的Item View

contact_item_layout.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    <QuickContactBadge
        android:id="@+id/quickcontact"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:scaleType="centerCrop"/>
    <TextView android:id="@+id/displayname"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_toRightOf="@+id/quickcontact"
              android:gravity="center_vertical"
              android:layout_alignParentRight="true"
              android:layout_alignParentTop="true"/>
</RelativeLayout>

4.4.2 自定義CursorAdapter

CursorAdapter顧名思義,適用于處理Cursor的Adapter,它是一個抽象類,里面有兩個抽象方法需要子類來實現(xiàn):

如下示例:

    private class ContactsAdapter extends CursorAdapter {
        private LayoutInflater mInflater;
        ...
        public ContactsAdapter(Context context) {
            super(context, null, 0);

            /*
             * Gets an inflater that can instantiate
             * the ListView layout from the file.
             */
            mInflater = LayoutInflater.from(context);
            ...
        }
        ...
        /**
         * Defines a class that hold resource IDs of each item layout
         * row to prevent having to look them up each time data is
         * bound to a row.
         */
        private class ViewHolder {
            TextView displayname;
            QuickContactBadge quickcontact;
        }
        ..
        @Override
        public View newView(
                Context context,
                Cursor cursor,
                ViewGroup viewGroup) {
            /* Inflates the item layout. Stores resource IDs in a
             * in a ViewHolder class to prevent having to look
             * them up each time bindView() is called.
             */
            final View view =
                    mInflater.inflate(
                            R.layout.contact_list_layout,
                            viewGroup,
                            false
                    );
            final ViewHolder holder = new ViewHolder();
            holder.displayname =
                    (TextView) view.findViewById(R.id.displayname);
            holder.quickcontact =
                    (QuickContactBadge)
                            view.findViewById(R.id.quickcontact);
            view.setTag(holder);
            return view;
        }
        ...
        @Override
        public void bindView(
                View view,
                Context context,
                Cursor cursor) {
            final ViewHolder holder = (ViewHolder) view.getTag();
            final String photoData =
                    cursor.getString(mPhotoDataIndex);
            final String displayName =
                    cursor.getString(mDisplayNameIndex);
            ...
            // Sets the display name in the layout
            holder.displayname.setText(displayName);
            ...
            /*
             * Generates a contact URI for the QuickContactBadge.
             */
            final Uri contactUri = Contacts.getLookupUri(
                    cursor.getLong(mIdIndex),
                    cursor.getString(mLookupKeyIndex));
            holder.quickcontact.assignContactUri(contactUri);
            /*
             * Decodes the thumbnail file to a Bitmap.
             * The method loadContactPhotoThumbnail() is defined
             * in the section "Set the Contact URI and Thumbnail"
             */
            Bitmap thumbnailBitmap =
                    loadContactPhotoThumbnail(photoData);
            /*
             * Sets the image in the QuickContactBadge
             * QuickContactBadge inherits from ImageView
             */
            holder.quickcontact.setImageBitmap(thumbnailBitmap);
    }

4.4.3 設(shè)置變量

    // Defines a ListView
    private ListView mListView;
    // Defines a ContactsAdapter
    private ContactsAdapter mAdapter;
    // Defines a Cursor to contain the retrieved data
    private Cursor mCursor;
    /*
     * Defines a projection based on platform version. This ensures
     * that you retrieve the correct columns.
     */
    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,
                    (Build.VERSION.SDK_INT >=
                            Build.VERSION_CODES.HONEYCOMB) ?
                            Contacts.PHOTO_THUMBNAIL_URI :
                        /*
                         * Although it's not necessary to include the
                         * column twice, this keeps the number of
                         * columns the same regardless of version
                         */
                            Contacts._ID

            };
    /*
     * As a shortcut, defines constants for the
     * column indexes in the Cursor. The index is
     * 0-based and always matches the column order
     * in the projection.
     */
// Column index of the _ID column
    private int mIdIndex = 0;
    // Column index of the LOOKUP_KEY column
    private int mLookupKeyIndex = 1;
    // Column index of the display name column
    private int mDisplayNameIndex = 3;
    /*
     * Column index of the photo data column.
     * It's PHOTO_THUMBNAIL_URI for Honeycomb and later,
     * and _ID for previous versions.
     */
    private int mPhotoDataIndex =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    3 :
                    0;

4.4.4 設(shè)置ListView

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

         /*
         * Instantiates the subclass of
         * CursorAdapter
         */
        mAdapter = new ContactsAdapter(this);
        /*
         * Gets a handle to the ListView in the file
         * contact_list_layout.xml
         */
        mListView = (ListView) findViewById(R.id.list);
        // Sets up the adapter for the ListView
        mListView.setAdapter(mAdapter);
        ...
    }

還有一個關(guān)鍵代碼:

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mAdapter.swapCursor(data);
    }

OK,跑起來就可以看到效果了.

總結(jié)

在本例中,官方的training中有很多地方的代碼是有問題的,并且官方提供的demo和training中代碼也不是很一致,最好的方式還是跟著training的思路,然后自己寫demo,官方的demo拿來做參考.
同時在訪問Contacts數(shù)據(jù)的過程中,有一些要注意的地方:

  • 使用Contacts Provider時需要設(shè)置權(quán)限.
  • 不同Android版本的數(shù)據(jù)庫的字段可能不一致.
  • 類的導(dǎo)入要注意,v4就統(tǒng)一v4.
  • 盡量使用Intent來操作.

Reference

  1. Accessing Contacts Data
  2. Retrieving a List of Contacts
  3. Retrieving Details for a Contact
  4. Modifying Contacts Using Intents
  5. Displaying the Quick Contact Badge
  6. Loader
  7. SQL 通配符
  8. ContactsList Demo
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戒突,一起剝皮案震驚了整個濱河市揖闸,隨后出現(xiàn)的幾起案子宿亡,更是在濱河造成了極大的恐慌,老刑警劉巖极景,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異臭笆,居然都是意外死亡逻炊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門慢宗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坪蚁,“玉大人,你說我怎么就攤上這事镜沽∶粑睿” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵缅茉,是天一觀的道長嘴脾。 經(jīng)常有香客問我,道長蔬墩,這世上最難降的妖魔是什么译打? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮拇颅,結(jié)果婚禮上奏司,老公的妹妹穿的比我還像新娘。我一直安慰自己樟插,他們只是感情好韵洋,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黄锤,像睡著了一般麻献。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猜扮,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天勉吻,我揣著相機(jī)與錄音,去河邊找鬼旅赢。 笑死齿桃,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的煮盼。 我是一名探鬼主播短纵,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼僵控!你這毒婦竟也來了香到?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悠就,沒想到半個月后千绪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡梗脾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年荸型,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炸茧。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡瑞妇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梭冠,到底是詐尸還是另有隱情辕狰,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布控漠,位于F島的核電站柳琢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏润脸。R本人自食惡果不足惜柬脸,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毙驯。 院中可真熱鬧倒堕,春花似錦、人聲如沸爆价。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铭段。三九已至骤宣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間序愚,已是汗流浹背憔披。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留爸吮,地道東北人芬膝。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像形娇,于是被迫代替她去往敵國和親锰霜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容