背景
之前给手机淘宝做了个准点提醒的中间件,类似闹铃提醒,使用sqlite和alarmManager两个组件实现,最近发现在MIUI上大量用户反馈提醒收不到,所以查了两天原因,把这个问题总结下以备后人参考。
场景再现
在说这个问题之前,先来说下MIUI奇葩的清理机制,没错,MIUI用户长按home键的那个清理过程,执行的是forceStopPackage操作。那么执行forcestop之后,android对于我们的app执行了什么操作呢,我们这里探究一下:
app 被forceStop以后,dalvik会调用ActivityManagerService的forceStopPackageLocked()方法,我们来看下这个方法:
private void forceStopPackageLocked(final String packageName, int uid) {
forceStopPackageLocked(packageName, uid, false, false, true, false);
Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
Uri.fromParts("package", packageName, null));
if (!mProcessesReady) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
intent.putExtra(Intent.EXTRA_UID, uid);
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null,
false, false, MY_PID, Process.SYSTEM_UID);
}
代码里面发送了一个广播:ACTION_PACKAGE_RESTARTED,这个广播大有文章。在android3.1以后的版本中,如果程序被强制停止后应用状态会被标记为STOPPED,同时发送该广播,进入一种冻结状态。
影响
1、对于Receiver的影响
在冻结状态下,应用无法收到其他应用的广播(就算监听系统的开机启动等广播也不行),要等到应用再开启一次,将STOPPED去掉以后才可以。
那是不是被冻结的应用除了被用户手动开启就再也没有办法启动了呢?不是的。
解决办法:
在广播发发送方发送广播时需要设置Intent.FLAG_INCLUDE_STOPPED_PACKAGES
Intent intent = new Intent();
intent.setAction("com.taobao.test");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);/
}
sendBroadcast(intent);
android 3.1以后有一类package 叫做stopped package, 它们就是那种安装了但是从来没有启动过的apk,或者被用户在程序管理里面force stop了的apk,intent中新加了一组flag(FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES),带有FLAG_EXCLUDE_STOPPED_PACKAGES的 intent对stopped package是不起作用的。系统对所有的广播intent都加了flag:FLAG_EXCLUDE_STOPPED_PACKAGES,当然boot complete广播也不例外。默认Intent也都是FLAG_EXCLUDE_STOPPED_PACKAGES的
我们先来看看AlarmManagerService.java的代码,可以看一个内部类UninstallReceiver
class UninstallReceiver extends BroadcastReceiver {
public UninstallReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
filter.addDataScheme("package");
mContext.registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(this, sdFilter);
}
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
String action = intent.getAction();
String pkgList[] = null;
if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
for (String packageName : pkgList) {
if (lookForPackageLocked(packageName)) {
setResultCode(Activity.RESULT_OK);
return;
}
}
return;
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else {
if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
&& intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
// This package is being updated; don't kill its alarms.
return;
}
Uri data = intent.getData();
if (data != null) {
String pkg = data.getSchemeSpecificPart();
if (pkg != null) {
pkgList = new String[]{pkg};
}
}
}
if (pkgList != null && (pkgList.length > 0)) {
for (String pkg : pkgList) {
removeLocked(pkg);
mBroadcastStats.remove(pkg);
}
}
}
}
}
可见AlarmManagerService接受了ACTION_PACKAGE_RESTARTED广播,而且执行了removeLocked(pkg)
removeLocked()是做什么的呢?继续看源码:
public void removeLocked(String packageName) {
removeLocked(mRtcWakeupAlarms, packageName);
removeLocked(mRtcAlarms, packageName);
removeLocked(mElapsedRealtimeWakeupAlarms, packageName);
removeLocked(mElapsedRealtimeAlarms, packageName);
}
private void removeLocked(ArrayList alarmList,
String packageName) {
if (alarmList.size() <= 0) {
return;
}
// iterator over the list removing any it where the intent match
Iterator it = alarmList.iterator();
while (it.hasNext()) {
Alarm alarm = it.next();
if (alarm.operation.getTargetPackage().equals(packageName)) {
it.remove();
}
}
}
看到这里,大家应该明白了,removeLocked就是将对应package设置的所有类型的alarm都remove掉。
至于为什么google要加入这样的机制呢?我认为应该是出于系统安全的考虑。