這是四大組件的第一篇(其他還沒整理好:) ),之前有個習慣,就是把一些筆記記在書上,但是隨著書越來越多,翻閱的時候比較麻煩,尤其是一段時間不用之后,想要翻閱某個知識點太費勁,這里就打算統一整理在一起,方便查看。
其中有很多內容參照了網上的博文,但是時間比較久,忘記出處了。另外就是參照Android developer的相關文檔,加上自己的理解,如果有錯誤,歡迎指出啊。
應用場景
同一APP內部的同一組件內的消息通信(單個或多個線程之間)
同一APP內部的不同組件間的消息通信(單個進程)
同一APP內部的具有不同進程的多個組件間的消息通信(多個進程)
不同APP組件間的消息通信(多個進程)
Android系統與APP之間的消息通信(系統廣播)
分類
1. 普通廣播
<pre>
public abstract void sendBroadcast (Intent intent, String receiverPermission)
public abstract void sendBroadcast (Intent intent)
</pre>
所有receiver都是無序的,各receiver往往同時運行。相對來說更為高效,但各receiver無法終止廣播或使用其他廣播執行的結果。
2. 有序廣播
<pre>
public abstract void sendOrderedBroadcast (Intent intent, String receiverPermission)
public abstract void sendOrderedBroadcast (Intent intent,
String receiverPermission,
BroadcastReceiver finalResultReceiver,
Handler scheduler,
int initialCode,
String initialData,
Bundle initialExtras)
</pre>
finalResultReceiver作為最末尾的receiver,可以得到前面一系列receiver的處理結果。通常應該提供自定義的receiver。
scheduler一般設為null,說明使用context的主線程。
initialCode一般設為RESULT_OK,作為resultCode的初始值。
initialData一般設為null。作為resultData的初始值。
initialExtras一般設為null,作為resultExtras的初始值。
通過Context.sendOrderedBroadcast發送,receiver會按照android:priority所指定的優先級由大到小依次執行(android:priority范圍為-1000~1000,數值越大優先級越高,默認優先級為0),由于是有序傳播,可以實現如下效果:
終止傳播:通過調用abortBroadcast,之后的receiver將接收不到該廣播
向后繼者傳遞數據:在onReceive中可以調用setResult、setResultCode、setResultData/setResultExtras設置信息,后繼者可以通過getResultCode、getResultData、getResultExtra來獲取信息。sendOrderedBroadcast中的參數initialCode、initialData、initialExtras提供了初始值。
注意:當靜態注冊與動態注冊使用了相同的優先級(priority)時,動態注冊的receiver處于更優先的位置。
安全方面的考慮
1. 隱患
其他APP可能會針對性的發出與當前APP中intent-filter相匹配的廣播,導致當前APP不斷受到廣播并處理。
其他APP可能注冊與當前APP一致的intent-filter用于接收廣播,從而截獲了廣播的具體信息
2. 措施
如果receiver是用于同一APP內部的,則直接將其exported設為false
在發送廣播時,使用含有permission的版本
在動態注冊receiver時,使用含有permission的版本
在靜態注冊receiver時,在xml中增加permission字段
在發送廣播時,可以指定receiver的包名。通過intent.setPackage(packageName)
3. 更便捷的方式——使用LocalBroadcastManager
獲取單例
<pre>
static LocalBroadcastManager getInstance(Context context);
</pre>注冊
<pre>
void registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
</pre>注銷
<pre>
void unregisterReceiver(BroadcastReceiver receiver);
</pre>發送廣播
<pre>
boolean sendBroadcast(Intent intent);
</pre>發送廣播(同步發送,會阻塞直至所有相關receiver執行完畢onReceive并返回)
<pre>
void sendBroadcastSync(Intent intent);
</pre>
注冊方式
1. 靜態注冊
通過xml文件的形式進行注冊,此種方式注冊的receiver會在APP運行期間一直存在,當APP被kill后就接收不到了。此種方式更為常用。
通過靜態注冊方式注冊的receiver,其onReceive(Context context, Intent intent)中的context為ReceiverRestrictedContext(Context含有的bindService和registerReceiver函數被禁用)
注意:當BroadcastReceiver作為內部類被實現,同時又使用了靜態注冊的方式,那么該內部類必須聲明為“public static”
注意:從4.0開始,需要至少啟動一次APP后,靜態注冊的receiver才算注冊完畢
2. 動態注冊
通過代碼的方式進行注冊,BroadcastReceiver可實現為內部類或一般的外部類。
一般情況下,可以在onResume注冊receiver,在onPause中注銷receiver(少數情況下,也可以在onCreate中注冊,在onDestory中注銷)
通過動態注冊方式注冊的receiver,其onReceive(Context context, Intent intent)中的context為Activity的Context。
3. LocalBroadcastManager的動態注冊
如果廣播只是在APP內部進行收發,那么更高效和安全的方式是使用LocalBroadcastManager。這種方式只能夠進行動態注冊。
采用此種方式注冊的receiver,其onReceive(Context context, Intent intent)中的context為Application的Context。
生命周期
receiver的生命周期
receiver對象只有在onReceive的調用期間是有效的,是“活躍”的,一旦從onReceive中返回,那么系統將認為該對象已經結束,變為“不活躍”狀態。
任何需要異步的操作都不應該放在onReceive中,因為當異步操作結束時,receiver可能已經處于“不活躍”狀態,不能保證對象是否還存在。
注意:不能夠在onReceive中顯示對話框,可行的替代方案是使用NotificationManager相關功能。
注意:不能夠在onReceive中綁定服務(bindService),可行的替代方案是通過startService發送命令。
process的生命周期
正在執行receiver中onReceive的代碼的process被認為是“前臺進程”,它會一直保持運行(除非遇到非常極端的內存方面的壓力才會被kill,這一般不會出現)
一旦從onReceive中返回,receiver變為“不活躍”狀態,它所在的process的重要性將取決于運行在該process中的其他組件。如果這個process沒有其他組件在運行(比如一個APP,用戶近期沒有與之交互過),那么系統將認為這是一個“空process”,會在合適的時機kill掉它。
產生的問題:如果在onReceive中產生一個thread,然后返回,整個進程、包括新產生的thread,都不認定為“不活躍”的,存在被kill的危機。解決的方式是與service相結合,在onReceive中啟動一個Service,讓Service去做具體的工作,由于Service的存在,當前的process的重要性取決于Service的狀態,只要Service執行的工作未完成,它就一直是“活躍”的,就不會被kill。
其他注意事項
如果希望在receiver中的onReceive中開啟一個Activity,則必須增加FLAG_ACTIVITY_NEW_TASK標記。
onReceive必須在10秒鐘內執行完畢,否則會產生ANR(Application Not Response)。如確實需要進行耗時的操作,可以通過啟動一個Service的方式進行。
與廣播相關的Intent的FLAG:
<pre>
FLAG_EXCLUDE_STOPPED_PACKAGES (不再通知process被終止的receiver,默認行為)
FLAG_INCLUDE_STOPPED_PACKAGES (仍然通知process被終止的receiver)
</pre>
從3.1開始,如果靜態注冊的APP退出后,不一定能夠收到廣播。
因為3.1開始系統增加了對APP是否處于運行狀態的跟蹤。在發送廣播時,系統默認增加了FLAG_EXCLUDE_STOPPED_PACKAGES的flag,導致即使是靜態注冊的receiver,當其所在process退出后,同樣無法接收到廣播。
對于自定義的廣播,可以修改這種行為,使靜態注冊的receiver在process被結束后依然可以收到廣播,方法就是在intent中將FLAG_EXCLUDE_STOPPED_PACKAGES改寫為FLAG_INCLUDE_STOPPED_PACKAGES。
對于系統廣播,則無能為力了。
常見系統廣播
ACTION_TIME_TICK
當前時間變化,每分鐘廣播一次。只能使用Context.registerReceiver()動態注冊,靜態注冊無效。
值: "android.intent.action.TIME_TICK"
ACTION_TIME_CHANGED
系統時間被設置。
值: "android.intent.action.TIME_SET"
ACTION_TIMEZONE_CHANGED
時區被修改。帶有extra:time-zone
值: "android.intent.action.TIMEZONE_CHANGED"
ACTION_BOOT_COMPLETED
系統啟動完成。可用進行一些初始化工作,比如安裝alarm等。
權限:RECEIVE_BOOT_COMPLETED
值: "android.intent.action.BOOT_COMPLETED"
ACTION_PACKAGE_ADDED
新的APP被安裝。(新安裝的APP不會受到此廣播)
Data:新APP的包名
可能包含的Extras:
- EXTRA_UID 新APP的UID.
- EXTRA_REPLACING 是否是重裝或升級。如果這個廣播緊跟在ACTION_PACKAGE_REMOVED之后,并且作用的是同一個包,那么這個值為true
值: "android.intent.action.PACKAGE_ADDED"
ACTION_PACKAGE_CHANGED
已安裝的APP被改動,比如禁用或使能了某個組件。
Data: 包名
Extras:
- EXTRA_UID 包的UID
- EXTRA_CHANGED_COMPONENT_NAME_LIST 包含了被修改的組件的類名(或包名本身)
- EXTRA_DONT_KILL_APP 布爾量,是否覆蓋重啟APP的默認action(待確認)
值: "android.intent.action.PACKAGE_CHANGED"
ACTION_PACKAGE_REMOVED
已安裝的APP被卸載。
Data:被卸載的APP的包名。
Extras:
- EXTRA_UID 被卸載APP的uid
- EXTRA_DATA_REMOVED 如果整個APP(包含代碼和數據)被卸載,其值被設為true
- EXTRA_REPLACING 是否是重裝或升級。如果這個廣播后面緊跟著ACTION_PACKAGE_ADDED之后,并且作用的是同一個包,那么這個值為true
值: "android.intent.action.PACKAGE_REMOVED"
ACTION_PACKAGE_RESTARTED
用戶重啟了這個APP,它的所有process被kill,所有與它相關的運行時狀態被移除(包括process、alarm、notification等)。被重啟的APP收不到這個廣播。
Data:被重啟APP的包名
Extras:
- EXTRA_UID 被重啟APP的uid
值: "android.intent.action.PACKAGE_RESTARTED"
ACTION_PACKAGE_DATA_CLEARED
用戶清空了APP的數據。這需要發生在ACTION_PACKAGE_RESTARTED之前。在擦除該APP所有持久化數據之后,本廣播被發出。被清空的APP收不到此廣播。
Data:被清空數據的APP的包名
Extras:
- EXTRA_UID APP的uid
值: "android.intent.action.PACKAGE_DATA_CLEARED"
ACTION_UID_REMOVED
UID被從系統移除。UID被以EXTRA_UID為鍵存儲在extras中。
值: "android.intent.action.UID_REMOVED"
ACTION_BATTERY_CHANGED
粘性廣播被聲明為過時的,此處待驗證。
ACTION_POWER_CONNECTED
連接外部電源。
值: "android.intent.action.ACTION_POWER_CONNECTED"
ACTION_POWER_DISCONNECTED
外部電源被移除。
值: "android.intent.action.ACTION_POWER_DISCONNECTED"