參考書籍:《第一行代碼》 第二版 郭霖
如有錯漏癌幕,請批評指出!
廣播機制簡介
Android中的廣播主要分兩種類型:標準廣播和有序廣播
-
標準廣播:一種完全異步執(zhí)行的廣播褪迟,在廣播發(fā)出之后美浦,所有的廣播接收器幾乎都會在同一時刻接收到這條廣播信息,沒有先后順序可言戚哎。這種廣播效率比較高裸诽,但是無法截斷。
-
有序廣播:一種同步執(zhí)行的廣播型凳,在廣播發(fā)出之后丈冬,同一時刻只會有一個廣播接收器能夠收到這條廣播消息,當這個廣播接收器中的邏輯執(zhí)行完畢后廣播才會繼續(xù)傳遞甘畅。此時廣播接收器有先后順序埂蕊,優(yōu)先級高的廣播接收器可以先收到廣播消息,并且前面的廣播接收器可以截斷廣播疏唾。
接收系統(tǒng)廣播
Android內(nèi)置了很多系統(tǒng)級別的廣播蓄氧,我們可以在應用程序中通過監(jiān)聽這些廣播來得到各種系統(tǒng)的狀態(tài)信息。使用廣播接收器可以自由地對需要的廣播進行注冊荸实,當有相應廣播發(fā)出時匀们,廣播接收器就能夠收到該廣播,并對其進行邏輯處理准给。
注冊廣播的方式一般有兩種:一是動態(tài)注冊泄朴,即在代碼中注冊重抖;二是靜態(tài)注冊,即在AndroidManifest文件中注冊祖灰。
創(chuàng)建廣播接收器的方法是新建一個類钟沛,繼承BroadcastReceiver類,并重寫onReceive()方法局扶,當有廣播到來時恨统,onReceive()方法會被調(diào)用,我們可以在onReceive()方法中添加具體邏輯三妈。
-
動態(tài)注冊監(jiān)聽網(wǎng)絡變化
下面我們使用動態(tài)注冊的方式來完成一個監(jiān)聽網(wǎng)絡變化的demo:
首先畜埋,在AndroidManifest文件中聲明系統(tǒng)網(wǎng)絡狀態(tài)的權限,因為我們要監(jiān)聽系統(tǒng)網(wǎng)絡狀態(tài)的變化畴蒲,就必須擁有這個權限:<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
接下來修改BroadcastActivity中的代碼:
public class BroadcastActivity extends AppCompatActivity { private NetworkChangeReceiver networkChangeReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initBroadCast(); } private void initBroadCast() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); networkChangeReceiver = new NetworkChangeReceiver(); registerReceiver(networkChangeReceiver, intentFilter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(networkChangeReceiver); } class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ConnectivityManager manager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = manager.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) { ToastUtil.showShortToast(context, "Network is connected"); }else { ToastUtil.showShortToast(context, "Network not connected"); } } } }
- 第一步悠鞍,創(chuàng)建一個內(nèi)部類NetworkChangeReceiver,繼承BroadcastReceiver類模燥,重寫它的onReceive()方法咖祭。在onReceive()方法中通過getSystemService()方法得到一個ConnectivityManager的實例,這是一個系統(tǒng)服務類蔫骂,專門用于管理網(wǎng)絡連接的么翰。然后調(diào)用它的getActiveNetworkInfo()方法可以得到NetworkInfo的實例,接著調(diào)用NetworkInfo的isConnected()方法辽旋,就可以判斷出當前是否有網(wǎng)絡了浩嫌。當然,想要onReceive()方法被觸發(fā)戴已,我們需要對網(wǎng)絡狀態(tài)變化時發(fā)出的系統(tǒng)廣播進行注冊固该。
- 第二步,因為系統(tǒng)網(wǎng)絡狀態(tài)發(fā)生變化時糖儡,會發(fā)出一條值為
android.net.conn.CONNECTIVITY_CHANGE 的廣播伐坏,所以我們創(chuàng)建一個IntentFilter實例,并給它添加值為 android.net.conn.CONNECTIVITY_CHANGE 的action(也就是說握联,我們想要監(jiān)聽什么廣播桦沉,就添加其對應的action)。 - 第三步金闽,創(chuàng)建一個NetworkChangeReceiver的實例纯露,然后調(diào)用 registerReceiver() 方法進行注冊,將NetworkChangeReceiver的實例和IntentFilter的實例傳進去代芜,這樣就完成了注冊埠褪,也就是說,當系統(tǒng)發(fā)出值為android.net.conn.CONNECTIVITY_CHANGE的廣播時,我們的onReceive()方法就會被觸發(fā)钞速。
-
第四步贷掖,重寫onDestroy()方法,調(diào)用unregisterReceiver()方法取消注冊渴语。這里要注意苹威,我們動態(tài)注冊的廣播都需要取消注冊。下面看效果(打開我們的Demo驾凶,然后切到網(wǎng)絡設置的頁面牙甫,注意,不要按back鍵退出demo调违,不然BroadcastActivity會被銷毀):
-
靜態(tài)注冊實現(xiàn)開機啟動
動態(tài)注冊的廣播接收器可以自由地控制注冊與注銷窟哺,在靈活性上有很大的優(yōu)勢,但是它也有自己的局限性技肩,即必須在程序啟動后才能接收到廣播脏答,因為注冊邏輯是寫在onCreate()方法中的。這時候亩鬼,靜態(tài)注冊的方式就有用武之地了。接下來阿蝶,我們通過靜態(tài)注冊的方式來接收一條開機廣播雳锋。- 第一步,新建一個BootCompleteReceiver類羡洁,繼承BroadcastReceiver類玷过,并重寫其onReceive()方法,使用Toast彈出一條消息:
public class BootCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ToastUtil.showShortToast(context, "Boot Completed"); } }
- 第二步筑煮,在AndroidManifest文件中注冊廣播:
<receiver android:name=".broadcast.BootCompleteReceiver" android:enabled="true" android:exported="true" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver>
通過android:name屬性指定我們創(chuàng)建的廣播接收器(完整路徑)辛蚊;android:enabled
屬性定義系統(tǒng)是否能夠?qū)嵗@個廣播接收器,為true時這個廣播接收器才能被啟用真仲,默認為true袋马;android:exported屬性用于指示該廣播接收器是否能夠接收來自應用程序外部的消息。然后在<intent-filter>標簽中添加系統(tǒng)開機廣播對應的action秸应,這和動態(tài)注冊創(chuàng)建Intentfilter實例并添加action的作用是一樣的虑凛。- 第三步,由于我們需要監(jiān)聽系統(tǒng)開機廣播软啼,因此也需要為其聲明權限桑谍,在AndroidManifest文件中聲明權限:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
這樣,我們的靜態(tài)注冊廣播就完成了祸挪,具體結果自行驗證锣披。
注意:在onReceive()方法中不能進行耗時操作,因為廣播接收器中不允許開啟線程,若onReceive()方法運行較長時間雹仿,程序就會報錯增热。
自定義廣播
前面是關于如何使用廣播接收器來接收系統(tǒng)廣播,接下來我們來看看如何發(fā)送自定義廣播盅粪。
-
發(fā)送標準廣播
首先來新建一個DiyBroadcastReceiver類钓葫,繼承BroadcastReceiver類,重寫onReceive()方法:public class DiyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ToastUtil.showShortToast(context, "received in my broadcast receiver"); abortBroadcast(); } }
然后在AndroidManifest文件中對這個廣播接收器進行注冊:
<receiver android:name=".broadcast.broadcast.DiyBroadcastReceiver" android:enabled="true" android:exported="true"> <intent-filter android:priority="13"> <action android:name="com.laughter.broadcast.DIY_BROADCAST"/> </intent-filter> </receiver>
我們添加一條值為 com.laughter.broadcast.DIY_BROADCAST 的action票顾,也就意味著础浮,我們待會兒發(fā)出一條值為這個的廣播,我們的廣播接收器就能收到奠骄。
接下來豆同,在我們的布局文件中添加一個Button,用于觸發(fā)發(fā)送廣播(很簡單含鳞,我就不貼代碼了)影锈,然后修改BroadcastActivity中的代碼:public class BroadcastActivity extends AppCompatActivity { @BindView(R.id.but_send) Button send; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_broadcast); ButterKnife.bind(this); } @OnClick(R.id.but_send) public void send() { Intent intent = new Intent("com.laughter.broadcast.DIY_BROADCAST"); sendBroadcast(intent); } }
發(fā)送廣播的方法很簡單,就是創(chuàng)建一個Intent對象蝉绷,將需要發(fā)送的廣播的值作為參數(shù)傳遞進去鸭廷,然后調(diào)用sendBroadcast()方法將廣播發(fā)送出去,這樣監(jiān)聽 com.laughter.broadcast.DIY_BROADCAST 這個值的廣播接收器就能收到這條廣播(這里就不貼效果圖了熔吗,大家可以自己驗證)辆床。由于是用Intent發(fā)送廣播,因此桅狠,還可以用這個Intent對象攜帶一些參數(shù)讼载。
廣播是一種可以跨進程的通信方式,因此中跌,我們在應用程序中發(fā)送的廣播咨堤,別的應用程序也是可以收到的。下面我們來驗證一下:
首先創(chuàng)建一個BroadcastTest項目漩符,然后定義一個廣播接收器:public class AnotherReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "received in another receiver", Toast.LENGTH_SHORT).show(); } }
在AndroidManifest文件中進行注冊:
這樣一喘,我們的兩個程序就都能說收到這條廣播了,下面將兩個app都運行起來嗜暴,驗證一下:<receiver android:name=".AnotherReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.laughter.broadcast.DIY_BROADCAST"/> </intent-filter> </receiver>
可以看到津滞,當我們點擊Button時,兩條Toast都彈出了灼伤, 也就意味著触徐,兩個app中的廣播接收器都收到了這條廣播。
-
發(fā)送有序廣播
要發(fā)送一條有序廣播狐赡,我們只需要在前面的BroadcastActivity中撞鹉,將sendBroadcast(intent) 方法換成 sendOrderedBroadcast(intent, null) 方法就行了(第一個參數(shù)還是Intent對象,第二個參數(shù)是與權限相關的字符串,這里直接傳null就行):public class BroadcastActivity extends AppCompatActivity { ··· @OnClick(R.id.but_send) public void send() { Intent intent = new Intent("com.laughter.broadcast.DIY_BROADCAST"); sendOrderedBroadcast(intent, null); } }
前面說過鸟雏,發(fā)送有序廣播的時候享郊,廣播接收器之間存在優(yōu)先級,那么如何指定優(yōu)先級呢孝鹊?
<receiver android:name=".broadcast.broadcast.DiyBroadcastReceiver" android:enabled="true" android:exported="true"> <intent-filter android:priority="13"> <action android:name="com.laughter.broadcast.DIY_BROADCAST"/> </intent-filter> </receiver>
我們只需要在注冊廣播接收器的時候給<intent-filter>標簽指定一個android:priority 屬性就行了炊琉,里面的值就是優(yōu)先級,值越大又活,優(yōu)先級越高苔咪。同樣的,給AnotherReceiver指定一個優(yōu)先級柳骄,數(shù)字比這里的13小就行团赏。這樣,優(yōu)先級高的廣播接收器就會先收到廣播耐薯。(這里也不貼圖了舔清,自行驗證)
前面還提到過,有序廣播是可以被攔截的曲初,如何攔截呢体谒?我們只需要在廣播接收器的onReceive()方法中調(diào)用 abortBroadcast(); 方法就可以攔截廣播,這樣優(yōu)先級比這個廣播接收器低的就收不到這條廣播了臼婆。
還是剛才的兩個app营密,我們再來運行起來看看:public class DiyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ToastUtil.showShortToast(context, "received in my broadcast receiver"); abortBroadcast(); } }
可以看到,現(xiàn)在只有一個優(yōu)先級高的廣播接收器的onReceive()方法被觸發(fā)目锭,彈出了Toast,而由于廣播被攔截纷捞,優(yōu)先級低的AnotherReceiver沒有收到廣播痢虹,所以沒有彈出Toast。
使用本地廣播
前面我們發(fā)送和接收的廣播全部屬于系統(tǒng)全局廣播主儡,即發(fā)出的廣播可以被任何其他的應用程序接收到奖唯,并且也可以接受其他任何應用程序的廣播。這樣就很容易引起安全性問題糜值。為此丰捷,Android引入了一套本地廣播機制,使用本地廣播機制寂汇,廣播只能在應用程序內(nèi)部進行傳遞病往,并且廣播接收器也只會接收到應用程序內(nèi)部的廣播。
其實本地廣播的用法和前面差不多骄瓣,我們先來看代碼:
public class BroadcastActivity extends AppCompatActivity {
@BindView(R.id.but_send)
Button send;
LocalBroadcastManager manager;
LocalReceiver localReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_broadcast);
ButterKnife.bind(this);
initView();
}
private void initView() {
manager = LocalBroadcastManager.getInstance(this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.laughter.broadcast.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
manager.registerReceiver(localReceiver, intentFilter);
}
@OnClick(R.id.but_send)
public void send() {
Intent intent = new Intent("com.laughter.broadcast.LOCAL_BROADCAST");
manager.sendBroadcast(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
manager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ToastUtil.showShortToast(context, "received local broadcast");
}
}
}
和前面一樣停巷,我們首先定義一個內(nèi)部類,即廣播接收器,然后定義一個 LocalBroadcastManager 對象畔勤,通過它的 getInstance() 方法獲取一個實例蕾各,接下來就是定義IntentFilter對象和廣播接收器對象,給IntentFilter添加一條值為 com.laughter.broadcast.LOCAL_BROADCAST 的action庆揪,然后調(diào)用 LocalBroadcastManager 的 registerReceiver() 方法注冊本地廣播監(jiān)聽器式曲,接下來在Button的點擊事件中通過 LocalBroadcastManager 的 sendBroadcast() 方法發(fā)送廣播,最后別忘了在onDestroy() 方法中取消注冊缸榛。這樣看起來吝羞,本地廣播區(qū)別于全局廣播的地方僅僅就是通過一個LocalBroadcastmanager 來管理廣播的注冊和發(fā)送。這樣我們的廣播就是僅僅在應用程序內(nèi)部傳遞的了(感興趣的自行驗證)仔掸。
注意:既然需要通過 LocalBroadcastmanager 來管理廣播的注冊和發(fā)送脆贵,那么對應的靜態(tài)注冊的方式怎么實現(xiàn)呢?很遺憾起暮,本地廣播是無法通過靜態(tài)注冊的方式來接收的卖氨。不過仔細想想,本地廣播的發(fā)送很顯然是需要在應用程序啟動的情況下完成的负懦,既然要在應用程序啟動的情況下發(fā)送廣播筒捺,那么也不用考慮在應用程序未啟動的情況下接收廣播了。而我們使用靜態(tài)注冊主要就是為了在應用程序未啟動的情況下也能收到廣播纸厉,所以思路就很清晰了系吭。
上一篇:Android基礎回顧(三)| 關于Fragment
下一篇:Android基礎回顧(五)| 數(shù)據(jù)存儲——持久化技術