BroadcastReceiver
Android應(yīng)用可以從Android系統(tǒng)和其他Android應(yīng)用發(fā)送或接收廣播消息辕羽,類似于 發(fā)布 - 訂閱 設(shè)計模式租冠。當(dāng)感興趣的事件發(fā)生時,發(fā)送這些廣播遏片。例如嘹害,Android系統(tǒng)在發(fā)生各種系統(tǒng)事件時發(fā)送廣播,例如系統(tǒng)啟動或設(shè)備開始充電時吮便。例如笔呀,應(yīng)用程序還可以發(fā)送自定義廣播,以通知其他應(yīng)用程序他們可能感興趣的內(nèi)容(例如髓需,已下載了一些新數(shù)據(jù))许师。
什么是廣播
由上述摘自官方文檔的描述我們可以知道,廣播可以用來在:APP內(nèi)部僚匆、不同APP之間枯跑、APP與Android系統(tǒng)之間傳遞消息。
廣播應(yīng)用場景
通過上述理解我們可以總結(jié)一下廣播的常用的應(yīng)用場景:
- 應(yīng)用內(nèi)外不同組件的通信
- 多線程通信
-
與Android系統(tǒng)在特定情況下的通信
等
廣播使用
廣播角色
使用廣播來進行消息傳遞白热,如果需要傳遞一個消息敛助,那么最少需要一個消息的發(fā)送者,一個消息的接收者屋确。廣播進行消息的傳遞也是使用了廣播發(fā)送和廣播接收兩個角色
廣播接收
為了方便理解先說廣播接收纳击,即如何接收其他廣播發(fā)送來的消息续扔。廣播的接收使用BroadcastReceiver
類,要實現(xiàn)一個廣播接收者需要兩個步驟
1焕数、繼承BroadcastReceiver
并重寫onReceive()
方法
public class MyBroadcastReceiver extends BroadcastReceiver {
/**
* 重寫該方法纱昧,當(dāng)接收到注冊的相應(yīng)廣播后會執(zhí)行該方法
* @param context
* @param intent
*/
@Override
public void onReceive(Context context, Intent intent) {
Log.e("接收到一個廣播");
}
}
廣播接收者示例代碼
2、注冊廣播接收者
廣播接收的注冊分為兩種方式堡赔。
-
靜態(tài)注冊
靜態(tài)注冊的廣播為常駐廣播识脆,即不會受到任何組件生命周期的影響。如果需要時刻監(jiān)聽某廣播則需要靜態(tài)注冊善已,如監(jiān)聽手機開機灼捂、鎖屏等操作。在App首次啟動時换团,系統(tǒng)會自動實例化繼承自BroadcastReceiver
的類并注冊到系統(tǒng)中悉稠。
優(yōu)點:應(yīng)用程序關(guān)閉后,程序依舊會被系統(tǒng)調(diào)用艘包。
缺點:耗電占用內(nèi)存的猛。
使用:在AndroidManifest.xml中通過<receive>標(biāo)簽聲明。
<receiver android:name="com.example.MyBroadcastReceiver">
<intent-filter>
<!-- 接收的廣播類型 -->
<action android:name="MY_STATIC_BROADCAST" />
<action android:name="Intent.ACTION_SCREEN_OFF" />
</intent-filter>
</receiver>1
注意:Android8.0禁止了大多數(shù)靜態(tài)注冊
動態(tài)注冊
不常駐廣播想虎,跟隨組件生命周期變化卦尊,在特定時刻監(jiān)聽廣播可以使用動態(tài)注冊。
使用:在代碼中調(diào)用Context.registerReceiver()方法進行注冊
//動態(tài)注冊廣播
myBroadcastReceiver=new MyBroadcastReceiver(); //實例化廣播接收者和IntentFilter
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(RECEIVER_ACTION); //設(shè)置接收廣播類型
intentFilter.addAction(Intent.ACTION_SCREEN_OFF); //可以接收多個廣播類型
registerReceiver(mDynamicRegisterReceiver,intentFilter); //注冊廣播
注意:動態(tài)廣播最好在onResume()中注冊onPause()中注銷舌厨,因為onPause()方法在app死亡之前一定會被執(zhí)行猫牡。其他則不一定。動態(tài)廣播注冊后必須注銷邓线,否則將導(dǎo)致內(nèi)存泄漏淌友。不允許重復(fù)注冊或者重復(fù)注銷。
動態(tài)注冊代碼示例
廣播發(fā)送
廣播發(fā)送根據(jù)所發(fā)送的消息類型以及發(fā)送方式分為了以下幾種廣播類型
-
普通廣播
開發(fā)者自定義的intent廣播骇陈,廣播發(fā)送后如果廣播接收中注冊的intentFilter的action與發(fā)送的intent相匹配震庭,則會接收此廣播并回調(diào)onReceive()。
使用:
//發(fā)送動態(tài)廣播
Intent intent=new Intent();
intent.setAction(RECEIVER_ACTION);
sendBroadcast(intent);
注意:如果發(fā)送廣播需要某一權(quán)限接收廣播則也需要相應(yīng)權(quán)限你雌。
-
系統(tǒng)廣播
Android內(nèi)值得多個系統(tǒng)廣播器联,手機上的基本操作都會發(fā)出相應(yīng)的廣播。
如:開關(guān)機婿崭,充電拨拓,電池狀態(tài),解鎖關(guān)閉屏幕等氓栈。
注意:使用系統(tǒng)廣播只需要注冊廣播接收即可渣磷,廣播由系統(tǒng)在相應(yīng)的時刻自動發(fā)送。
-
有序廣播
發(fā)送出去的廣播被廣播接受者按照先后順序接收授瘦。
1醋界、廣播接收順序規(guī)則:
按照Priority
屬性值從大到小排序竟宋。
Priority
屬性相同者,動態(tài)注冊廣播優(yōu)先形纺。
2丘侠、有序廣播特點:
接收廣播按順序接收。
先接受廣播的廣播接收者可以對廣播進行截斷逐样,后續(xù)的廣播接收者將不會再收到此廣播蜗字。
先接受廣播的廣播接收者可以對廣播進行修改,后續(xù)的廣播接收者收到的是被修改過的廣播脂新。
3挪捕、如何發(fā)送有序廣播:
將sendBroadcast(intent)
換成sendOrderdBroadcast(intent);
-
APP應(yīng)用內(nèi)廣播
1戏羽、全局廣播導(dǎo)致的問題
因為Android
中的廣播可以跨App直接通信,所以可能會導(dǎo)致其他App不斷的發(fā)出針對性的廣播導(dǎo)致當(dāng)前App不斷接收廣播并處理楼吃,或者有其他App注冊與當(dāng)前App一致的intent-filter
接收廣播始花,獲取廣播具體信息,導(dǎo)致出現(xiàn)安全性與效率問題孩锡。使用App應(yīng)用內(nèi)廣播可以解決上述問題酷宵。
2、應(yīng)用內(nèi)廣播的含義
應(yīng)用內(nèi)廣播的廣播接收發(fā)送都屬于同一個app躬窜,與全局廣播相比應(yīng)用內(nèi)廣播安全性和效率更高浇垦。
3、應(yīng)用內(nèi)廣播的使用
3.1荣挨、將全局廣播設(shè)置成局部廣播
注冊廣播時將exported
屬性設(shè)置為false
(非本APP內(nèi)部發(fā)出的此廣播不被接收).
廣播發(fā)送和接收時增加permission
權(quán)限男韧,進行權(quán)限驗證。
廣播發(fā)送時通過intent.setPackage(packageName)
指定廣播接收器所在的包名默垄,則該廣播只會發(fā)送到App內(nèi)與之相匹配的廣播接收器中此虑。
3.2、使用封裝好的LocalBrodcastManager
類
注冊/取消廣播接收器和廣播發(fā)送器時將參數(shù)context
變?yōu)?code>LocalBroadcastManager的單一實例口锭。
使用LocalBroadcastManager
方式發(fā)送的應(yīng)用內(nèi)廣播只能通過LocalBroadcastManager
動態(tài)注冊朦前。
//注冊本地廣播
mDynamicRegisterReceiver=new DynamicRegisterReceiver();
mLocalBroadcastManager=LocalBroadcastManager.getInstance(BroadcastReceiverActivity.this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(RECEIVER_ACTION);
mLocalBroadcastManager.registerReceiver(mDynamicRegisterReceiver,intentFilter);
//發(fā)送本地廣播
Intent intent=new Intent();
intent.setAction(RECEIVER_ACTION);
mLocalBroadcastManager.sendBroadcast(intent);
-
粘性廣播
在Android5.0,API21中已失效鹃操。
廣播原理
廣播接收者通過Binder
機制在AMS
注冊韭寸,
廣播發(fā)送者通過Binder
機制向AMS
發(fā)送廣播,
AMS
根據(jù)廣播發(fā)送者要求荆隘,在已注冊列表中恩伺,依據(jù)IntentFilter/Permission
尋找合適的廣播接收者
AMS
將廣播發(fā)送到合適的廣播接收者相應(yīng)的消息循環(huán)隊列中
廣播接受者通過消息隊列拿到廣播,并回調(diào)onReceive()
方法椰拒。
廣播接收和注冊異步執(zhí)行莫其。
注意事項
-
不同注冊方式的廣播接收器回調(diào)
onReceive
中的context
返回值不同
靜態(tài)注冊:
????????返回ReceiverRestrictedContext
癞尚。
全局廣播的動態(tài)注冊:
????????返回Activity Context
。
應(yīng)用內(nèi)廣播LocalBroadcastManager
的動態(tài)注冊:
????????返回Application Context
乱陡。
應(yīng)用內(nèi)廣播非LocalBroadcastManager
的動態(tài)注冊:
????????返回Activity Context
浇揩。 - Andorid 8.0禁止使用大部分廣播的靜態(tài)注冊
ContentProvider
內(nèi)容提供程序管理對結(jié)構(gòu)化數(shù)據(jù)集的訪問。它們封裝數(shù)據(jù)憨颠,并提供用于定義數(shù)據(jù)安全性的機制胳徽。 內(nèi)容提供程序是連接一個進程中的數(shù)據(jù)與另一個進程中運行的代碼的標(biāo)準(zhǔn)界面。
什么是ContentProvider
ContentProvider
內(nèi)容提供者爽彤,在Android
四大組件的日常使用中出現(xiàn)次數(shù)相對較少养盗,但是重要性一點也沒有減少,由官方解釋我們可以知道它是用來統(tǒng)一管理數(shù)據(jù)進行數(shù)據(jù)共享并且可以進行跨進程通信适篙。
-
使用場景
如:我們1使用ContentResolver
在app
中得到手機通訊錄的信息或者通過相應(yīng)的uri
訪問其他應(yīng)用的ContentProvider
提供的信息等往核。
ContentProvider的使用
上面說道ContentProvider
提供統(tǒng)一的數(shù)據(jù)管理和數(shù)據(jù)共享、進行跨進程通信并舉了相關(guān)例子嚷节,接下來詳細說明一下聂儒。
-
統(tǒng)一數(shù)據(jù)管理、數(shù)據(jù)共享
我們平常進行對數(shù)據(jù)操作的時候由于數(shù)據(jù)的不同類型有了許多不同的組織和使用方式硫痰,如對文件的存儲和數(shù)據(jù)庫的使用衩婚,便截然不同,而ContentProvider
相當(dāng)于在這些不同的操作上又進行了一層包裝并以Uri
的方式提供數(shù)據(jù)的訪問接口效斑,使得我們在對數(shù)據(jù)操作時非春,可以忽略底層的差異。統(tǒng)一使用ContentResolver
調(diào)用上層接口進行數(shù)據(jù)操作 -
跨進程通信
我們知道一個APP
就是一個進程缓屠,而不同進程之間通信是比較困難的奇昙,但是ContentProvider
就提供了這種在不同APP
間進行通信的功能,即跨進程通信敌完,也是ContentProvider
被使用最多的方面敬矩。ContentProvider
進行跨進程通信的底層原理使用了Binder
。
接下來說一下具體使用
URI蠢挡、UriMatcher弧岳、MIME
-
URI(Uniform Resource Identifier):統(tǒng)一資源標(biāo)識符
可以用來唯一標(biāo)識ContentProvider和其中的數(shù)據(jù) -
格式
一個uri
由以下部分組成:
主題名 | 授權(quán)信息 | 表名 | 記錄 |
---|---|---|---|
content | com.example.provider | User | 1 |
如:content://com.example.provider/User/1
在uri
中可以使用通配符*
和#
*
:任意長度的有效字符串
#
:任意長度的數(shù)字字符串
-
UriMatcher
UriMatcher
簡單說來是一個對uri
進行管理的類。它主要的方法有:
1业踏、UriMatcher.addURI
在ContentProvider
中注冊uri
//初始化
mUriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
//在ContentProvider中注冊uri
mUriMatcher.addURI(AUTHORITY,"user",user_code);
2禽炬、UriMatcher.match(uri)
根據(jù)uri返回匹配該uri的自定義代碼
- MIME
用于指定某個擴展名的文件用某種應(yīng)用程序打開。
由類型+子類型組成
1勤家、必須以vnd
開頭
2凡辱、內(nèi)容uri
以路徑結(jié)尾即表名結(jié)尾后接android.cursor.dir/
淌山,如果以id
結(jié)尾即數(shù)據(jù)庫表id
結(jié)尾則后接android.cursor.item/
3钉寝、最后接上vnd.<authority>.<path>
磺平,例:
content://com.chenlei.content_provider.user
對應(yīng)MIME
類型為:vnd.android.cursor.dir/vnd.com.chenlei.content_provider.user
content://com.chenlei.content_provider.user/1
對應(yīng)MIME
類型為:vnd.android.cursor.item/vnd.com.chenlei.content_provider.user
ContentProvider
ContentProvider
即是內(nèi)容提供器渺绒,它可以用來統(tǒng)一管理和組織應(yīng)用數(shù)據(jù),向其他應(yīng)用程序提供接口 便于其他應(yīng)用程序?qū)?strong>本應(yīng)用允許的數(shù)據(jù)進行操作,以進行跨進程通訊。
簡單來說就是ContentProvider
向其他APP
提供近尚,自己APP
內(nèi)數(shù)據(jù),的訪問接口
主要方法由以下幾個
-
onCreate
初始化ContentProvider
的使用使用场勤,如果對數(shù)據(jù)庫進行操作則通常完成數(shù)據(jù)庫的創(chuàng)建和升級操作 -
insert戈锻、delete、update和媳、query
對數(shù)據(jù)進行操作的核心方法增刪改查格遭,方法中對應(yīng)方法名來對數(shù)據(jù)進行相應(yīng)的增刪改查操作。 -
getType
用來得到數(shù)據(jù)類型留瞳,即返回當(dāng)前uri
所代表數(shù)據(jù)的MIME
類型
/**
* 初始化ContentProvider的使用使用
* 通常完成數(shù)據(jù)庫的創(chuàng)建和升級操作
* @return true初始化成功拒迅,false初始化失敗
*/
@Override
public boolean onCreate() {
mContext=getContext();
mDBHelper=new DBHelper(getContext());
sqLiteDatabase=mDBHelper.getWritableDatabase();
//初始化數(shù)據(jù)庫表
sqLiteDatabase.execSQL("delete from user");
sqLiteDatabase.execSQL("insert into user values(1,'Carson');");
sqLiteDatabase.execSQL("insert into user values(2,'Kobe');");
sqLiteDatabase.execSQL("delete from job");
sqLiteDatabase.execSQL("insert into job values(1,'Android');");
sqLiteDatabase.execSQL("insert into job values(2,'iOS');");
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
String tableName = getTableName(uri);
sqLiteDatabase=mDBHelper.getReadableDatabase();
Cursor cursor=null;
cursor=sqLiteDatabase.query(tableName,strings,s,strings1,null,null,s1,null);
return cursor;
}
/**
* 根據(jù)傳入的內(nèi)容來返回相應(yīng)的MIME類型
* @param uri
* @return
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
String mime = null;
switch (mUriMatcher.match(uri)){
case user_code:
mime="vnd.android.cursor.dir/vnd.com.chenlei.content_provider.user";
break;
case job_code:
mime="vnd.android.cursor.dir/vnd.com.chenlei.content_provider.job";
break;
}
return mime;
}
/**
* 插入數(shù)據(jù)
* @param uri 數(shù)據(jù)的資源路徑
* @param contentValues 要插入的數(shù)據(jù)內(nèi)容
* @return 返回一個用于記錄新紀錄的uri
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
String tableName=getTableName(uri);
//插入數(shù)據(jù)
sqLiteDatabase.insert(tableName,null,contentValues);
mContext.getContentResolver().notifyChange(uri,null);
return uri;
}
在AndoridManifest.xml
中注冊
當(dāng)代碼完成后還需要在AndoridManifest.xml
中注冊才能被訪問使用。
<!--聲明外界進程可訪問該Provider的權(quán)限(讀 & 寫)-->
<!--android:permission="com.chenlei.PROVIDER"-->
<!--權(quán)限可細分為讀 & 寫的權(quán)限-->
<!--外界需要聲明同樣的讀 & 寫的權(quán)限才可進行相應(yīng)操作她倘,否則會報錯-->
<!--android:readPermisson = "com.chenlei.Read"-->
<!--android:writePermisson = "com.chenlei.Write"-->
<!--設(shè)置此provider是否可以被其他進程使用-->
<!--android:exported="true"-->
<provider
android:authorities="com.chenlei.content_provider"
android:name="com.example.androidprimarycodedemo.four_components.about_content_provider.CreateLocalContentProvider"
android:permission="com.chenlei.PROVIDER"
android:exported="true"
/>
ContentResolver
由于如果應(yīng)用需要對多個ContentProvider
進行操作璧微,需要了解各個不同ContentProvider
的實現(xiàn)等再進行操作。所以API
中提供了ContentResolver
類統(tǒng)一管理對ContentProvider
的操作帝牡。
簡單來說就是通過ContentResolver
往毡,來對其他APP
內(nèi)的數(shù)據(jù)蒙揣,進行操作
ContentResolver
使用示例靶溜。
//user表的資源路徑
private Uri uriUser=Uri.parse("content://com.chenlei.content_provider/user");
/**
* ContentResolver統(tǒng)一管理ContentProvider間的操作
* 由于如果需要使用多個ContentProvider進行操作,需要了解各個不同ContentProvider的實現(xiàn)等再進行操作
* 使用ContentResolver同一管理方便操作和使用懒震。
*/
private ContentResolver contentResolver=null;
/**
* 向ContentProvider中插入數(shù)據(jù)
*/
private void insertData(){
contentResolver=getContentResolver();
//插入表中的數(shù)據(jù)
ContentValues contentValues=new ContentValues();
contentValues.put("_id", 3);
contentValues.put("name", "Iverson");
contentResolver.insert(uriUser,contentValues);
Logger.e("插入完成");
}
/**
* 從ContentProvider中查詢數(shù)據(jù)
*/
private void queryData(){
contentResolver=getContentResolver();
Cursor cursor=contentResolver.query(uriUser,new String[]{"_id","name"}, null, null, null);
while (cursor.moveToNext()){
Logger.e(cursor.getInt(0) +","+ cursor.getString(1));
}
cursor.close();
}
- 在
AndoridManifest.xml
中注冊權(quán)限
<!--應(yīng)用跨進程通訊的權(quán)限-->
<uses-permission android:name="com.chenlei.PROVIDER"/>
ContentProvider
部分知識點參考自https://blog.csdn.net/carson_ho/article/details/76101093