有个系统应用(没有图标,只有一个service服务)依靠开机广播BOOT_COMPLETED和网络变化广播ConnectivityManager.CONNECTIVITY_ACTION来进行自启动,但是后来需要将这个应用修改为非系统应用,开机之后出现了服务无法自启动的问题。
原因:Android在3.1以后将新安装并且从未被启动的应用置为“STOPPED”状态,这种状态指那些安装了但从未启动过的apk,或在settings中被force stop的apk,这种状态下的应用是无法接收到广播的(IntentResolver.buildResolveList)。Android这样做的目的是防止广播无意或者不必要地开启未启动的APP后台服务,因此,无法在应用未启动的情况下通过接收广播,来完成一些操作。
解决思路:Android提供了添加特定Flag广播的方式,用来标识Intent是否要对处于”STOPPED“状态下的App起作用
frameworks/base/core/java/android/content/Intent.java
/**
* 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状态下的package
FLAG_INCLUDE_STOPPED_PACKAGES:包含stopped状态下的package
先看下默认设置了FLAG_EXCLUDE_STOPPED_PACKAGES标识代码位置:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
int realCallingPid, int userId, boolean allowBackgroundActivityStarts) {
intent = new Intent(intent);
final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
// Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
if (callerInstantApp) {
intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
}
// By default broadcasts do not go to stopped apps.
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
// If we have not finished booting, don't allow this to launch new processes.
if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
......
可以看到一般情况下会默认添加FLAG_EXCLUDE_STOPPED_PACKAGES flag。
再看下广播跳过“STOPPED”状态的地方
frameworks/base/services/core/java/com/android/server/IntentResolver.java
private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
boolean debug, boolean defaultOnly, String resolvedType, String scheme,
F[] src, List<R> dest, int userId) {
final String action = intent.getAction();
final Uri data = intent.getData();
final String packageName = intent.getPackage();
final boolean excludingStopped = intent.isExcludingStopped();
......
final int N = src != null ? src.length : 0;
boolean hasNonDefaults = false;
int i;
F filter;
for (i=0; i<N && (filter=src[i]) != null; i++) {
int match;
if (debug) Slog.v(TAG, "Matching against filter " + filter);
if (excludingStopped && isFilterStopped(filter, userId)) {//如果设置了FLAG_EXCLUDE_STOPPED_PACKAGES标识,并且是stop app,则跳过
if (debug) {
Slog.v(TAG, " Filter's target is stopped; skipping");
}
continue;
}
// Is delivery being limited to filters owned by a particular package?
if (packageName != null && !isPackageForFilter(packageName, filter)) {
if (debug) {
Slog.v(TAG, " Filter is not from package " + packageName + "; skipping");
}
continue;
}
.....
可以看出这里是否跳过有两个要求:
1.excludingStopped:设置了FLAG_EXCLUDE_STOPPED_PACKAGES标识,默认设置,所以一般情况下stopped应用都无法接收到广播。
frameworks/base/core/java/android/content/Intent.java
public boolean isExcludingStopped() {
return (mFlags&(FLAG_EXCLUDE_STOPPED_PACKAGES|FLAG_INCLUDE_STOPPED_PACKAGES))
== FLAG_EXCLUDE_STOPPED_PACKAGES;
}
2.isFilterStopped:是否是Stopped app。这里一般只包含非系统应用、并且状态为stopped的应用才会跳过广播接受。
frameworks/base/services/core/java/com/android/server/pm/ComponentResolver.java
@Override
protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter, int userId) {
if (!sUserManager.exists(userId)) return true;
PackageParser.Package p = filter.activity.owner;
if (p != null) {
PackageSetting ps = (PackageSetting) p.mExtras;
if (ps != null) {
// System apps are never considered stopped for purposes of
// filtering, because there may be no way for the user to
// actually re-launch them.
return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0
&& ps.getStopped(userId);
}
}
return false;
}
解决方案:
1.可以直接在AMS.broadcastIntentLocked里将FLAG_EXCLUDE_STOPPED_PACKAGES替换成FLAG_INCLUDE_STOPPED_PACKAGES
但是这样做会导致所有stop应用接收广播,具有很大风险,不建议使用。
2.在发送intent的地方加上FLAG_INCLUDE_STOPPED_PACKAGES的flag
Intent intent = new Intent();
intent.setAction("android.intent.action.BOOT_COMPLETED");
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);