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
这个路径有部分系统会不同,有兴趣可以看看系统源码:frameworksaseservicescorejavacomandroidserverpmSetting.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)杀死。
另外,系统应用不受此限制(还没看这里的源码,以后看了再补充,或者有大神指点一下吗?)。