BroadcastReceiver詳解及應用
使用Android手機的時候,我們的手機管家中經常會出現開機自啟動某某app,那么對于這個某某APP來說,他是怎么知道系統什么時候開機的呢?還有,系統短信怎么知道收到了短信?以及屏幕點亮與關閉、應用卸載與安裝等等。
這就講到了Android四大組件之一: BroadcastReceiver ,翻譯是 廣播接收者 。意思就是接收廣播用的。他可以接收到系統開機完成的廣播,以及系統電量不足的廣播,以及系統收到短信的廣播,等等。我們收到廣播后就可以做我們想做的事了。現實中使用廣播時,有發送廣播的電臺,接收廣播的收音機以及廣播傳遞的媒介電磁波。而在Android中的廣播機制與現實中一樣,發送廣播的是 Broadcast ,接收廣播的 BroadcastReceiver 及廣播之間傳遞數據的 Intent 。
注冊BroadcastReceiver接收廣播
繼承BroadcastReceiver這是一個抽象類,
public abstract class BroadcastReceiver {
- 實現抽象方法 public abstract void onReceive(Context context, Intent intent); * 當收到注冊的廣播時,onReceive方法會被調用。
- context是上下文,Intent就是廣播攜帶的數據。
public class MyBroadcastReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String action = intent.getAction();//獲取到收到的廣播的名稱 Log.e("hui", "收到的廣播的Action是:"+action); }}
- context是上下文,Intent就是廣播攜帶的數據。
- 注冊BroadcastReceiver ,作為四大組件之一,當然需要注冊。
BroadcastReceiver有兩種注冊方式:- 靜態注冊(在AndroidManifest.xml清單文件中注冊)
- 動態注冊(在代碼中注冊)
廣播接收者靜態注冊方式
當我們需要一直接收某種廣播時,可以使用靜態注冊方式。
以 監聽手機打電話 為例子。
<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表示要監聽的廣播類型,這里的表示開機完成的廣播。 因為監聽用戶的電話狀態屬于侵犯用戶隱私,所以需要添加 android.permission.PROCESS_OUTGOING_CALLS 權限。
下圖是接收打電話廣播:
接收打電話廣播
實戰 開機自啟動APP : 鏈接
廣播接收者動態注冊方式
當我們不需要一直接收某種廣播時,可以使用動態注冊廣播接收者的方式。
以 監聽屏幕點亮與關閉 為例子。
public class MainActivity extends Activity {
private MyBroadcastReceiver receiver ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
registerMyReceiver();//在activity創建的時候進行注冊監聽
}
private void registerMyReceiver() {
receiver = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter();//創建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接收屏幕點亮與關閉廣播
可以看到,退出APP后,接收打電話廣播任然起作用,但是接收屏幕點亮與關閉的廣播卻沒效果。為什么呢?看下述差異:
實戰 短信驗證碼自動填入 : 鏈接在這
廣播接收者靜態注冊方式與靜態注冊方式差異
- 靜態注冊 靜態注冊依附于清單文件,只要APP啟動過一次,所靜態注冊的廣播就會生效,無論當前的APP處于停止使用還是正在使用狀態。只要相應的廣播事件發生,系統就會遍歷所有的清單文件,通知相應的廣播接收者接收廣播,然后調用廣播接收者的onReceiver方法。
- 動態注冊動態注冊方式依賴于所注冊的組件,當APP關閉后,組件對象都不在了動態注冊的代碼都不存在了,所動態注冊監聽的action自然不在生效。
- 靜態注冊的廣播傳播速度要遠遠慢于動態注冊的廣播。
對廣播接收者同時使用靜態與動態注冊
上面例子中MyBroadcastReceiver使用靜態注冊監聽用戶打電話,使用動態注冊監聽用戶屏幕點亮與關閉。所以,監聽到屏幕的開關只有在APP運行的狀態才可以,但是監聽打電話的狀態無論此時app是否在運行,都可以監聽到。
需要注意:動態注冊的廣播的優先級大于靜態注冊的廣播。至于這個是為什么呢?額(⊙o⊙)…谷歌寫的源代碼的時候先對動態廣播進行處理然后在對靜態廣播進行處理。后面我們了解到廣播的優先級后會實例證明的。
BroadcastReceiver分類
廣播的發送,可以分為 有序廣播 、 無序廣播 、 本地廣播 以及 sticky廣播 。
有序廣播
有序廣播是一種分先后廣播接收器的廣播,廣播接收者的優先級越高,越先接收廣播。優先級高的廣播先收到廣播,收到廣播后可以修改廣播的內容,也可以攔截廣播不讓廣播向下傳遞。就像皇上通知知府每人賞金100兩,知府通知知縣每人賞金100兩,最后才是農民知道了賞金的事,一旦知府或者知縣不告訴下級賞金的的事,那么農民就不知道賞金的事了,這就是有序廣播的攔截廣播;當然知府或者知縣也可以向下級通知只有賞金10兩的事,這就是有序廣播的修改廣播內容。
無序廣播
無序廣播指所有與之匹配的廣播接收者都能收到廣播,沒有先后順序,直到沒有廣播接收者接收廣播為止才會停止廣播的傳遞。就像皇上貼告示,昭告天下每人賞金100兩銀子一樣,那么所有的農民都知道了這件事,沒有先后之分,當農民直到了錢的事之后這件事就算了結了。
前文講過,有廣播發送時,系統會遍歷全部APP的receiver。如果想使得本APP的廣播不被外界的廣播所干擾,可以在receiver節點添加android:exported="false"屬性 ,這樣系統遍歷全部APP清單文件的廣播接收者時不會對本receiver進行判斷及處理。
這個值為FALSE表示不予其他APP相交互。
本地廣播
與有序和無序廣播的全局廣播(任何一方發出廣播本手機的任何一個程序都能收到對應的廣播)相比,本地廣播是局部的廣播基于本程序的廣播,其他的程序無法收到這個廣播。本地廣播就類似當地的知縣單獨給農民發一兩銀子,只有當地人才知道,其他的人不知道。這個廣播是API 21的V4包中新增的,用來保證廣播是獨家私有的。這種廣播是安全的,外界不會干擾他,廣播也不會被其他進程所收到。
sticky廣播
sticky粘性的意思。這種廣播一般不會終止,只要有符合條件的廣播接收者能接收廣播,那么就會發送給他廣播。永遠不會終止發送廣播,除非某個廣播接收者告訴它不要再發送廣播了。
發送自定義廣播
實例演練:創建兩個廣播接收者:ZhiFuReceiver/ZhiXianReceiver
創建:
public class ZhiXianReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("hui", "ZhiXianReceiver = " + intent.getStringExtra("qian"));//取出廣播中攜帶的數據,因為我存數據的時候是intent.putExtra("qian", "100");存入的。遵循如何存如何取得原則取數據
}}
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>
發送無序廣播
public void sendCustomBroadcast(View view){
Intent intent = new Intent("my.broadcast.faqian");//action是my.broadcast.faqian,清單文件中的action與之一致方可收到廣播
intent.putExtra("qian", "100");//廣播中攜帶的數據
sendBroadcast(intent);//發送無序廣播
}
這里寫圖片描述
雖然這里打印順序有先后但是這個先后順序是無意義的,總體來看還是無序的。
發送有序廣播
發送方式一:
public void sendCustomBroadcast(View view){
Intent intent = new Intent("my.broadcast.faqian");//action是my.broadcast.faqian,清單文件中的action與之一致方可收到廣播
intent.putExtra("qian", "100");//廣播中攜帶的數據
/** * sendOrderedBroadcast(Intent intent, String receiverPermission); */ sendOrderedBroadcast(intent, null);//發送有序廣播
}
清單文件配置
<receiver android:name=".ZhiFuReceiver">
<intent-filter android:priority="100">//設置優先級,為整數,越大優先級越高
<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優先級大于ZhiFuReceiver優先級,故ZhiXianReceiver先收到廣播。
發送方式二:
sendOrderedBroadcast的另一個重載方法如下。
sendOrderedBroadcast(
Intent intent,//封裝了action及其他數據
String receiverPermission, //廣播接收者需要的權限
BroadcastReceiver resultReceiver,//
Handler scheduler,
int initialCode,
String initialData,
Bundle initialExtras);
參數解釋:
- intent 封裝了action及其他數據
- receiverPermission, //廣播接收者需要的權限
- resultReceiver 有序廣播是支持攔截的,一旦被攔截可以修改廣播中數據甚至直接終止廣播,這個resultReceiver表示無論當廣播傳播結束以后我任然會受到廣播。(下面會有栗子演示)
- initialCode 發送廣播的時候默認攜帶的數據
- initialData 發送廣播的時候默認攜帶的數據
- initialExtras 發送廣播的時候默認攜帶的數據 實例:將上面例子中的發送廣播的方法修改如下
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數據
sendOrderedBroadcast(
intent,
null, //permission為null
new ZhiFuReceiver(), //這里的new ZhiFuReceiver()為最終的廣播接收者,也就是說無論他曾經有沒有收到廣播都會再次收到廣播。
null,
666,//initCode
"我是initialData",//initData
bundle);//bundle //以上所有入參都會攜帶在廣播中,如何取出呢?
}
接收廣播
public class ZhiXianReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/***********獲取數據*************/
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) {
/***********獲取數據*************/
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數據
有一點需要說明,這里ZhiFuReceiver 收到了兩次數據。為什么呢?ZhiXianReceiver 得優先級大于ZhiFuReceiver ,同時ZhiXianReceiver 未攔截廣播,所以會先ZhiXianReceiver 一次后ZhiFuReceiver 一次,而發送廣播的時候聲明了ZhiFuReceiver 為最終接受者,所以無論他曾經有沒有收到廣播都會再次收到廣播。
圖示:
理解ResultBroadcastReceiver
下面我們看看攔截后會有什么效果。
有序廣播的攔截與修改數據
攔截廣播將上面例子中的ZhiXianReceiver 添加一行攔截廣播的代碼,看看結果。
public class ZhiXianReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/***********獲取數據*************/
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();//攔截廣播,廣播被終止,以后不會有其他廣播接收者再收到廣播了。
}}
攔截廣播
這里abortBroadcast()攔截了有序廣播,不是說每人能再收到廣播了么?為什么ZhiFuReceiver 還能收到廣播呢?這是因為ZhiFuReceiver 是廣播的最終接受者,廣播從優先級高的廣播接收者優先接收,一層一層向優先級較低的傳送。當廣播被攔截后,廣播部分的層層發送這里鏈路發送完畢,但是有最終廣播接收者,故最終廣播接收者會收到最后的廣播。故ZhiFuReceiver 會收到廣播。
下圖理解:
理解攔截廣播以及ResultBroadcastReceiver
修改廣播中內容
public class ZhiXianReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/***********獲取數據*************/
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"));
/**************修改數據****************/
setResultCode(8989);//修改initCode
setResultData("ZhiXianReceiver修改了數據"); //修改initData
//修改bundle數據
Bundle bundle = new Bundle();
bundle.putString("qian", "10");
setResultExtras(bundle); }}
修改廣播中數據
上面例子中我把幾個廣播接收者都寫在一個APP中了,如果把每個廣播接收者分別放在不同的app中一樣都能收到廣播(如果廣播不被攔截)。如果我只想發送的廣播給我自己APP種的廣播接收到,可以使用本地廣播,這種廣播是安全的,外界不會干擾他,廣播也不會被其他進程所收到。
發送本地廣播
本地廣播的使用是寫在代碼中的,因為本地廣播發送廣播時是直接在代碼中注冊的廣播中進行匹配從而調用其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++) {
//在這里直接調用其onReceiver方法了
br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
}
}
}
}
使用localBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter)注冊:
/** * 本地廣播接收者進行注冊,必須在代碼中注冊,清單文件注冊是無效的 */
public void registerMyAPPReceiver(View view) { //創建廣播接收者
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); }
注意:
- registerReceiver注冊一個廣播接收者可以多次執行,比如:我把‘ocalBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter);’寫兩遍,那么myBroadCastReceiver的onReceiver會被調用兩次,不建議這樣寫。
- 本地廣播不能攔截
- registerReceiver對應的還有unregisterReceiver(receiver)
/** * 發送本地廣播 * @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數據
intent.putExtra("bundle_data", bundle); //使用LocalBroadcastManager發送廣播
LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcastSync(intent);//發送
}
發送sticky廣播
添加權限: <user-permission android:name="android.permission.BROADCAST_STICKY"/>
發送 context.sendStickyBroadcast()
停止使用 context.removeStickyBroadcast()
如有錯誤,不吝賜教啊
累死了,北京時間:2016.12.5 凌晨 0:27
來自:http://www.jianshu.com/p/989e7c2f9293