1. 程式人生 > >安卓權威編程指南-筆記(第27章 broadcast intent)

安卓權威編程指南-筆記(第27章 broadcast intent)

自己的 靜態 深入 cas per 闖入 運行 evel ive

本章需求:首先,讓應用輪詢新結果並在有所發現時及時通知用戶,即使用戶重啟設備後還沒有打開過應用。其次,保證用戶在使用應用時不出現新結果通知。

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)