Android設置中“強行停止”詳解

Android設置中“強行停止”詳解

最近工作上遇到了廣播接受不到的問題,查看了《Android 開發藝術探索》一書中關於廣播的發送和接受的章節(P356-P362)。其中(P358)介紹了從Android 3.1 之後廣播的一些區別 。

從 Android 3.1 開始,系統爲所有的廣播都默認添加了FLAG_EXCLUDE_STOPPED_PACKAGES 標誌。所有處於停止狀態的應用將無法接受到該標誌的廣播。注意,只有兩種情況下應用纔會處於停止狀態:
  • 應用安裝後未運行
  • 應用被手動(設置-應用-強制停止)或者其他應用強制停止了
如果需要啓動處於停止狀態的應用,則只要爲Intent添加 FLAG_INCLUDE_STOPPED_PACKAGES 標記即可。

技術小黑屋的這個解析的確很完整,但是在問答環節我看到這樣一個問題:

提問:如果我的程序沒有activity只有一個receiver,我改如何激活才能接收到正常的廣播intent呢
回答:實際上,如果是上面所述的情況,該應用在安裝之後不是處於停止狀態,因爲它沒有任何用戶可以直接點擊的行爲去將它移除停止狀態.你可以正常接收廣播intent,除非你人爲地將它強制停止.
在頁面的最底端我也看到了有人這樣留言:
  • 你好,我寫了一個只有receiver和service的app,安裝後是無法接受系統廣播的,後來用了一個自定義的廣播啓動它過一次後,纔開始接受系統廣播了
  • 我認爲你的那個app安裝之後不是處於停止狀態(關於驗證,你可以查看系統設置中的應用程序,那裏面能準確的看出是否爲停止狀態),另外我感覺你應該確認一下系統廣告是否發出,因爲你後面的說,自定義的廣告可以接收即表明這個程序處於非停止狀態。希望可以確認一下哈。
看到這個回答,我的內心是抗拒的。於是我自己寫了個Demo測試了一下,非常簡單的代碼:

AndroidManifest.xml:

[代碼]xml代碼:

?
1
2
3
4
5
6
<receiver android:name="com.roger.BroadcastTest.Receiver" android:enabled="true" android:exported="true">
      <intent-filter>
          <action android:name="android.intent.action.BOOT_COMPLETED">
          <action android:name="com.roger.broadcast.test">
      </action></action></intent-filter>
   </receiver>
JAVA:

[代碼]java代碼:

?
1
2
3
4
5
6
7
8
public class Receiver extends BroadcastReceiver {
 
    @Override
    public void onReceive(Context arg0, Intent arg1) {
        // TODO Auto-generated method stub
        Log.i("Tag", "receive:" + arg1.getAction());
    }
}
應用中就註冊了一個廣播接收器,安裝後直接重啓,果然並沒有收到開機成功的廣播。然而設置中查看,該應用並沒有處於停止狀態。所以我猜想,安裝後未啓動的應用(未啓動過該應用中任何四大組件),雖然在設置中顯示未停止,但在framework中,應該是處於停止狀態的。

來,開始苦逼的看代碼之路,我想要搞清楚的有兩點:
  • framework中廣播分發者如何區分廣播接收器是否處於停止狀態
  • framework中如何將應用從停止狀態切換到非停止狀態
開始第一點,對於廣播分發的過程

我們從Intent中開始,介紹一個framework源碼搜索網站:http://androidxref.com/,我們選4.4的源碼,搜索FLAG_INCLUDE_STOPPED_PACKAGES,點擊鏈接跟進到Intent源碼中,看到這樣一個方法:

[代碼]java代碼:

?
1
2
3
4
5
/** @hide */
    public boolean isExcludingStopped() {
        return (mFlags&(FLAG_EXCLUDE_STOPPED_PACKAGES|FLAG_INCLUDE_STOPPED_PACKAGES))
                == FLAG_EXCLUDE_STOPPED_PACKAGES;
    }
很明顯,這個方法返回的是發送的廣播是否需要包含處於停止狀態的應用。再跟進改方法的使用者,跟進到IntentResolver.java中:

[代碼]java代碼:

?
1
final boolean excludingStopped = intent.isExcludingStopped();
這個變量在哪使用呢?

[代碼]java代碼:

?
1
2
3
4
5
6
if (excludingStopped && isFilterStopped(filter, userId)) {
                if (debug) {
                    Slog.v(TAG, "  Filter's target is stopped; skipping");
                }
               continue;
           }
在buildResoleList中,Good,看到log我們似乎離成功不遠了,那麼這個isFilterStopped方法肯定是判斷應用是否處於停止狀態的,再更進:

[代碼]java代碼:

?
1
2
3
4
5
6
7
8
/**
 * Returns whether the object associated with the given filter is
 * "stopped," that is whether it should not be included in the result
 * if the intent requests to excluded stopped objects.
 */
protected boolean isFilterStopped(F filter, int userId) {
    return false;
}

什麼鬼~?直接返回false?逗我嗎?..再認真看下注釋,大概意思是傳到這個方法的filter必須已經排除了’stopped’ 的應用,這?應該是被重寫了,繼續在全局搜索該方法名,果然,在PackageManagerService中,看到了如下代碼:

[代碼]java代碼:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@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;
}

這下無誤了吧!再看到380行:

[代碼]java代碼:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
// All available activities, for your resolving pleasure.
    final ActivityIntentResolver mActivities =
            new ActivityIntentResolver();
 
    // All available receivers, for your resolving pleasure.
    final ActivityIntentResolver mReceivers =
            new ActivityIntentResolver();
 
    // All available services, for your resolving pleasure.
    final ServiceIntentResolver mServices = new ServiceIntentResolver();
 
    // All available providers, for your resolving pleasure.
    final ProviderIntentResolver mProviders = new ProviderIntentResolver();

379行註釋:所有有效的receivers都被這個ActivityIntentResolver解析,Good!現在我們看到主要是通過PackageSetting這個類中的getStopped方法來判斷應用是否處於停止狀態。那麼有哪些動作將會對這個值有影響呢?看到PackageSettingBase中關於設定改值的方法:

[代碼]java代碼:

?
1
2
3
void setStopped(boolean stop, int userId) {
     modifyUserState(userId).stopped = stop;
}

再通過查詢調用者,我們會發現主要有以下代碼將影響改值:

從停止到非停止狀態(成功啓動該應用中的四大組件即可)
ActivityStack中

[代碼]java代碼:

?
1
2
3
4
5
6
// Launching this app's activity, make sure the app is no longer
// considered stopped.
try {
   AppGlobals.getPackageManager().setPackageStoppedState(
            next.packageName, false, next.userId); /* TODO: Verify if correct userid */
}

ActiveServices中
       

[代碼]java代碼:

?
01
02
03
04
05
06
07
08
09
10
11
// Service is now being launched, its package can't be stopped.
        try {
            AppGlobals.getPackageManager().setPackageStoppedState(
                    r.packageName, false, r.userId);
        }
ActivityManagerService中
                        // Content provider is now in use, its package can't be stopped.
                        try {
                            AppGlobals.getPackageManager().setPackageStoppedState(
                                    cpr.appInfo.packageName, false, userId);
                        }

BroadcastQueue中
            

[代碼]java代碼:

?
1
2
3
4
5
// Broadcast is being executed, its package can't be stopped.
            try {
                AppGlobals.getPackageManager().setPackageStoppedState(
                        r.curComponent.getPackageName(), false, UserHandle.getUserId(r.callingUid));
            }

從非停止狀態到停止狀態(初始化以及設置中點擊強制停止)
在/frameworks/base/services/java/com/android/server/pm/Settings.java 中 有對 “packages-stopped.xml”文件的讀操作,未找到寫操作,但我相信在應用安裝成功後會加入”packages-stopped.xml”文件中
設置中點擊強制停止 /packages/apps/Settings/src/com/android/settings/applications/ProcessStatsDetail.java:
    

[代碼]java代碼:

?
1
2
3
4
5
6
private void killProcesses() {
        ActivityManager am = (ActivityManager)getActivity().getSystemService(
                Context.ACTIVITY_SERVICE);
        am.forceStopPackage(mEntry.mUiPackage);
       checkForceStop();
    }

至此,通過源碼我們已經瞭解,如果新安裝的應用,未曾成功啓動過四大組件,默認是處於停止狀態的,這也是Google對系統的保護想要達到的效果。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章