使用Android手機的時候,我們的手機管家中經(jīng)常會出現(xiàn)開機自啟動某某app迁央,那么對于這個某某APP來說掷匠,他是怎么知道系統(tǒng)什么時候開機的呢?還有岖圈,系統(tǒng)短信怎么知道收到了短信讹语?以及屏幕點亮與關閉、應用卸載與安裝等等蜂科。
這就講到了Android四大組件之一:BroadcastReceiver顽决,翻譯是廣播接收者。意思就是接收廣播用的导匣。他可以接收到系統(tǒng)開機完成的廣播才菠,以及系統(tǒng)電量不足的廣播,以及系統(tǒng)收到短信的廣播贡定,等等赋访。我們收到廣播后就可以做我們想做的事了。現(xiàn)實中使用廣播時缓待,有發(fā)送廣播的電臺蚓耽,接收廣播的收音機以及廣播傳遞的媒介電磁波。而在Android中的廣播機制與現(xiàn)實中一樣旋炒,發(fā)送廣播的是Broadcast步悠,接收廣播的BroadcastReceiver及廣播之間傳遞數(shù)據(jù)的Intent。
注冊BroadcastReceiver接收廣播
** 繼承BroadcastReceiver**這是一個抽象類瘫镇,
public abstract class BroadcastReceiver {
-
實現(xiàn)抽象方法
public abstract void onReceive(Context context, Intent intent);
* 當收到注冊的廣播時鼎兽,onReceive方法會被調(diào)用答姥。 - context是上下文,Intent就是廣播攜帶的數(shù)據(jù)谚咬。
public class MyBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();//獲取到收到的廣播的名稱
Log.e("hui", "收到的廣播的Action是:"+action); }}
-
注冊BroadcastReceiver鹦付,作為四大組件之一,當然需要注冊序宦。
BroadcastReceiver有兩種注冊方式: - 靜態(tài)注冊(在AndroidManifest.xml清單文件中注冊)
- 動態(tài)注冊(在代碼中注冊)
廣播接收者靜態(tài)注冊方式
當我們需要一直接收某種廣播時睁壁,可以使用靜態(tài)注冊方式。
以監(jiān)聽手機打電話為例子互捌。
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
上面的receiver 表示這個MyBroadcastReceiver是廣播接收者。action表示要監(jiān)聽的廣播類型行剂,這里的表示開機完成的廣播秕噪。 因為監(jiān)聽用戶的電話狀態(tài)屬于侵犯用戶隱私,所以需要添加android.permission.PROCESS_OUTGOING_CALLS
權限厚宰。
下圖是接收打電話廣播:
實戰(zhàn)開機自啟動APP:鏈接
廣播接收者動態(tài)注冊方式
當我們不需要一直接收某種廣播時腌巾,可以使用動態(tài)注冊廣播接收者的方式。
以監(jiān)聽屏幕點亮與關閉為例子铲觉。
public class MainActivity extends Activity {
private MyBroadcastReceiver receiver ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
registerMyReceiver();//在activity創(chuàng)建的時候進行注冊監(jiān)聽
}
private void registerMyReceiver() {
receiver = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter();//創(chuàng)建IntentFilter對象
filter.addAction(Intent.ACTION_SCREEN_OFF);//IntentFilter對象中添加要接收的關屏廣播
filter.addAction(Intent.ACTION_SCREEN_ON);//添加點亮屏幕廣播
registerReceiver(receiver, filter);
}
private void unRegisterMyReceiver(){
if(receiver != null){
unregisterReceiver(receiver);//反注冊廣播澈蝙,也就是注銷廣播接收者,使其不起作用
}
}
}
下圖是接收屏幕點亮與關閉廣播:
下圖是退出APP的狀況:
可以看到撵幽,退出APP后灯荧,接收打電話廣播任然起作用,但是接收屏幕點亮與關閉的廣播卻沒效果盐杂。為什么呢逗载?看下述差異:
實戰(zhàn)短信驗證碼自動填入:鏈接在這
廣播接收者靜態(tài)注冊方式與靜態(tài)注冊方式差異
- 靜態(tài)注冊 靜態(tài)注冊依附于清單文件,只要APP啟動過一次链烈,所靜態(tài)注冊的廣播就會生效厉斟,無論當前的APP處于停止使用還是正在使用狀態(tài)。只要相應的廣播事件發(fā)生强衡,系統(tǒng)就會遍歷所有的清單文件擦秽,通知相應的廣播接收者接收廣播,然后調(diào)用廣播接收者的onReceiver方法漩勤。
- 動態(tài)注冊動態(tài)注冊方式依賴于所注冊的組件感挥,當APP關閉后,組件對象都不在了動態(tài)注冊的代碼都不存在了锯七,所動態(tài)注冊監(jiān)聽的action自然不在生效链快。
- 靜態(tài)注冊的廣播傳播速度要遠遠慢于動態(tài)注冊的廣播。
對廣播接收者同時使用靜態(tài)與動態(tài)注冊
上面例子中MyBroadcastReceiver使用靜態(tài)注冊監(jiān)聽用戶打電話眉尸,使用動態(tài)注冊監(jiān)聽用戶屏幕點亮與關閉域蜗。所以巨双,監(jiān)聽到屏幕的開關只有在APP運行的狀態(tài)才可以,但是監(jiān)聽打電話的狀態(tài)無論此時app是否在運行霉祸,都可以監(jiān)聽到筑累。
需要注意:動態(tài)注冊的廣播的優(yōu)先級大于靜態(tài)注冊的廣播。至于這個是為什么呢丝蹭?額(⊙o⊙)…谷歌寫的源代碼的時候先對動態(tài)廣播進行處理然后在對靜態(tài)廣播進行處理慢宗。后面我們了解到廣播的優(yōu)先級后會實例證明的。
BroadcastReceiver分類
廣播的發(fā)送奔穿,可以分為有序廣播镜沽、無序廣播、本地廣播以及sticky廣播贱田。
有序廣播
有序廣播 是一種分先后廣播接收器的廣播缅茉,廣播接收者的優(yōu)先級越高,越先接收廣播男摧。優(yōu)先級高的廣播先收到廣播蔬墩,收到廣播后可以修改廣播的內(nèi)容,也可以攔截廣播不讓廣播向下傳遞耗拓。就像皇上通知知府每人賞金100兩拇颅,知府通知知縣每人賞金100兩,最后才是農(nóng)民知道了賞金的事乔询,一旦知府或者知縣不告訴下級賞金的的事樟插,那么農(nóng)民就不知道賞金的事了,這就是有序廣播的攔截廣播哥谷;當然知府或者知縣也可以向下級通知只有賞金10兩的事岸夯,這就是有序廣播的修改廣播內(nèi)容。
無序廣播
無序廣播 指所有與之匹配的廣播接收者都能收到廣播们妥,沒有先后順序猜扮,直到?jīng)]有廣播接收者接收廣播為止才會停止廣播的傳遞。就像皇上貼告示监婶,昭告天下每人賞金100兩銀子一樣旅赢,那么所有的農(nóng)民都知道了這件事,沒有先后之分惑惶,當農(nóng)民直到了錢的事之后這件事就算了結了煮盼。
前文講過,有廣播發(fā)送時带污,系統(tǒng)會遍歷全部APP的receiver僵控。如果想使得本APP的廣播不被外界的廣播所干擾,可以在receiver節(jié)點添加android:exported="false"屬性 鱼冀,這樣系統(tǒng)遍歷全部APP清單文件的廣播接收者時不會對本receiver進行判斷及處理报破。
這個值為FALSE表示不予其他APP相交互悠就。
本地廣播
與有序和無序廣播的全局廣播(任何一方發(fā)出廣播本手機的任何一個程序都能收到對應的廣播)相比,本地廣播是局部的廣播基于本程序的廣播充易,其他的程序無法收到這個廣播梗脾。本地廣播就類似當?shù)氐闹h單獨給農(nóng)民發(fā)一兩銀子,只有當?shù)厝瞬胖理镅ィ渌娜瞬恢勒搿_@個廣播是API 21的V4包中新增的,用來保證廣播是獨家私有的稿静。這種廣播是安全的梭冠,外界不會干擾他,廣播也不會被其他進程所收到自赔。
sticky廣播
sticky粘性的意思妈嘹。這種廣播一般不會終止,只要有符合條件的廣播接收者能接收廣播绍妨,那么就會發(fā)送給他廣播。永遠不會終止發(fā)送廣播柬脸,除非某個廣播接收者告訴它不要再發(fā)送廣播了他去。
發(fā)送自定義廣播
實例演練:創(chuàng)建兩個廣播接收者:ZhiFuReceiver/ZhiXianReceiver
創(chuàng)建:
public class ZhiXianReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("hui", "ZhiXianReceiver = " + intent.getStringExtra("qian"));//取出廣播中攜帶的數(shù)據(jù),因為我存數(shù)據(jù)的時候是intent.putExtra("qian", "100");存入的倒堕。遵循如何存如何取得原則取數(shù)據(jù)
}}
public class ZhiFuReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("hui", "ZhiFuReceiver = " + intent.getStringExtra("qian"));
}}
清單文件如下配置:
<receiver android:name=".ZhiFuReceiver">
<intent-filter >
<action android:name="my.broadcast.faqian"/>//自定義的廣播接收者接收的廣播名稱
</intent-filter>
</receiver>
<receiver android:name=".ZhiXianReceiver">
<intent-filter >
<action android:name="my.broadcast.faqian"/>
</intent-filter>
</receiver>
發(fā)送無序廣播
public void sendCustomBroadcast(View view){
Intent intent = new Intent("my.broadcast.faqian");//action是my.broadcast.faqian灾测,清單文件中的action與之一致方可收到廣播
intent.putExtra("qian", "100");//廣播中攜帶的數(shù)據(jù)
sendBroadcast(intent);//發(fā)送無序廣播
}
雖然這里打印順序有先后但是這個先后順序是無意義的,總體來看還是無序的。
發(fā)送有序廣播
發(fā)送方式一:
public void sendCustomBroadcast(View view){
Intent intent = new Intent("my.broadcast.faqian");//action是my.broadcast.faqian垦巴,清單文件中的action與之一致方可收到廣播
intent.putExtra("qian", "100");//廣播中攜帶的數(shù)據(jù)
/** * sendOrderedBroadcast(Intent intent, String receiverPermission); */ sendOrderedBroadcast(intent, null);//發(fā)送有序廣播
}
清單文件配置
<receiver android:name=".ZhiFuReceiver">
<intent-filter android:priority="100">//設置優(yōu)先級媳搪,為整數(shù),越大優(yōu)先級越高
<action android:name="my.broadcast.faqian"/>
</intent-filter>
</receiver>
<receiver android:name=".ZhiXianReceiver">
<intent-filter android:priority="200" >
<action android:name="my.broadcast.faqian"/>
</intent-filter>
</receiver>
ZhiXianReceiver優(yōu)先級大于ZhiFuReceiver優(yōu)先級骤宣,故ZhiXianReceiver先收到廣播秦爆。
發(fā)送方式二:
sendOrderedBroadcast的另一個重載方法如下。
sendOrderedBroadcast(
Intent intent,//封裝了action及其他數(shù)據(jù)
String receiverPermission, //廣播接收者需要的權限
BroadcastReceiver resultReceiver,//
Handler scheduler,
int initialCode,
String initialData,
Bundle initialExtras);
參數(shù)解釋:
- intent 封裝了action及其他數(shù)據(jù)
- receiverPermission, //廣播接收者需要的權限
- resultReceiver 有序廣播是支持攔截的憔披,一旦被攔截可以修改廣播中數(shù)據(jù)甚至直接終止廣播等限,這個resultReceiver表示無論當廣播傳播結束以后我任然會受到廣播。(下面會有栗子演示)
- initialCode 發(fā)送廣播的時候默認攜帶的數(shù)據(jù)
- initialData 發(fā)送廣播的時候默認攜帶的數(shù)據(jù)
- initialExtras 發(fā)送廣播的時候默認攜帶的數(shù)據(jù) 實例:將上面例子中的發(fā)送廣播的方法修改如下
public void sendCustomBroadcast(View view){
Intent intent = new Intent("my.broadcast.faqian");//action是my.broadcast.faqian芬膝,清單文件中的action與之一致方可收到廣播
Bundle bundle = new Bundle();
bundle.putString("qian","100");//廣播中攜帶的bundle數(shù)據(jù)
sendOrderedBroadcast(
intent,
null, //permission為null
new ZhiFuReceiver(), //這里的new ZhiFuReceiver()為最終的廣播接收者望门,也就是說無論他曾經(jīng)有沒有收到廣播都會再次收到廣播。
null,
666,//initCode
"我是initialData",//initData
bundle);//bundle //以上所有入?yún)⒍紩y帶在廣播中锰霜,如何取出呢筹误?
}
接收廣播
public class ZhiXianReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/***********獲取數(shù)據(jù)*************/
int initCode = getResultCode();//獲取傳遞過來的initCode
String initData = getResultData();//獲取傳遞過來的initData
Bundle initBundle = getResultExtras(true);//獲取傳遞過來的Bundle
Log.d("hui", "ZhiXianReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian"));
}}
public class ZhiFuReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/***********獲取數(shù)據(jù)*************/
int initCode = getResultCode();//獲取傳遞過來的initCode
String initData = getResultData();//獲取傳遞過來的initData
Bundle initBundle = getResultExtras(true);//獲取傳遞過來的Bundle
Log.d("hui", "ZhiFuReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian"));
}}
結果:
ZhiXianReceiver = initCode = 666 ,initdata = 我是initialData ,bundle = 100ZhiFuReceiver = initCode = 666 ,initdata = 我是initialData ,bundle = 100ZhiFuReceiver = initCode = 666 ,initdata = 我是initialData ,bundle = 100```
![拿到BroadcastReceiver數(shù)據(jù)](http://upload-images.jianshu.io/upload_images/3884536-9bdd11eff6c9d742?imageMogr2/auto-orient/strip)
有一點需要說明,這里ZhiFuReceiver 收到了兩次數(shù)據(jù)癣缅。為什么呢厨剪?ZhiXianReceiver 得優(yōu)先級大于ZhiFuReceiver 哄酝,同時ZhiXianReceiver 未攔截廣播,所以會先ZhiXianReceiver 一次后ZhiFuReceiver 一次丽惶,而發(fā)送廣播的時候聲明了ZhiFuReceiver 為最終接受者炫七,所以無論他曾經(jīng)有沒有收到廣播都會再次收到廣播。
圖示:![理解ResultBroadcastReceiver](http://upload-images.jianshu.io/upload_images/3884536-eae36c1f24b09cfb?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
下面我們看看攔截后會有什么效果钾唬。
###有序廣播的攔截與修改數(shù)據(jù)####
攔截廣播將上面例子中的ZhiXianReceiver 添加一行攔截廣播的代碼万哪,看看結果。
public class ZhiXianReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/***********獲取數(shù)據(jù)*************/
int initCode = getResultCode();//獲取傳遞過來的initCode
String initData = getResultData();//獲取傳遞過來的initData
Bundle initBundle = getResultExtras(true);//獲取傳遞過來的Bundle
Log.d("hui", "ZhiXianReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian"));
abortBroadcast();//攔截廣播抡秆,廣播被終止奕巍,以后不會有其他廣播接收者再收到廣播了。
}}
![攔截廣播](http://upload-images.jianshu.io/upload_images/3884536-e28501409c684933?imageMogr2/auto-orient/strip)
這里abortBroadcast()攔截了有序廣播儒士,不是說每人能再收到廣播了么的止?為什么ZhiFuReceiver 還能收到廣播呢?這是因為ZhiFuReceiver 是廣播的最終接受者着撩,廣播從優(yōu)先級高的廣播接收者優(yōu)先接收诅福,一層一層向優(yōu)先級較低的傳送。當廣播被攔截后拖叙,廣播部分的層層發(fā)送這里鏈路發(fā)送完畢氓润,但是有最終廣播接收者,故最終廣播接收者會收到最后的廣播薯鳍。故ZhiFuReceiver 會收到廣播咖气。
下圖理解:![理解攔截廣播以及ResultBroadcastReceiver](http://upload-images.jianshu.io/upload_images/3884536-90a65e07f0376065?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####修改廣播中內(nèi)容
public class ZhiXianReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/***********獲取數(shù)據(jù)*************/
int initCode = getResultCode();//獲取傳遞過來的initCode
String initData = getResultData();//獲取傳遞過來的initData
Bundle initBundle = getResultExtras(true);//獲取傳遞過來的Bundle
Log.d("hui", "ZhiXianReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian"));
/**************修改數(shù)據(jù)****************/
setResultCode(8989);//修改initCode
setResultData("ZhiXianReceiver修改了數(shù)據(jù)"); //修改initData
//修改bundle數(shù)據(jù)
Bundle bundle = new Bundle();
bundle.putString("qian", "10");
setResultExtras(bundle); }}
![修改廣播中數(shù)據(jù)](http://upload-images.jianshu.io/upload_images/3884536-93091fde838c6675?imageMogr2/auto-orient/strip)
上面例子中我把幾個廣播接收者都寫在一個APP中了,如果把每個廣播接收者分別放在不同的app中一樣都能收到廣播(如果廣播不被攔截)挖滤。如果我只想發(fā)送的廣播給我自己APP種的廣播接收到崩溪,可以使用本地廣播,這種廣播是安全的斩松,外界不會干擾他伶唯,廣播也不會被其他進程所收到。
###發(fā)送本地廣播
本地廣播的使用是寫在代碼中的砸民,因為本地廣播發(fā)送廣播時是直接在代碼中注冊的廣播中進行匹配從而調(diào)用其onReceiver的抵怎。
簡單看下源碼:
public void sendBroadcastSync(Intent intent) {
if (sendBroadcast(intent)) {
executePendingBroadcasts();
} }
private void executePendingBroadcasts() {
while (true) {
BroadcastRecord[] brs = null;
synchronized (mReceivers) {
final int N = mPendingBroadcasts.size();
if (N <= 0) {
return;
}
brs = new BroadcastRecord[N];
mPendingBroadcasts.toArray(brs);
mPendingBroadcasts.clear();
}
for (int i=0; i<brs.length; i++) {
BroadcastRecord br = brs[i];
for (int j=0; j<br.receivers.size(); j++) {
//在這里直接調(diào)用其onReceiver方法了
br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
}
}
}
}
使用localBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter)注冊:
/** * 本地廣播接收者進行注冊,必須在代碼中注冊岭参,清單文件注冊是無效的 */
public void registerMyAPPReceiver(View view) { //創(chuàng)建廣播接收者
MyBroadCastReceiver myBroadCastReceiver = new MyBroadCastReceiver();
MyBroadcastReceiver2 myBroadCastReceiver2 = new MyBroadcastReceiver2(); //封裝要接收的廣播類型
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("my.broadcast.faqian2"); //拿到LocalBroadcastManager對象反惕,對固定的Receiver進行注冊,成為本地廣播接收者
LocalBroadcastManager localBroadcastManager =LocalBroadcastManager.getInstance(MainActivity.this);
localBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter);
localBroadcastManager.registerReceiver(myBroadCastReceiver2, intentFilter); }
>注意:
>1. registerReceiver注冊一個廣播接收者可以多次執(zhí)行演侯,比如:我把‘ocalBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter);’寫兩遍姿染,那么myBroadCastReceiver的onReceiver會被調(diào)用兩次,不建議這樣寫。
>2. 本地廣播不能攔截
>3. registerReceiver對應的還有unregisterReceiver(receiver)
/** * 發(fā)送本地廣播 * @param view */
public void sendMyAPPBroadcat(View view){
Intent intent = new Intent("my.broadcast.faqian2");//action是my.broadcast.faqian悬赏,清單文件中的action與之一致方可收到廣播
Bundle bundle = new Bundle();
bundle.putString("qian","100");//廣播中攜帶的bundle數(shù)據(jù)
intent.putExtra("bundle_data", bundle); //使用LocalBroadcastManager發(fā)送廣播
LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcastSync(intent);//發(fā)送
}
### 發(fā)送sticky廣播
添加權限:```<user-permission android:name="android.permission.BROADCAST_STICKY"/>```
發(fā)送```context.sendStickyBroadcast()```
停止使用```context.removeStickyBroadcast()```
如有錯誤狡汉,不吝賜教啊
累死了,北京時間:2016.12.5 凌晨 0:27