Android學習筆記二十一.使用ContentProvider實現資料共用(四).作業系統(連絡人)的ContentProvider,androidprovider
Android系統本身提供了大量的ContentProvider,例如連絡人資訊、系統的多媒體資訊等,我們開發的應用程式主要是通過ContentResolver來調用系統的ContentProvider提供的query()、insert()、update()和delete()方法來擷取Android內部的資料。一、如何使用ContentResolver作業系統ContentProvider暴露的內部資料?1.調用Activity的getContentResolver()擷取ContentResolver對象;2.根據需要調用ContentResolver的insert()、delete()、update()和query()方法操作資料。二、實戰源碼剖析1.加入讀寫權限<uses-permission android:name="android.permission.READ_CONTACTS" /> //授予讀連絡人ContentProvider的許可權<uses-permission android:name="android.permission.WRITE_CONTACTS" /> //授予寫連絡人ContentProvider的許可權2.連絡人ContentProvider的Uri(1)ContactsContract.Contacts.CONTENT_URI(content://com.android.contacts/contacts):管理連絡人的Uri;(2)ContactsContract.CommonDataKinds.Phone.CONTENT_URI(content://com.android.contacts/data/phones):管理連絡人的電話的Uri;(3)ContactsContract.CommonDataKinds.Email.CONTENT_URI(content://com.android.contacts/data/emails ):管理連絡人的E-Mail的Uri。3.取得所有連絡人的表的Cursor對象:
1)ContentResolver contentResolver=getContentResolver();//擷取 ContentResolver物件查詢在ContentProvider裡定義的共用對象;2)Cursor cursor=contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);//根據URI對象ContactsContract.Contacts.CONTENT_URI查詢所有連絡人4.擷取指定連絡人(_ID)的具體資訊 從Cursor對象裡我們關鍵是要取得連絡人的_id。通過它,再通過ContactsContract.CommonDataKinds的各個子類查詢該_id連絡人的電話(ContactsContract.CommonDataKinds.Phone),email(ContactsContract.CommonDataKinds.Email)等等。以取得該連絡人所有電話為例:1)int idFieldIndex=cursor.getColumnIndex(ContactsContract.Contacts._ID); int id=cursor.getInt(idFieldIndex);//根據列名取得該連絡人的id;2)Cursor phonecursor=contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID+"="+id, null, null);//再類ContactsContract.CommonDataKinds.Phone中根據查詢相應id連絡人的所有電話; 類似地可以ContactsContract.CommonDataKinds的不同的子類查詢不同的內容。android文檔告訴我們推薦使用ContactsContract.Contacts.LOOKUP_KEY代替ContactsContract.Contacts._ID。
升華筆記:1.Cursor介面(1)功能概述:該介面主要用於隨機讀寫從資料庫查詢返回的set集合形式的結果。(2)常用方法:abstract void close() :關閉Cursor,釋放所有資源abstract boolean moveToNext() :將遊標移動到下一行String getString(int columnIndex):將指定的列索引對應的值轉換為字串返回int getColumnIndex(String columnName) :返回給定列名對應的列索引值(為0開始的整型值),傳入參數為列名;2.由於讀取連絡人比較的佔用資源,為了提高使用者的體驗度。考慮將讀取的過程放線上程裡完成,推薦使用AsyncTask類。取Android系統的通訊錄時一般會先讀取連絡人然後再讀取其號碼,嵌套迴圈讀取。如果通訊錄人數不多速度尚可,但是通訊錄裡有1-2百人恐怕就比較慢了,如果硬體再差點體驗就更差了。可以使用ContactsContract.CommonDataKinds.Phone.CONTENT_URI(對應contacts2.db的資料檢視view_data_restricted)視圖來讀取避免嵌套讀取,而對於PhoneLookup.CONTENT_FILTER_URI確不能直接使用
4.連絡人的API(1)ContactsContract[public final class] java.lang.Object ↳ android.provider.ContactsContract ContactsContract是連絡人資料提供者和應用程式的契約書,它定義所有支援的Content Provider URI和資料列。 從Android 2.0(API Level 5)開始,Android平台提供了一個改進的Contacts API,以適應一個連絡人可以有多個帳戶的需求,比如說手機通訊錄和GMAIL通訊錄,兩個通訊錄中的兩條記錄可以是同一個人。新的Contacts API主要是由ContactsContract及其相關的類來管理,舊的API(android.provider.Contacts)已不贊成使用,但為了相容仍可以使用,只不過像以前一樣,只能返回第一個帳戶的資訊。 在新的Contacts API中,連絡人資料被放到三張表中:Contacts、RawContacts和Data。這樣可以協助系統更好地儲存與管理一個連絡人的多個帳戶的資訊。ContactsContract表結構介紹:A.ContactsContract.Data 用於儲存個人資料,例如電話號碼、郵件、手機鈴聲、即時通訊方式、照片等等B.ContactsContract.RawContacts 用於關聯連絡人資訊與帳號,因為有可能手機的連絡人資訊是從不同的Gmail或者其他地方匯入的,為互相區別並方便同步,特引入帳號概念。C.ContactsContract.Contacts 屬於不同帳號下的某連絡人資訊可能描述的是同一個人,這張表就是RawContacts的並集,如果某連絡人資訊被修改,和它描述同一個人資訊肯定也要做相應的更新。
(2)ContactsContract.Data Data表格儲存體了連絡人的詳細資料,表中的每一行儲存一個特定類型的資訊,比如Email、Address或Phone。每一行通過一個mimetype_id的欄位來表示該行儲存的是什麼類型的資料,該欄位引用了mimetyps表,此表格儲存體了常用的資料類型。Data表的欄位主要有:mimetype_id :表示該行儲存的資訊的類型 raw_contact_id: 表示該行所屬的RawContactis_primary :多個data資料群組成一個raw contact,該欄位表示此data是否是其所屬的raw contact的主data,即其display name會作為raw contact的display nameis_super_primary :該data是否是其所屬的contact的主data,如果is_super_primary為1則is_primary一定為1data1~data15 :15個資料欄位,對於不同類型的資訊,表示不同的含義,ContactsContract.CommomDataKinds類中定義了與常用的資料類型相對應的一些類,這些類中分別定義了相應資料類型中這些欄位表示的含義。一般data1表是主資訊(如電話,Email地址等),data2表示副資訊,data15表示Blob資料。data_sync1~data_sync4 sync_adapter:要用的欄位(sync_adapter用於資料的同步,比如你手機中的Gmail帳戶與Google伺服器的同步)。data_version :資料的版本,用於資料的同步。 Data類中的Data.CONTACT_ID與Data.RAW_CONTACT_ID分別表示該表項對應的連絡人在Contact與RawContract表中的ID,我們只需要知道某一個連絡人的contactId或rawContractId,並根據其尋找的資料的類型就可以查到相應類型的資訊。(3)ContactsContract.RawContact RawContact表中的一行儲存Data表中一些資料行的集合及一些其他的資訊,表示一個連絡人某一特定帳戶的資訊,比如Facebook或Exchange的一個連絡人。 當插入一個raw contact或當一個raw contact所屬的一個data改變時,系統會檢查這個raw contact跟其他的raw contact是否可以匹配(比如如果兩個raw contact的data包含相同的電話號碼或名字),如果匹配他們就會被綜合到一起,也就是說他們會屬於同一個cantact,表現為在RawContact表中他們引用的cantact_id是一樣的。 連絡人姓名、組織、電話號碼、Email或暱稱的改變會引發raw contact的重新彙總。有兩個方法控制彙總的行為Aggregaton Mode與ContactsContract.AggregationExceptions。 Aggregaton Mode:RawContact表中有一個欄位aggregation_mode,通過向特定raw contact行中插入這個欄位可以修改系統對這個raw contact的彙總行為,其允許的值如下:AGGREGATION_MODE_DEFAULT:正常模式,允許自動彙總;AGGREGATION_MODE_DISABLE:不允許彙總;AGGREGATION_MODE_SUSPENDED:當一個raw contact的aggregation mode修改為suspended時,如果其已是一個已彙總的contact的一部分,那麼它仍會保持與原來彙總到一起的raw contact的關係,即使它已改變不再跟其他raw contact匹配。ContactsContract.AggregationExceptions:在資料庫中存在一個表:agg_exceptions。通過欄位raw_contact_id1、raw_contact_id2、mode儲存兩個raw contact彙總的方法,系統定義的彙總行為有3個:TYPE_AUTOMATIC=0 由系統決定彙總行為,預設值。TYPE_KEEP_SEPARATE=2 不彙總TYPE_KEEP_TOGETHER=1 彙總(4)ContactsContract.ContactContact表中的一行表示一個連絡人,它是RawContact表中的一行或多行的資料的組合,這些RawContact表中的行表示同一個人的不同的帳戶資訊。Contact中的資料由系統組合RawContact表中的資料自動產生。 不可以直接向這個表中插入資料,當一個raw contact被插入的時候,系統會首先尋找Contact表看是否有記錄跟插入的raw contact表示同一個人,如果找到了,則把找到的這個contact的_ID插入raw contact記錄的CONTACT_ID欄位,如果沒有找到,則系統自動插入一個Contact記錄並把它的_ID插入新插入的raw contact的CONTACT_ID列。 Contact表中只有TIMES_CONTACTED、LAST_TIME_CONTACTED、STARRED、CUSTOM_RINGTONE、SENE_TO_VOICEMAIL列可更改,這些列的更改會導致相應的raw contact被更改。 當刪除Contact表中的記錄時,會刪除一個連絡人的所有帳戶的資訊,也就是說,其對應的所有raw contacts也會被刪除,各raw contact對應的data也就被刪除了,sync adapter同步時也會刪除伺服器端的相應記錄。 如果需要讀取一個連絡人的資訊用CONTENT_LOOKUP_RUI代替CONTENT_URI(見後面);如果需要通過電話號碼尋找一個連絡人,用PhoneLookup.CONTENT_FIILTER_URI,這個URI為這個目的進行了最佳化;如果需要通過部分名字的匹配尋找,用CONTENT_FILTER_URI;如果需要通過email,address等資訊尋找,尋找表ContactsContract.Data,結果包含contact ID,名字... ContactsContract的子類ContactsContract.Contacts是一張表,代表了所有連絡人的統計資訊。比如連絡人ID(—ID),查詢鍵(LOOKUP_KEY),連絡人的姓名(DISPLAY_NAME_PRIMARY),頭像的id(PHOTO_ID)以及群組的id等等。
(5)ContactsContract.CommonDataKinds類在前面講Data表的結構時講到,Data的data1~data15欄位用於儲存各類型的資料資訊,那麼這15個欄位分別表示什麼資訊呢?前面提到了,Data表中有一個mimetype_id欄位,通過這個欄位關聯mimetypes表表示該行代表的資訊類型,因為Data表中的每一行可以表示如Phone或Address等不同類型的資訊,所以對於不同類型的資訊,data1~data15這15列表示不同的含義,如果要靠記憶記住這15列對於特定的類型分別表示什麼意義自然不行,於是Google就預定義了一些類,每一個類對應一些預先定義好的資料類型,在每個類中定義了一些語義地、方便記憶的常量,用來對應這15個欄位,比如在CommonDataKinds.Email類中有如下定義publicstaticfinal String ADDRESS = DATA1;publicstaticfinal String DISPLAY_NAME = DATA4;DATA1與DATA4為繼承自DataColumns中的常量,在DataColumns中是這樣定義的:publicstaticfinal String DATA1 = "data1";publicstaticfinal String DATA4 = "data4";這樣,當於們要尋找Email地址時,只需要通過ContactsContract.CommonDataKinds.Email.ADDRESS引用,而不需要知道它是儲存在Data表中的data1列中。
三、源碼
public class ContactProviderTest extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button search=(Button)findViewById(R.id.searchBtn); /*查詢連絡人*/ search.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { //1.定義兩個List來封裝系統的連絡人資訊,指定連絡人的電話號碼、Email等詳情 final ArrayList<String> names=new ArrayList<String>(); final ArrayList<ArrayList<String>> details=new ArrayList<ArrayList<String>>(); //2.使用ContentResolver尋找連絡人資料(Query the given URI, returning a Cursor over the result set) Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null); //3.遍曆查詢結果,擷取系統中所有連絡人 while(cursor.moveToNext()) { //a.擷取連絡人ID String contactId=cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)); //b.擷取連絡人的名字 String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); names.add(name); //將名字依次添加到List列表中 /*-----------------------------------------------------------------------------------------------------*/ //c.使用ContentResolver尋找連絡人的電話號碼 Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId, null, null); //d.遍曆查詢結果,擷取該連絡人的多個電話號碼 ArrayList<String> detail = new ArrayList<String>(); while(phones.moveToNext()) { String phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); detail.add("電話號碼"+phoneNumber); } phones.close(); /*-----------------------------------------------------------------------------------------------------*/ //e.使用ContentResolver尋找連絡人的E-Mail地址 Cursor emails = getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, ContactsContract.CommonDataKinds.Email.CONTACT_ID+"="+contactId, null, null); //f.遍曆查詢結果,擷取該連絡人的多個E-Mail地址 while(emails.moveToNext()) { String emailAddress = emails.getString(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)); detail.add("郵件地址:"+emailAddress); } emails.close(); details.add(detail); } cursor.close(); /*-----------------------------------------------------------------------------------------------------*/ //4.載入result.xml View resultDialog = getLayoutInflater().inflate(R.layout.result, null); //5.擷取resultDialog中ID為list的ExpandableListView ExpandableListView list = (ExpandableListView) resultDialog.findViewById(R.id.list); //6.建立一個ExpandableListAdapter對象 ExpandableListAdapter adapter = new BaseExpandableListAdapter() { //a.擷取指定組位置、指定子清單項目處的子清單項目資料 @Override public Object getChild(int groupPosition, int childPosition) { return details.get(groupPosition).get(childPosition); } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public int getChildrenCount(int groupPosition) { return details.get(groupPosition).size(); } private TextView getTextView() { AbsListView.LayoutParams lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 64); TextView textView = new TextView(ContactProviderTest.this); textView.setLayoutParams(lp); textView.setGravity(Gravity.CENTER_VERTICAL|Gravity.LEFT); textView.setPadding(36, 0, 0, 0); textView.setTextSize(20); return textView; } //b.該方法決定每個子選項的外觀 @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { TextView textView = getTextView(); textView.setText(getChild(groupPosition,childPosition).toString()); return textView; } //c.擷取指定組位置處的組資料 @Override public Object getGroup(int groupPosition) { return names.get(groupPosition); } @Override public int getGroupCount() { return names.size(); } @Override public long getGroupId(int groupPosition) { return groupPosition; } //d.該方法決定每個組選項的外觀 @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { TextView textView = getTextView(); textView.setText(getGroup(groupPosition).toString()); return textView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } @Override public boolean hasStableIds() { return true; } }; //7.為ExpandableListView設定Adapter對象 list.setAdapter(adapter); //8.使用對話方塊來顯示查詢結果 new AlertDialog.Builder(ContactProviderTest.this) .setView(resultDialog) .setPositiveButton("確定",null) .show(); } }); /*新增連絡人...*/ Button add=(Button)findViewById(R.id.addBtn); add.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { //a.擷取程式介面中的三個文字框 String name = ((EditText)findViewById(R.id.name)).getText().toString(); String phone = ((EditText)findViewById(R.id.phone)).getText().toString(); String email = ((EditText)findViewById(R.id.email)).getText().toString(); //b.建立一個空的ContentValues ContentValues values = new ContentValues(); //c.向RawContacts.CONTENT_URI執行一個空值插入嗎,目的是擷取系統返回的rawContactId Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values); long rawContactId=ContentUris.parseId(rawContactUri); values.clear(); /*-----------------------------------------------------*/ values.put(Data.RAW_CONTACT_ID, rawContactId); //d.設定內容類型 values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); //e.設定連絡人名字 values.put(StructuredName.GIVEN_NAME, name); //f.向連絡人URI新增連絡人...名字 getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI, values); values.clear(); /*-----------------------------------------------------*/ values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); //g.設定連絡人的電話號碼 values.put(Phone.NUMBER, phone); //h.設定電話類型 values.put(Phone.TYPE, Phone.TYPE_MOBILE); //i.向連絡人電話號碼URI添加電話號碼 getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI, values); values.clear(); /*-----------------------------------------------------*/ values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); //j.設定連絡人的E-mail地址 values.put(Email.DATA, email); //k.設定該電子郵件的類型 values.put(Email.TYPE, Email.TYPE_WORK); //l.向連絡人E-mail URI添加E-mail資料 getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI, values); Toast.makeText(ContactProviderTest.this,"連絡人資料添加成功", Toast.LENGTH_SHORT).show(); } }); } }
效果如下:
參考:http://blog.163.com/hesky_fly/blog/static/732868692011102311551131/http://wear.techbrood.com/reference/android/provider/ContactsContract.Data.html