2012年11月13日 星期二

[Android] 3.1 之後 Broadcast 行為的改變

前些陣子聽到實驗室學妹跟我說她自己寫的 App 收不到 BOOT_COMPLETED,我還嗤之以鼻的表示一定收的到啊~結果今天自己在 trace code 的時候就被打臉了 ... Q__Q
(學妹表示:你太嫩了!)

In frameworks/base/services/java/com/android/server/am/ActivityManagerService.java :

    private final int broadcastIntentLocked(ProcessRecord callerApp,
            String callerPackage, Intent intent, String resolvedType,
            IIntentReceiver resultTo, int resultCode, String resultData,
            Bundle map, String requiredPermission,
            boolean ordered, boolean sticky, int callingPid, int callingUid) {
        intent = new Intent(intent);

        // By default broadcasts do not go to stopped apps.
        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

        ... (略)

就是由於 Android 在 broadcast 的時候將 intent 加上了預設的 flag (Intent.FLAG_EXCLUDE_STOPPED_PACKAGES),因此,在 Stopped 階段的 packages 基本上收不到 intent,但是 3rd party app 可以自己加入 Intent.FLAG_INCLUDE_STOPPED_PACKAGES 這個 flag 讓 Stopped 階段的程式可以收到廣播內容!

因此,如果你不一想讓 App 收 System broadcast 的話,可以到管理選單上「Force Stop」,這樣就不會收到 broadcast 囉。同樣的,如果 App 沒有 Activity 的話,目前看起來就無法讓他自己跑起來了 ... ?

In frameworks/base/service/java/com/android/server/am/ActivityManagerService.java :


    public void forceStopPackage(final String packageName) {
        if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                != PackageManager.PERMISSION_GRANTED) {
            String msg = "Permission Denial: forceStopPackage() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }

        long callingId = Binder.clearCallingIdentity();
        try {
            IPackageManager pm = AppGlobals.getPackageManager();
            int pkgUid = -1;
            synchronized(this) {
                try {
                    pkgUid = pm.getPackageUid(packageName);
                } catch (RemoteException e) {
                }
                if (pkgUid == -1) {
                    Slog.w(TAG, "Invalid packageName: " + packageName);
                    return;
                }
                forceStopPackageLocked(packageName, pkgUid);
                try {
                    pm.setPackageStoppedState(packageName, true);
                } catch (RemoteException e) {
                } catch (IllegalArgumentException e) {
                    Slog.w(TAG, "Failed trying to unstop package "
                            + packageName + ": " + e);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }

在這邊有個 forceStopPackage() method,會呼叫 PackageManagerService.setPackageStoppedState,這個 method 會呼叫 Setting.setPackageManagerServiceLPw,然後把相對應 PackageName 的 PackageSetting 設定成 stopped ...

Android Developer 上有提到:http://developer.android.com/about/versions/android-3.1.html(但是我之前都沒注意到啊啊啊啊啊....)。

這個教訓告訴我們「要注意看 API Document」!


BTW,我這邊貼 code 是用 http://hilite.me/ 這個網站產生的。