源碼茶舍之FLAG_EXCLUDE_STOPPED_PACKAGES與廣播喚醒

發現

我們先隨便實現一個BroadcastReceiver,靜態註冊:

class TestReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.w("TEST-1", "onReceive ${intent?.action}")
    }
}
<receiver android:name=".TestReceiver">
    <intent-filter>
        <action android:name="com.xxx.yyy.action_test_receiver" />
    </intent-filter>
</receiver>

其他諸如Activity什麼的就不寫了哈,然後我們啓動這個測試App之後,用adb命令發一條廣播:

adb shell am broadcast -p com.xxx.yyy -a com.xxx.yyy.action_test_receiver

其中參數p表示廣播接收所在進程包名,a表示action。命令執行後終端會輸出:

Broadcasting: Intent { act=com.xxx.yyy.action_test_receiver flg=0x400000 pkg=com.xxx.yyy }
Broadcast completed: result=0

然後查看logcat,我們可以如願以償地看到onReceive中的log。接下來我們殺掉進程,任意方式均可,這裏我還是用adb命令,方便:

adb shell am force-stop com.xxx.yyy

殺進程後,再重複上面的廣播發送命令,就會發現收不到廣播了。這是爲什麼呢?表面看來這個問題很弱智,進程都死了當然不能再搞事。但實際上背後的邏輯還是值得探索的,系統也不是想象中那麼簡單地直接判斷進程死活然後決定廣播發送。

探祕

很顯然我們要搞明白廣播發送的底層邏輯。這裏主要分析Framework層面的源碼(基於Android 10),涉及到的關鍵類:

frameworks/base/core/java/android/content/Intent.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/services/core/java/com/android/server/pm/ComponentResolver.java
frameworks/base/services/core/java/com/android/server/IntentResolver.java

關於廣播發送的細節,可以參考此文:https://www.jianshu.com/p/c5323a22f3f3,雖然源碼版本不是最新,但基本邏輯差異不大,時序圖也畫得非常清晰。下面我們只針對文題簡單地分析關鍵路徑即可。

當我們樂呵呵地調用了 sendBroadcast 方法之後,會調用到AMS(ActivityManagerService)的 broadcastIntent 方法,進而調用內部的 broadcastIntentLocked 方法(此處插入一個題外話:很多同學可能經常見到源碼裏 xxxLocked 這種方法,這個locked是什麼意思呢?顧名思義就是加鎖咯,即你要調用這些locked後綴的方法時,必須保證是線程安全的,所以一般就會看到synchronize關鍵字,這也算是AOSP的編碼規範吧)。

由於 broadcastIntentLocked 方法非常長,我們只截取關鍵片段:

    final int broadcastIntentLocked(Intent intent/*省略18個參數*/) {
        intent = new Intent(intent);
		// ...
        // By default broadcasts do not go to stopped apps.
        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
        // ...
        // Figure out who all will receive this broadcast.
        List receivers = null;
        List<BroadcastFilter> registeredReceivers = null;
        // Need to resolve the intent to interested receivers...
        if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                 == 0) {
            receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
        }
        // ...
    }

可以看到,起手就是new Intent(intent),爲什麼不直接使用參數中的intent來進行後續操作呢?此處一個小細節可以看出源碼邏輯的謹慎,去查Intent的構造方法就知道,這是對入參的拷貝,避免被其他線程修改。
然後最關鍵的便是下面的 FLAG_EXCLUDE_STOPPED_PACKAGES ,註釋也寫得很清楚,即廣播發送會排除(exclude)已停止運行的進程

但我初次分析時看了半天沒發現是怎麼排除的,於是找這個flag引用的地方,在 Intent 源碼中發現一個方法,判斷該intent是否要排除已停止的進程:

    public boolean isExcludingStopped() {
        return (mFlags&(FLAG_EXCLUDE_STOPPED_PACKAGES|FLAG_INCLUDE_STOPPED_PACKAGES))
                == FLAG_EXCLUDE_STOPPED_PACKAGES;
    }

非常好,我們直接查 isExcludingStopped 方法的引用,發現在 IntentResolver 中:

    private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
            boolean debug, boolean defaultOnly, String resolvedType, String scheme,
            F[] src, List<R> dest, int userId) {
        // ...
        final boolean excludingStopped = intent.isExcludingStopped();
		// ...
        for (i=0; i<N && (filter=src[i]) != null; i++) {
            // ...
            if (excludingStopped && isFilterStopped(filter, userId)) {
                if (debug) {
                    Slog.v(TAG, "  Filter's target is stopped; skipping");
                }
                continue;
            }
        }
        // ...
    }

這是通過intent構造resolve列表的一個私有方法,代碼也非常清晰,此判斷邏輯 excludingStopped && isFilterStopped 過濾了最後要啓動的組件。其中 isFilterStopped 是真正判斷進程是否已停止的方法,而 excludingStopped 是我們剛纔傳入的flag對應的控制標識。所以要過濾掉已停止進程有兩個必要條件,一是這個進程真的死了,二是開發者要通過flag來聲明確實需要過濾(是不是超人性化哈哈哈)。

因此,我們也可以聲明不需要過濾,即給intent設置 FLAG_EXCLUDE_STOPPED_PACKAGES 的兄弟flag:FLAG_INCLUDE_STOPPED_PACKAGES (注意是 include),這樣在發送廣播時,即便是已停止的進程,也能接收到了。這就是上古時期通過廣播喚醒死亡進程的方法,現在基本上被各大ROM廠商給優化沒了,一般都是三方應用被禁,系統應用依然可以。

分析到此,其實只是有頭有尾,但沒有中間過程,broadcastIntentLocked 最終怎麼就調到了 buildResolveList 呢?
我們回到上面的 broadcastIntentLocked 方法,其中的 receivers 對象存儲的是靜態廣播的集合,registeredReceivers 則是動態廣播的集合。我們只看靜態廣播即可,它來自於 collectReceiverComponents 方法,此方法最後返回的receiver肯定是被過濾後的:

    private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
            int callingUid, int[] users) {
        // ...
        List<ResolveInfo> receivers = null;
        try {
            // ...
            for (int user : users) {
                // ...
                List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
                        .queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
                // ...
                if (newReceivers != null && newReceivers.size() == 0) {
                    newReceivers = null;
                }
                if (receivers == null) {
                    receivers = newReceivers;
                // ...
            }
        } catch (RemoteException ex) {
            // pm is in same process, this will never happen.
        }
        return receivers;
    }

這個方法內部邏輯較爲簡單,我們可以看到最終receivers的來源便是那個 queryIntentReceivers 方法,此方法實現在PMS(PackageManagerService)裏面:

    @Override
    public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
            String resolvedType, int flags, int userId) {
        return new ParceledListSlice<>(
                queryIntentReceiversInternal(intent, resolvedType, flags, userId,
                        false /*allowDynamicSplits*/));
    }

    private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
            String resolvedType, int flags, int userId, boolean allowDynamicSplits) {
        // ...
        synchronized (mPackages) {
            String pkgName = intent.getPackage();
            if (pkgName == null) {
                final List<ResolveInfo> result =
                        mComponentResolver.queryReceivers(intent, resolvedType, flags, userId);
                return applyPostResolutionFilter(
                        result, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
                        intent);
            }
            final PackageParser.Package pkg = mPackages.get(pkgName);
            if (pkg != null) {
                final List<ResolveInfo> result = mComponentResolver.queryReceivers(
                        intent, resolvedType, flags, pkg.receivers, userId);
                return applyPostResolutionFilter(
                        result, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
                        intent);
            }
            return Collections.emptyList();
        }
    }

最終receiver集合的查詢操作在私有方法中,由 mComponentResolver.queryReceivers 得來,似乎越來越接近真相了,馬上查看ComponentResolver:

    List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, int flags, int userId) {
        synchronized (mLock) {
            return mReceivers.queryIntent(intent, resolvedType, flags, userId);
        }
    }

    List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, int flags,
            List<PackageParser.Activity> receivers, int userId) {
        synchronized (mLock) {
            return mReceivers.queryIntentForPackage(intent, resolvedType, flags, receivers, userId);
        }
    }

這裏正好對應上面的兩個不同情況下的調用,它們最終都會調用到 queryIntent 方法,此方法在ComponentResolver的一個靜態內部類ActivityIntentResolver中實現:

    private static final class ActivityIntentResolver
            extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
        @Override
        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
                boolean defaultOnly, int userId) {
            // ...
            return super.queryIntent(intent, resolvedType, defaultOnly, userId);
        }

        List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
                int userId) {
            // ...
            return super.queryIntent(intent, resolvedType,
                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
                    userId);
        }
        // ...
    }

內部類的中的 queryIntent 方法只是做了一些參數處理,進一步調用的是父類的實現,這個父類也就是起初我們提到的 IntentResolver

    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
            int userId) {
        // ...
        if (firstTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
                    scheme, firstTypeCut, finalList, userId);
        }
        if (secondTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
                    scheme, secondTypeCut, finalList, userId);
        }
        if (thirdTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
                    scheme, thirdTypeCut, finalList, userId);
        }
        if (schemeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
                    scheme, schemeCut, finalList, userId);
        }
        filterResults(finalList);
        sortResults(finalList);
		// ...
        return finalList;
    }

看上面的 buildResolveList 方法,照應了開頭分析的結果。最終返回的 finalList 也就是過濾之後的receiver集合。

總結

  • 在廣播發送的前序步驟(位於AMS)裏,通過 intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); 設置了標識,以聲明廣播不發給已停止的進程,層層調用後最終在 IntentResolverbuildResolveList 方法中實現過濾。
  • 在原生Android的設計邏輯中,若要突破上述限制,在 sendBroadcast 之前,給intent添加 flag:FLAG_INCLUDE_STOPPED_PACKAGES 即可。但鑑於各ROM廠商的正負優化,這個操作已經不適用了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章