安卓權威編程指南-筆記(第27章 broadcast intent)
本章需求:首先,讓應用輪詢新結果並在有所發現時及時通知用戶,即使用戶重啟設備後還沒有打開過應用。其次,保證用戶在使用應用時不出現新結果通知。
1. 一般intent和broadcast intent
許多系統組件需要知道某些事件的發生(WIFI信號時有時無,電話的呼入等),為滿足這樣的需求,Andorid提供了broadcast intent 組件。
broadcast intent的工作原理類似於之前學過的intent,但不同的是broadcast intent可以被多個叫做broadcast receiver的組件接收、
2. 接受系統broadcast : 重啟後喚醒
2.1 standalone receiver
standalone receiver 是一個在manifest配置文件中聲明的broadcast receiver。即使應用進程已消滅,standalone receiver也可以被激活。(還有一種是可以同fragemt或activity的生命周期綁定的dynamic receiver)
broadcast receiver必須在系統中登記後才能發揮作用,如果不登記,系統就不知道該向哪裏發送intent,broadcast receiver的onReceiver()方法也就得不到預定的調用了。
要登記broad receiver,首先要創建它:
public class StartupReceiver extends BroadcastReceiver {
private static final String TAG = "StartupReceiver";
@Override
public void onReceive(Context context, Intent intent) { //onReceiver是在主線程中執行的
Log.i(TAG, "Received broadcast intent: " + intent.getAction());
boolean isOn = QueryPreferences.isAlarmOn(context);
PollService.setServiceAlarm(context, isOn);
}
}
broadcast receiver是接受intent的組件,當有intent發送給StartupReceiver時,它 的onReceive()方法會被調用。
然後在AndroidManifest.xml文件中聲明:
<usespermissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver android:name=".StartupReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
完成聲明後,即使app並未運行,只要有匹配的broadcast intent發來,broadcast receiver就會醒來接受,一收到intent,broadcast receiver的onReceive(Context. Intent)方法即開始執行,隨後會被銷毀。
3. 過濾前臺通知消息
PhotoGallery應用的另一缺陷,通知消息雖然很有用,但應用開著的時候不應該受到通知消息。
解決方式:
首先,我們發送(或接收)定制版broadcast intent(最後會鎖定它,只允許PhotoGallery應用部件接收它)。其次,不再使用manifest文件,改用代碼為broadcast intent動態登記receiver。(動態註冊的receiver與fragment進行綁定,收到廣播時說明是在app中) 最後,發送一個有序broadcast在一組receiver中傳遞數據,借此保證最後才運行某個receiver(最後的receiver決定顯不顯示通知,這個receiver是靜態註冊的)。
3.1 發送broadcast intent
要發送broadcast intent,需要創建一個intent,並傳入sendBroadcast(intent)方法即可。
public static final String ACTION_SHOW_NOTIFICATION = "com.bignerdranch.android.photogallery.SHOW_NOTIFICATION";
sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION));
3.2 動態broadcast receiver
動態broadcast receiver是在代碼中,而不是在配置文件中完成登記聲明。要在代碼中登記,可調用registerReceiver(BroadcastReceiver, IntentFliter)方法,取消登記時,則調用unregiseterReceiver
(BroadcastReceiver)方法。receiver自身通常被定義為一個內部類實例,如同一個按鈕點擊監聽器。在registerReceiver()和unregisterReceiver()方法的BroadcastReceiver需要的是同一個實例、
我們要只在應用開啟的時候接受發過來的廣播過濾,就不能在 manifest 中聲明一個過濾器,而是要動態地建立一個廣播接收器。我們在這裏建立一個用於隱藏前臺通知的通用 fragment 子類:
public abstract class VisibleFragment extends Fragment {
private static final String TAG = "VisibleFragment";
@Override
public void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter(PollService.ACTION_SHOW_NOTIFICATION);
getActivity().registerReceiver(mOnShowNotification, filter,
PollService.PERM_PRIVATE, null);
}
@Override
public void onStop() {
super.onStop();
getActivity().unregisterReceiver(mOnShowNotification);
}
private BroadcastReceiver mOnShowNotification = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 如果接收到廣播,說明應用正在前臺,所以把 ResultCode 更改掉
Log.i(TAG, "canceling notification");
setResultCode(Activity.RESULT_CANCELED);
}
};
}
3.3 使用私有權限
使用動態broadcast receiver存在一個問題,即系統中的任何應用均可監聽並觸發我們的receiver。
有兩種辦法可以阻止應用闖入我們的私人領域,一種辦法是在mainfest配置文件裏給receiver標簽添加一個android:exported= “false”屬性,聲明它僅限應用內部使用。
另外,也可以創建自己的使用權限,可以通過在AndroidManifest.xml中添加一個permission標簽來完成:
<permission android:name="com.bignerdranch.android.photogallery.PRIVATE"
android:protectionLevel="signature" />
<uses-permission android:name="com.bignerdranch.android.photogallery.PRIVATE" />
要使用權限,須將其作為參數傳入sendBroadcast(),有了這個權限,所有應用都必須使用同樣的權限才能接受我們發送的intent。
要怎麽保護我們的broad receiver呢?其他應用可通過創建自己的broadcast intent來觸發它。同樣,在 registerReceiver(...) 方法中傳入自定義權限就能解決該問題:
public abstract class VisibleFragment extends Fragment { ... @Override public void onStart() { super.onStart(); IntentFilter filter = newIntentFilter(PollService.ACTION_SHOW_NOTIFICATION); getActivity().registerReceiver(mOnShowNotification, filter, PollService.PERM_PRIVATE, null); } ... }
3.3.1 深入學習安全級別
自定義權限必須指定 android:protectionLevel 屬性值。Android根據 protectionLevel 屬性值確定自定義權限的使用方式。在PhotoGallery應用中,我們使用的 protectionLevel 是signature 。signature 安全級別表明,如果其他應用需要使用我們的自定義權限,則必須使用和當前應用相同的key做簽名認證。對於僅限應用內部使用的權限,選擇 signature 安全級別比較合適。既然其他開發者沒有相同的key,自然也就無法接觸到權限保護的東西。此外,有了自己的key,將來還可用於我們開發的其他應用中。
3.4 使用有序broadcast
如果想讓程序在打開時不發送出通知,就不能再讓服務來發出通知了,因為它無法知道前臺的運行狀態。所以我們讓 PollService 發送一個有序廣播。
public static final String REQUEST_CODE = "REQUEST_CODE"; public static final String NOTIFICATION = "NOTIFICATION"; private void showBackgroundNotification(int requestCode, Notification notification) { Intent i = new Intent(ACTION_SHOW_NOTIFICATION); i.putExtra(REQUEST_CODE, requestCode); i.putExtra(NOTIFICATION, notification); sendOrderedBroadcast(i, PERM_PRIVATE, null, null, Activity.RESULT_OK, null, null); }
有序廣播是按照優先級發送的,先發送給優先級高的接收器,再發給優先級低的接收器。因為在應用結束後也要發出通知,顯然我們發出通知的廣播接收器是需要聲明在 manifest 文件中的。
內部實現如下:
public class NotificationReceiver extends BroadcastReceiver { private static final String TAG = "NotificaitonReceiver"; @Override public void onReceive(Context context, Intent intent) { Log.i(TAG, "received result: " + getResultCode()); if (getResultCode() != Activity.RESULT_OK) { // PollService 發出的 intent 帶的結果碼是 RESULT_OK // 如果接到的不是,說明應用在前臺,將結果碼修改了 return; } // 如果沒有 return,說明應用不在前臺,就可以發出通知了。 int requestCode = intent.getIntExtra(PollService.REQUEST_CODE, 0); Notification notification = (Notification) intent.getParcelableExtra(PollService.NOTIFICATION); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.notify(requestCode, notification); } }
<receiver android:name=".NotificationReceiver" android:exported="false"> <!-- 在這裏將優先級設為最低,即 -999 --> <intent-filter android:priority="-999"> <action android:name="com.kniost.photogallery.SHOW_NOTIFICATION" /> </intent-filter> </receiver>
3.5 receiver與長時間運行任務
如不想受限與主線程的時間限制,希望broadcast intent觸發一個長時間運行任務,該如何做呢?
- 將任務交給服務處理,然後通過broadcast receiver啟動服務。
- 使用BroadcastReceiver.getAsync()方法。該方法返回一個 BroadcastReceiver.PendingResult 對象,隨後可使用該對象提供結果。因此,可將 PendingResult 交給AsyncTask 去執行長時運行的任務,然後再調用 PendingResult 的方法響應broadcast。
- goAsync() 方法的弊端是不夠靈活。我們仍需快速響應broadcast(10秒內),並且與使用服務相比,沒什麽架構模式好選擇。當然, goAsync() 方法也有明顯的優勢:可調用該方法設置有序broadcast的結果。
3.6 使用EventBus
broadcast intent可實現系統內全局性的消息傳遞。如果僅需要應用內的消息事件廣播,該怎
麽做呢?答案是使用事件總線(event bus)。事件總線的設計思路就是,提供一個應用內的部件可以訂閱的共享總線或數據流。事件一旦
發布到總線上,各訂閱部件就會被激活並執行相應的回調代碼。由greenrobot出品的EventBus是目前廣為人知的一個第三方事件總線庫。
為實現在應用內發送broadcast intent,Android自己也提供了一個叫作 LocalBroadcast-
Manager 的廣播管理類;但上述第三方類庫用起來更為靈活和方便。
安卓權威編程指南-筆記(第27章 broadcast intent)