背景
之前給手機淘寶做了個準點提醒的中間件,類似鬧鈴提醒,使用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的
2、對於alarmManager的影響
我們先來看看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<Alarm> alarmList,
String packageName) {
if (alarmList.size() <= 0) {
return;
}
// iterator over the list removing any it where the intent match
Iterator<Alarm> it = alarmList.iterator();
while (it.hasNext()) {
Alarm alarm = it.next();
if (alarm.operation.getTargetPackage().equals(packageName)) {
it.remove();
}
}
}
看到這裏,大家應該明白了,removeLocked就是將對應package設置的所有類型的alarm都remove掉。
至於爲什麼google要加入這樣的機制呢?我認爲應該是出於系統安全的考慮。