編輯:關於Android編程
也許一些使用alarmmanager做定時任務的同學遇到過這樣的問題:設定alarm後,進入設置-->應用程序管理-->強行停止app後,定時任務就失效了。
簡單的講就是:force stop會導致alarm失效。
最典型的例子就是我碰到過的一個bug,使用android手機的時鐘app設置一個鬧鐘,然後進入設置-->應用程序管理裡面,將時鐘這個app force stop掉,結果鬧鐘就不響了。
其實這不是bug,這是android系統的新加入的機制。下面我來詳細分析一下來龍去脈。
1. 在設置的應用程序管理裡面強行停止app:
這裡會最終會調用到 ActivityManagerService的forceStopPackageLocked()
源代碼如下:
[java]
<SPAN style="FONT-SIZE: 18px"> 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);
}</SPAN>
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,這個廣播大有文章。
2. 再看看AlarmManagerService.java的代碼,可以看一個內部類UninstallReceiver
源代碼如下:
[java]
<SPAN style="FONT-SIZE: 18px">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);
}
}
}
}
}</SPAN>
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()是做什麼的呢?繼續看源碼:
[java]
<SPAN style="FONT-SIZE: 18px"> 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();
}
}
}</SPAN>
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掉。
看到這裡大家應該知道為什麼自己設置的alarm會不起作用了吧?
思考:
為什麼google要加入這樣的機制呢?
應該是出於系統安全的考慮,google在4.0系統中在安全方面做了很多努力。
很多病毒程序都不希望自己的進程被用戶強行停止,希望自己的病毒程序可以一直運行,而常見的方式就是通過設置alarm,在病毒進程被殺死後,通過定時發送廣播來拉起病毒進程,來實現病毒進程的重新啟動。
google也正是看到了這個一點,所以加入了forceStopPackage的這一機制,讓用戶能夠有機會干掉病毒進程。
android系統的安全性一直是android系統的短板,google在提升系統安全性方面也在不斷努力,在之後的文章中,我會再進行介紹。
最近幾天寫了一個簡單的朋友圈程序,包含了朋友圈的列表實現,視頻的錄制、預覽與上傳,圖片可選擇拍照或者從相冊選取,從相冊選取可以一次選擇多張照片,並且限制照片的張數。大致也
從一開始寫Android程序,就被告知這些常識1.長度寬度的數值要使用dp作為單位放入dimens.xml文件中2.字體大小的數值要使用sp作為單位,也放入dimens.
Android 確實是不允許在子線程中進行UI 操作。但有些時候,我們必須在子線程裡去執行一些耗時任務,然後根據任務的執行結果來更新相應的UI 控件,這該如何是好呢?對於
可移動頁面MoveActivity滑出式菜單從界面上看,像極了一個水平滾動視圖HorizontalScrollView,當然也可以使用HorizontalScrollVi