ContentProvider
前面幾節(jié)介紹了進(jìn)程間通信的幾種方式,包括消息包級(jí)別的Messenger、接口調(diào)用級(jí)別的AIDL、啟動(dòng)頁(yè)面/服務(wù)級(jí)別的Notification,還有就是本節(jié)這個(gè)數(shù)據(jù)庫(kù)級(jí)別的ContentProvider。
ContentProvider為存取數(shù)據(jù)提供統(tǒng)一的接口,它讓不同APP之間得以共享數(shù)據(jù)。ContentProvider類本身是個(gè)服務(wù)端的數(shù)據(jù)存取接口,主要操作類似SQLite,也都提供了如下常見的數(shù)據(jù)庫(kù)管理API:
query : 查詢數(shù)據(jù)。
insert : 插入數(shù)據(jù)。
update : 更新數(shù)據(jù)。
delete : 刪除數(shù)據(jù)。
getType : 獲取數(shù)據(jù)類型。
實(shí)際開發(fā)中,APP很少會(huì)開放數(shù)據(jù)接口給其他應(yīng)用,所以ContentProvider類作為服務(wù)端接口反而基本用不到。Content組件中能夠用到的場(chǎng)合,基本上是APP想要使用系統(tǒng)的手機(jī)通訊數(shù)據(jù),比如查看聯(lián)系人/短信/彩信/通話記錄,以及對(duì)這些通訊信息進(jìn)行增刪改。
ContentResolver
使用說明
ContentResolver是客戶端APP用來操作服務(wù)端數(shù)據(jù)的接口,相對(duì)應(yīng)的ContentProvider是服務(wù)端的接口。獲取一個(gè)ContentResolver對(duì)象,調(diào)用Context.getContentResolver()即可。
與ContentProvider一樣,客戶端的ContentResolver也提供了query、insert、update、delete、getType等等方法。其中最常用的是query函數(shù),調(diào)用該函數(shù)返回一個(gè)Cursor對(duì)象,有關(guān)Cursor的操作參見《Android開發(fā)筆記(三十一)SQLite游標(biāo)及其數(shù)據(jù)結(jié)構(gòu)》。下面是query的具體參數(shù)說明:
uri : Uri類型,可以理解為本次操作的數(shù)據(jù)表路徑
projection : String[]類型,指定將要查詢的字段名列表
selection : String類型,指定查詢條件
selectionArgs : String[]類型,指定查詢條件中的參數(shù)取值列表
sortOrder : String類型,指定排序條件
下面是ContentResolver在查看通訊信息中的具體運(yùn)用:
讀取聯(lián)系人
代碼示例如下:
private static Uri mContactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; private static String[] mContactColumn = new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME }; public static int readPhoneContacts(ContentResolver resolver) { ArrayList<Contact> contactArray = new ArrayList<Contact>(); Cursor cursor = resolver.query(mContactUri, mContactColumn, null, null, null); if (cursor.moveToFirst()) { for (;; cursor.moveToNext()) { Contact contact = new Contact(); contact.phone = cursor.getString(0).replace("+86", "").replace(" ", ""); contact.name = cursor.getString(1); Log.d(TAG, contact.name+" "+contact.phone); contactArray.add(contact); if (cursor.isLast() == true) { return contactArray.size();
上面代碼獲取的是手機(jī)里的聯(lián)系人。獲取SIM卡上的聯(lián)系人與之類似,不同之處要把Uri換成“content://icc/adn”。
讀取短信
代碼示例如下:
private static Uri mSmsUri; private static String[] mSmsColumn; @TargetApi(Build.VERSION_CODES.KITKAT) public static int readSms(ContentResolver resolver, String phone, int gaps) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mSmsUri = Telephony.Sms.Inbox.CONTENT_URI; mSmsColumn = new String[] { Telephony.Sms.ADDRESS, Telephony.Sms.PERSON, Telephony.Sms.BODY, Telephony.Sms.DATE, mSmsUri = Uri.parse("content://sms/inbox"); mSmsColumn = new String[] { "address","person","body","date","type" }; ArrayList<SmsContent> smsArray = new ArrayList<SmsContent>(); if (phone!=null && phone.length()>0) { selection = String.format("address=''%s''", phone); selection = String.format("%s%sdate>%d", selection, (selection.length()>0)?" and ":"", System.currentTimeMillis()-gaps*1000); Cursor cursor = resolver.query(mSmsUri, mSmsColumn, selection, null, "date desc"); if (cursor.moveToFirst()) { for (;; cursor.moveToNext()) { SmsContent sms = new SmsContent(); sms.address = cursor.getString(0); sms.person = cursor.getString(1); sms.body = cursor.getString(2); sms.date = formatDate(cursor.getLong(3)); sms.type = cursor.getInt(4); //type=1表示收到的短信,type=2表示發(fā)送的短信 Log.d(TAG, sms.address+" "+sms.person+" "+sms.date+" "+sms.type+" "+sms.body); if (cursor.isLast() == true) {
讀取彩信
代碼示例如下:
private static Uri mMmsUri; private static String[] mMmsColumn; @TargetApi(Build.VERSION_CODES.KITKAT) public static int readMms(ContentResolver resolver, int gaps) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mMmsUri = Telephony.Mms.Inbox.CONTENT_URI; mMmsColumn = new String[] { Telephony.Mms.DATE, Telephony.Mms.READ, Telephony.Mms.SUBJECT, Telephony.Mms.EXPIRY, Telephony.Mms.STATUS, Telephony.Mms.MESSAGE_SIZE}; mMmsUri = Uri.parse("content://mms/inbox"); mMmsColumn = new String[] { "date","read","sub","exp","st","m_size" }; ArrayList<MmsContent> mmsArray = new ArrayList<MmsContent>(); String selection = String.format("date>%d", System.currentTimeMillis()-gaps*1000); Cursor cursor = resolver.query(mMmsUri, mMmsColumn, selection, null, "date desc"); if (cursor.moveToFirst()) { for (;; cursor.moveToNext()) { MmsContent mms = new MmsContent(); mms.date = formatDate(cursor.getLong(0)); mms.read = cursor.getString(1); mms.subject = cursor.getString(2); mms.expire = cursor.getString(3); mms.status = cursor.getString(4); mms.message_size = cursor.getString(5); Log.d(TAG, mms.date+" "+mms.read+" "+mms.subject+" "+mms.expire+" "+mms.status+" "+mms.message_size); if (cursor.isLast() == true) {
讀取通話記錄
代碼示例如下:
private static Uri mRecordUri = CallLog.Calls.CONTENT_URI; private static String[] mRecordColumn = new String[] { CallLog.Calls.CACHED_NAME, CallLog.Calls.NUMBER, CallLog.Calls.TYPE, CallLog.Calls.DATE, CallLog.Calls.DURATION, CallLog.Calls.NEW }; public static int readCallRecord(ContentResolver resolver, int gaps) { ArrayList<CallRecord> recordArray = new ArrayList<CallRecord>(); String selection = String.format("date>%d", System.currentTimeMillis()-gaps*1000); Cursor cursor = resolver.query(mRecordUri, mRecordColumn, selection, null, "date desc"); if (cursor.moveToFirst()) { for (;; cursor.moveToNext()) { CallRecord record = new CallRecord(); record.name = cursor.getString(0); record.phone = cursor.getString(1); record.type = cursor.getInt(2); //type=1表示接聽,2表示撥出,3表示未接 record.date = formatDate(cursor.getLong(3)); record.duration = cursor.getLong(4); record._new = cursor.getInt(5); Log.d(TAG, record.name+" "+record.phone+" "+record.type+" "+record.date+" "+record.duration); if (cursor.isLast() == true) { return recordArray.size();
ContentProviderOperation
使用說明
前面說過,ContentResolver可以由客戶端用來給服務(wù)端添加數(shù)據(jù),不過有時(shí)候某種數(shù)據(jù)在服務(wù)端對(duì)應(yīng)的是多張表,比如說聯(lián)系人信息在服務(wù)端實(shí)際有聯(lián)系人姓名表、聯(lián)系人電話表(因?yàn)橛屑彝ル娫?、工作電話之分)、?lián)系人電子郵箱表。對(duì)于這種情況,使用ContentResolver固然可以通過多次插入來實(shí)現(xiàn),可是多次插入就對(duì)應(yīng)多個(gè)事務(wù),一旦某次插入失敗,那我們還得手工進(jìn)行回滾操作,非常麻煩。
針對(duì)上面的問題,Android提供了ContentProviderOperation類,用于在一個(gè)事務(wù)中批量插入多條記錄,這樣即使出現(xiàn)失敗,也會(huì)由ContentProviderOperation統(tǒng)一處理回滾事宜,避免了開發(fā)者關(guān)注內(nèi)部事務(wù)的麻煩。
下面是兩種插入方式在添加聯(lián)系人信息中的具體運(yùn)用:
ContentResolver方式
代碼示例如下:
public static void addContacts(ContentResolver resolver) { //往 raw_contacts 中添加數(shù)據(jù),并獲取添加的id號(hào) Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts"); ContentValues values = new ContentValues(); long contactId = ContentUris.parseId(resolver.insert(raw_uri, values)); //往 data 中添加數(shù)據(jù)(要根據(jù)前面獲取的id號(hào)) Uri uri = Uri.parse("content://com.android.contacts/data"); ContentValues name = new ContentValues(); name.put("raw_contact_id", contactId); name.put("mimetype", "vnd.android.cursor.item/name"); resolver.insert(uri, name); ContentValues phone = new ContentValues(); phone.put("raw_contact_id", contactId); phone.put("mimetype", "vnd.android.cursor.item/phone_v2"); phone.put("data1", "15960238696"); resolver.insert(uri, phone); ContentValues email = new ContentValues(); email.put("raw_contact_id", contactId); email.put("mimetype", "vnd.android.cursor.item/email_v2"); email.put("data1", "aaa@163.com"); resolver.insert(uri, email);
ContentProviderOperation方式
代碼示例如下:
public static void addFullContacts(ContentResolver resolver) { Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts"); Uri uri = Uri.parse("content://com.android.contacts/data"); ContentProviderOperation op_main = ContentProviderOperation.newInsert(raw_uri) .withValue("account_name", null).build(); ContentProviderOperation op_name = ContentProviderOperation.newInsert(uri) .withValueBackReference("raw_contact_id", 0) .withValue("mimetype", "vnd.android.cursor.item/name") .withValue("data2", "阿三").build(); ContentProviderOperation op_phone = ContentProviderOperation.newInsert(uri) .withValueBackReference("raw_contact_id", 0) .withValue("mimetype", "vnd.android.cursor.item/phone_v2") .withValue("data1", "15960238696").build(); ContentProviderOperation op_email = ContentProviderOperation .newInsert(uri).withValueBackReference("raw_contact_id", 0) .withValue("mimetype", "vnd.android.cursor.item/email_v2") .withValue("data1", "aaa@163.com").build(); ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); operations.add(op_phone); operations.add(op_email); resolver.applyBatch("com.android.contacts", operations);
ContentObserver
使用說明
有時(shí)我們不但要獲取以往的數(shù)據(jù),還要實(shí)時(shí)獲取新增的數(shù)據(jù),最常見的業(yè)務(wù)場(chǎng)景便是短信驗(yàn)證碼。電商APP中常常在用戶注冊(cè)或者付款時(shí)下發(fā)驗(yàn)證碼短信,這時(shí)為提高用戶體驗(yàn),APP就得自動(dòng)獲取手機(jī)剛收到的短信驗(yàn)證碼。類似的場(chǎng)景在系統(tǒng)APP中也存在,比如流量監(jiān)控APP向運(yùn)營(yíng)商發(fā)送流量校準(zhǔn)短信,此時(shí)APP也得自動(dòng)攔截短信來獲取流量信息。
由于系統(tǒng)在接收短信后會(huì)同時(shí)發(fā)出一個(gè)廣播“android.provider.Telephony.SMS_RECEIVED”,所以我們可以使用廣播接收器來監(jiān)聽短信的接收動(dòng)作。然而不是所有的系統(tǒng)數(shù)據(jù)變更都會(huì)觸發(fā)廣播(比如添加聯(lián)系人),所以Android又提供了ContentObserver類,該類可協(xié)助處理Content數(shù)據(jù)變化的監(jiān)聽事件。
下面是在ContentResolver對(duì)象中使用ContentObserver的相關(guān)方法:
registerContentObserver : 注冊(cè)內(nèi)容觀察者。
unregisterContentObserver : 注銷內(nèi)容觀察者。
notifyChange : 通知內(nèi)容觀察者發(fā)生了數(shù)據(jù)變化。
下面是兩種監(jiān)聽方式在監(jiān)聽短信接收中的具體運(yùn)用,監(jiān)聽結(jié)果消息使用了Notification推送到消息欄,有關(guān)Notification的使用說明參見《Android開發(fā)筆記(五十二)通知推送Notification》。
廣播方式
廣播類的代碼示例如下:
import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.telephony.SmsMessage; public class SmsGetReceiver extends BroadcastReceiver { private static final String TAG = "SmsGetReceiver"; public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); SmsMessage[] smsMessages = null; pdus = (Object[]) bundle.get("pdus"); smsMessages = new SmsMessage[pdus.length]; for (int i=0; i<pdus.length; i++){ smsMessages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); sender = smsMessages[i].getOriginatingAddress(); content = smsMessages[i].getMessageBody(); Log.d(TAG, "SMS:"+sender+content); NotificationUtil.sendSmsNotify(context, "廣播來源:"+sender, content);
配置文件需要注冊(cè)該廣播
<receiver android:name=".content.util.SmsGetReceiver"> <action android:name="android.provider.Telephony.SMS_RECEIVED" />
觀察者方式
觀察者類的代碼示例如下:
import android.annotation.TargetApi; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.os.Handler; import android.provider.Telephony; @TargetApi(Build.VERSION_CODES.KITKAT) public class SmsGetObserver extends ContentObserver { private static final String TAG = "SmsGetObserver"; private Context mContext; private static Uri mSmsUri; private static String[] mSmsColumn; public SmsGetObserver(Context context, Handler handler) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mSmsUri = Telephony.Sms.Inbox.CONTENT_URI; mSmsColumn = new String[] { Telephony.Sms.ADDRESS, Telephony.Sms.BODY, Telephony.Sms.DATE }; mSmsUri = Uri.parse("content://sms/inbox"); mSmsColumn = new String[] { "address","body","date" }; public void onChange(boolean selfChange) { String selection = String.format("date>%d", System.currentTimeMillis()-2*1000); Cursor cursor = mContext.getContentResolver().query( mSmsUri, mSmsColumn, selection, null, null); while(cursor.moveToNext()){ sender = cursor.getString(0); content = cursor.getString(1); NotificationUtil.sendSmsNotify(mContext, "觀察者來源:"+sender, content); super.onChange(selfChange);
主頁(yè)面中對(duì)觀察者類的調(diào)用代碼如下:
SmsGetObserver observer = new SmsGetObserver(this, new Handler()); getContentResolver().registerContentObserver( Uri.parse("content://sms"), true, observer);
常用的Uri
總結(jié)下在Content組件中使用過程中遇到的幾個(gè)Uri常量:
聯(lián)系人信息(不包含手機(jī)號(hào)與電子郵箱):
ContactsContract.Contacts.CONTENT_URI content://com.android.contacts/contacts
聯(lián)系人電話信息:
ContactsContract.CommonDataKinds.Phone.CONTENT_URI content://com.android.contacts/data/phones
聯(lián)系人郵箱信息:
ContactsContract.CommonDataKinds.Email.CONTENT_URI content://com.android.contacts/data/emails
SIM卡聯(lián)系人信息:
content://icc/adn
短信信息:
Telephony.Sms.CONTENT_URI content://sms
彩信信息:
Telephony.Mms.CONTENT_URI content://mms
通話記錄信息:
CallLog.Calls.CONTENT_URI content://call_log/calls
下面是與短信有關(guān)的Uri分類說明:
收件箱:
Telephony.Sms.Inbox.CONTENT_URI content://sms/inbox
已發(fā)送:
Telephony.Sms.Sent.CONTENT_URI content://sms/sent
草稿箱:
Telephony.Sms.Draft.CONTENT_URI content://sms/draft
發(fā)件箱(正在發(fā)送的信息):
Telephony.Sms.Outbox.CONTENT_URI content://sms/outbox
發(fā)送失?。?br>
content://sms/failed
待發(fā)送列表(比如開啟飛行模式后,該短信就在待發(fā)送列表里):
content://sms/queued
|