1、背景&解決方法 最近碰到一個需求,app監聽特定的廣播,接收到廣播後啟動自己再進行處理。需求很簡單,靜態註冊就好,不過,在自測的時候遇到一個問題,app安裝後沒啟動過的狀態下,什麼廣播都收不到!なにもない! 後來,網上各種查,找到了“罪魁禍首”:Android 3.1以後新增的stopped機制 ...
1、背景&解決方法
最近碰到一個需求,app監聽特定的廣播,接收到廣播後啟動自己再進行處理。需求很簡單,靜態註冊就好,不過,在自測的時候遇到一個問題,app安裝後沒啟動過的狀態下,什麼廣播都收不到!なにもない!
後來,網上各種查,找到了“罪魁禍首”:Android 3.1以後新增的stopped機制。
解決方法是,發送廣播時添加flag:FLAG_INCLUDE_STOPPED_PACKAGES
是的,沒錯,這個解決方法對系統廣播無效,如果要處理的是系統廣播,本文對你無效。
2、stopped機制是什麼?
我們都知道流氓軟體非常影響用戶體驗,而且經常在用戶不知不覺的情況下就被安裝到手機中,如果這個軟體再監聽了一些通用的系統廣播,就可以消無聲息地在你手機後臺乾各種事情。
為了避免這種情況,android 3.1以後加入了stopped機制。系統會在遍歷所有app後,講過app信息記錄到一個xml文件中,路徑為:
data/system/users/0/package-restrictions.xml
這個路徑有部分系統會不同,有興趣可以看看系統源碼:frameworks\base\services\core\java\com\android\server\pm\Setting.java
每個app的信息會被記錄到一個pkg標簽中,而這個標簽有個屬性stopped
,顧名思義,當這個屬性被置為true時,這個app就是停止狀態。例:
<pkg name="com.example.test" stopped="true" />
讓我們再看到Intent的兩個Flag:
/**
* If set, this intent will not match any components in packages that
* are currently stopped. If this is not set, then the default behavior
* is to include such applications in the result.
*/
public static final int FLAG_EXCLUDE_STOPPED_PACKAGES = 0x00000010;
/**
* If set, this intent will always match any components in packages that
* are currently stopped. This is the default behavior when
* {@link #FLAG_EXCLUDE_STOPPED_PACKAGES} is not set. If both of these
* flags are set, this one wins (it allows overriding of exclude for
* places where the framework may automatically set the exclude flag).
*/
public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x00000020;
簡單地說,帶有FLAG_EXCLUDE_STOPPED_PACKAGES
的廣播不會被髮送給stopped狀態的app,而兩者都有的情況下以FLAG_INCLUDE_STOPPED_PACKAGES
為準。這就是stopped機制,能在一定程度上防範流氓軟體惡意監聽系統廣播,而FLAG_INCLUDE_STOPPED_PACKAGES
則相當於留給開發者用於自定義廣播的後門了。
3、那麼,為啥我沒加那個啥flag,也收不到?
我們可以看看廣播被傳到sendBroadcase方法後都被幹了什麼。
找到ContextImpl的sendBroadcast方法:
public void sendBroadcast(Intent intent) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
我們看到裡面調用了一個Service的broadcastIntent方法,這個Service實際為ActivityManagerService(binder原理這裡就不寫了),我們直接找到ActivityManagerService類的broadcastIntent方法:
public final int broadcastIntent(IApplicationThread caller,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle resultExtras,
String[] requiredPermissions, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
synchronized(this) {
intent = verifyBroadcastLocked(intent);
final ProcessRecord callerApp = getRecordForAppLocked(caller);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
// 這裡再傳進去
int res = broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null,
intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, appOp, bOptions, serialized, sticky,
callingPid, callingUid, userId);
Binder.restoreCallingIdentity(origId);
return res;
}
}
可以看到intent被傳給了broadcastIntentLocked方法,繼續進去,這個方法就是對intent進行一系列處理的地方,不難看到有一句:
// By default broadcasts do not go to stopped apps.
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
一切真相大白,android預設設置自定義廣播也不發送給stopped狀態的app。所以需要做到我們上面的需求,必須添加FLAG_INCLUDE_STOPPED_PACKAGES
標誌位。
4、什麼情況下會被置為stopped state?
主要有3種情況:
(1)安裝後未被啟動過;
(2)被用戶強制停止(應用管理-->應用詳情-->強制停止);
(3)被調用forceStopPackage(pkg)殺死。
另外,系統應用不受此限制(還沒看這裡的源碼,以後看了再補充,或者有大神指點一下嗎?)。