【qcom msm8953 android712】rtc 調試分析續

ps:問題描述,在進行系統裁剪以及引導加速後導致設備rtc功能異常–timeservice服務無法開機時被廣播帶起,導致rtc set time無法在網絡更新時間後執行。

文章續自:【qcom msm8953 android712】rtc 調試分析

在這裏插入圖片描述
上圖是本平臺網絡更新並帶RTC功能的大概框圖,其宏觀大概工作如下:

  1. 網絡時間更新,觸發Android層的systime set;
  2. Android層time set後觸發TIME_SET廣播的產生和發送;
  3. TimeService靜態接收TIME_SET廣播並執行相應操作;
  4. TimeService和time_daemon進行交互將時間設置並實現rtc set time至kernel rtc driver。

這裏主要分析下從NetTime到TimeServcie的一個工作流程,涉及代碼如下所示:
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/services/core/java/com/android/server/am/UserController.java
frameworks/base/services/core/java/com/android/server/am/UserState.java
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
frameworks/base/services/core/java/com/android/server/IntentResolver.java
frameworks/base/core/java/android/os/UserManagerInternal.java
frameworks/base/core/java/android/content/IntentFilter.java
frameworks/base/core/java/android/content/pm/PackageParser.java

基於解決本問題的原因是TimeService沒有正常啓動(開機階段),那麼帶着這個問題去分析。

Q:TimeService沒有正常啓動的原因?
A:沒收到廣播。

Q:爲什麼沒收到廣播?
A:廣播沒發或者發了沒收到(被過濾或者丟棄或者攔截)。

那麼事關廣播發送接收相關機制,那麼先從這裏開始。

1.先確認廣播是否發出,如何確認,很簡單,找到發送廣播的地方,根據前面???的分析,知道廣播發送者是AlarmManagerService 的 AlarmThread線程,至於它是怎麼工作的,這裏不做分析。
如下:

private class AlarmThread extends Thread{
	//....
	public void run(){
		//...
		Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                                | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
		//...
	}
	//....
}

這裏可以很直接的看到AlarmManagerService.AlarmThread.run() 通過 sendBroadcastAsUser 發送了攜帶ACTION_TIME_CHANGED信息的intent,即action = android.intent.action.TIME_SET。

結論:廣播確認已經“發出”。並且攜帶了FLAG_RECEIVER_REPLACE_PENDING 和 FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 標誌信息,UserHandle.ALL類型的接收者。關於標誌信息和UserHandle.ALL的相關可自行查閱資料。

2.跟蹤至廣播發送處理代碼–broadcastIntentLocked
broadcastIntentLocked方法位於AMS中。通過broadcastIntent調用(不做闡述),如下:
注意,該方法代碼可以所非常之多,這裏只將與本文的部分留下

final int broadcastIntentLocked(ProcessRecord callerApp,
            String callerPackage, Intent intent, String resolvedType,
            IIntentReceiver resultTo, int resultCode, String resultData,
            Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
            boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
            // ...
            receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
            // ...
}

這裏我只留下了collectReceiverComponents方法調用的代碼,將broadcastIntentLocked方法的工作內容列如下,可參考源碼閱讀:

  1. 取得intent對象,並添加FLAG_EXCLUDE_STOPPED_PACKAGES標誌;
  2. 驗證廣播的合法性和合理性;
  3. 處理action;
  4. 處理sticky類型廣播;
  5. 判斷調用者想讓廣播送給所有用戶還是特定用戶;
  6. 找出廣播的接收者,包括動態註冊和靜態註冊;
  7. 分別將合法合理的動態廣播和靜態廣播入隊–BroadcastQueue;
  8. 執行廣播發送調度。

ps:這裏說明下,該方法裏面用了大量的篇幅去驗證廣播的合法性合理性,權限等問題。另外,該方法實際上只是對廣播做了分類處理和驗證處理以及入隊處理,真正發送廣播的操作實際還在後面,也就是上面提到廣播入隊後的發送調度。

回到上面提到的collectReceiverComponents方法,該方法是針對靜態廣播接收者的一個整理收集。
它的返回類型是一個ResolveInfo類型的集合。
如下:

private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
            int callingUid, int[] users) {
            // ...
            List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
                        .queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
            // ...
}

可從上面看出,實際上返回的結果是從queryIntentReceivers方法中得到的,而該方法又是通過getPackageManager()得到,即實現在PMS中, 跟蹤如下:

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

從上可以得出以下兩點:

  1. 該方法是重寫的;
  2. 該方法的結果實際是從queryIntentReceiversInternal得到;

PMS繼承了IPackageManager.Stub繼而實現了queryIntentReceivers方法,因此在AMS中可以通過getPackageManager()調用該方法。
queryIntentReceiversInternal方法實現如下所示:

private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
            String resolvedType, int flags, int userId) {
        // ...
        flags = updateFlagsForResolve(flags, userId, intent);
        // ...
        // reader
        synchronized (mPackages) {
            String pkgName = intent.getPackage();
            if (pkgName == null) {
                return mReceivers.queryIntent(intent, resolvedType, flags, userId);
            }
            final PackageParser.Package pkg = mPackages.get(pkgName);
            if (pkg != null) {
                return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
                        userId);
            }
            return Collections.emptyList();
        }
        // ...
}

可以從上分析得出如下結論:

  1. 調用updateFlagsForResolve方法得到新的標誌信息;
  2. 查詢intent相應的信息又根據是否存在包名來走不同的處理路線,沒有包名則調用queryIntent方法,否則通過PackageParser去解析,解析成功則通過queryIntentForPackage得到相關信息,否則返回空列表。

這裏先看第一點:updateFlagsForResolve方法如何實現:
updateFlagsForResolve是通過層層調用至PMS.updateFlags方法,該方法的作用是根據當前用戶的加密狀態更新給定標誌。也就是說,這裏的更新標誌信息和用戶的加密狀態扯上關係了,這裏留個心眼,後面有用。
updateFlags實現如下:

    /**
     * Update given flags based on encryption status of current user.
     */
    private int updateFlags(int flags, int userId) {
        if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                | PackageManager.MATCH_DIRECT_BOOT_AWARE)) != 0) {
            // Caller expressed an explicit opinion about what encryption
            // aware/unaware components they want to see, so fall through and
            // give them what they want
        } else {
            // Caller expressed no opinion, so match based on user state
            if (getUserManagerInternal().isUserUnlockingOrUnlocked(userId)) {
                flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
            } else {
                flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE;
            }
        }
        return flags;
    }

    private UserManagerInternal getUserManagerInternal() {
        if (mUserManagerInternal == null) {
            mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
        }
        return mUserManagerInternal;
    }

可以看到最終的返回結果是通過getUserManagerInternal方法得到的對象攜帶的isUserUnlockingOrUnlocked方法得到的,getUserManagerInternal得到的對象是UserManagerInternal對象,可以看到是通過LocalServices註冊的,跟到UserManagerInternal對象引用的isUserUnlockingOrUnlocked方法看看發現該方法是個抽象方法,由子類實現,那麼實現了該方法是UserManagerService(該服務是爲UserManager服務)的內部類LocalService,LocalService繼承了UserManagerInternal

        @Override
        public boolean isUserUnlockingOrUnlocked(int userId) {
            synchronized (mUserStates) {
                int state = mUserStates.get(userId, -1);
                return (state == UserState.STATE_RUNNING_UNLOCKING)
                        || (state == UserState.STATE_RUNNING_UNLOCKED);
            }
        }

這裏又發現最終是通過mUserStates取userId對應的鍵值得到的,真是繞的深啊。也就是說如果只要當前用戶不是正在解鎖狀態和已經解鎖狀態的其中一個,那麼isUserUnlockingOrUnlocked的返回值就是true。
最終PMS.updateFlagsForResolve的標誌就會是如下:

flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;

這個標誌最終會傳入上面提到的PMS.queryIntentReceiversInternal.mReceivers.queryIntent 或者 queryIntentForPackage方法中,那麼問題來了mReceivers是什麼?

    // All available receivers, for your resolving pleasure.
    final ActivityIntentResolver mReceivers =
            new ActivityIntentResolver();

可以看到mReceivers 是 ActivityIntentResolver類型,實際上它是存放所有廣播類型信息的地方。
ActivityIntentResolver是PMS的內部類,繼承了IntentResolver,實現了它的抽象方法,也就是上面提到的queryIntent、queryIntentForPackage等方法:

    final class ActivityIntentResolver
            extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
            // ...
            return super.queryIntent(intent, resolvedType, defaultOnly, userId);
            // ...
            return super.queryIntent(intent, resolvedType,
                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
            // ...
            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
    }

可以看到,它們都調用了父類的相應方法,也就是實現是在IntentResolver類中:

    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
            int userId) {
            // ...
            ArrayList<R> finalList = new ArrayList<R>();
            // ...
            F[] firstTypeCut = null;
	        F[] secondTypeCut = null;
	        F[] thirdTypeCut = null;
	        F[] schemeCut = null;
            // ...
            buildResolveList(...);
            // ...
    }

這裏實際上是加快了對廣播的查找(分類查找,而不是全部遍歷查找),最終都是通過buildResolveList方法得到數據存在了finalList數組中返回上去。
buildResolveList方法實現如下:

    private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
            boolean debug, boolean defaultOnly,
            String resolvedType, String scheme, F[] src, List<R> dest, int userId) {
            // ...
            match = filter.match(action, resolvedType, scheme, data, categories, TAG);
            if (match >= 0) {
                if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
                    // ...
                    final R oneResult = newResult(filter, match, userId);
                    if (oneResult != null) {
                        dest.add(oneResult);
                        // ...
                    }
                } else {
                    hasNonDefaults = true;
                }
            }
            // ...
    }

該方法重點就是filter.match() (IntentFilter類型),它找到是否有匹配的廣播接收者。如果大於0並且newResult方法得到的新結果不爲空,那麼就加入dest,這個dest就是從queryIntent傳進來的finalList數組。最終反饋到AMS.collectReceiverComponents的返回值中,得到靜態廣播對應的接收者集合。

ps:

updateFlagsForResolve -> updateFlagsForComponent -> updateFlags

那麼問題來了,匹配匹配總要有二者吧,匹配的源從哪裏來?這裏談的是靜態廣播的接收者查找匹配,那麼就得看看靜態廣播是如何被加入到filter中去的。

靜態廣播的註冊流程
PMS處理靜態廣播的註冊事宜,即PMS處理包括對AndroidManifest.xml的解析工作,也就是靜態廣播在xml中註冊的原因,因爲安裝包或者服務等時候我們都在xml中聲明相關信息。

PMS在構造的時候,會對指定的目錄進行掃描安裝,如下:

    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
            // ...
            for (File f : RegionalizationDirs) {
                    File RegionalizationSystemDir = new File(f, "system");
                    // Collect packages in <Package>/system/priv-app
                    scanDirLI(new File(RegionalizationSystemDir, "priv-app"),
                            PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR
                            | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
                    // Collect packages in <Package>/system/app
                    scanDirLI(new File(RegionalizationSystemDir, "app"),
                            PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,
                            scanFlags, 0);
                    // Collect overlay in <Package>/system/vendor
                    scanDirLI(new File(RegionalizationSystemDir, "vendor/overlay"),
                            PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,
                            scanFlags | SCAN_TRUSTED_OVERLAY, 0);
                }
            // ...
    }

這裏可以看到分別對四個路徑的目錄進行了掃描處理,那是如何處理的?跟進去看看:

private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
	// ...
	Runnable scanTask = new Runnable() {
                public void run() {
                		// ...
                        scanPackageTracedLI(ref_file, ref_parseFlags | PackageParser.PARSE_MUST_BE_APK,
                                ref_scanFlags, ref_currentTime, null);
                        // ...
                    }
                    // ...
                }
            };
	// ...
}

可以看到在scanDirLI中起了一個線程(掃描這麼多目錄確實需要線程),而真正的執行者是scanPackageTracedLI。
這裏注意了:scanPackageTracedLI有兩個方法,注意它們是不一樣的。

    /**
     *  Traces a package scan.
     *  @see #scanPackageLI(File, int, int, long, UserHandle)
     */
    private PackageParser.Package scanPackageTracedLI(File scanFile, final int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
            // ...
            return scanPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);
            // ...
    }
private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
            final int policyFlags, int scanFlags, long currentTime, UserHandle user)
                    throws PackageManagerException {
                    // ...
                    // Scan the parent
            scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user);
            // Scan the children
            final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
            for (int i = 0; i < childCount; i++) {
                PackageParser.Package childPkg = pkg.childPackages.get(i);
                scanPackageLI(childPkg, policyFlags,
                        scanFlags, currentTime, user);
            }
                    // ...
}

這裏經過分析,沒有實際驗證,只驗證了安裝的情況,應該是構造的時候去掃描走的前者,後面安裝的時候掃描解析走的後者,這樣導致二者雖然都是調用了scanPackageLI方法,卻實際上走到線路確是不同的。
筆者分析rtc的原因是走了後者,這裏直接跟蹤後者:
後者最終是到了PackageParser.Package scanPackageLI

    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
        boolean success = false;
		// ...
            final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,
                    currentTime, user);
            success = true;
            return res;
        // ...
        }
    }

這裏也是通過另一個方法scanPackageDirtyLI來得到結果,這個方法也是相當的長,實際上它就是對xml文件解析的實際者:

    private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
            final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
            throws PackageManagerException {
            // ...
            for (i=0; i<N; i++) {
                PackageParser.Activity a = pkg.receivers.get(i);
                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        a.info.processName, pkg.applicationInfo.uid);
                mReceivers.addActivity(a, "receiver");
                if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(' ');
                    }
                    r.append(a.info.name);
                }
            }
            // ...
            }

它將xml文件中的receiver標籤的信息剝離出出來,這個receiver信息就是靜態註冊時候的廣播action。
將receiver信息添加進fliter中是通過mReceivers.addActivity方法,這個mReceivers是不是在哪見過?沒錯,就是前面提到過的,這也就進一步證明了xml文件中的reciver信息都存在這個類型對象裏面。
看看addActivity方法做了什麼?

public final void addActivity(PackageParser.Activity a, String type) {
	// ...
	addFilter(intent);
	// ...
}

它就直接調用了addFilter方法,該方法在抽象類IntentResolver中實現。就是將指定intent加入IntentResolver中。這樣一加入,就保證,我們在AMS.collectReceiverComponents方法中能夠得到相對應的匹配了,這樣就能讓廣播發送者將廣播發送至對應的接收者了。

通過上面一跟蹤,發現設備TimeService沒啓動的原因是PMS.updateFlagsForResolve中更新的標誌異常了(這個異常是因爲mUserStates.get方法得到的用戶狀態是0,也就是正在BOOTING狀態),導致後面傳入給mReceivers.queryIntent方法的flags異常,最終導致AMS中針對TIME_SET的廣播接收者的收集裏沒有TimeService,這樣靜態廣播就無法帶動TimeService的進程啓動了。這樣就收不到TIME_SET的廣播了。

原因爲什麼這樣,後面抓log,查資料發現Android7.0以後引入了一種啓動模式–Direct Boot Mode,講的是針對多用戶的安全而產生的,開機後用戶會有幾種狀態變化,包括前面提到的解鎖中和已解鎖的狀態等等。

而在有些應用是不能在某些狀態前啓動的(在查詢接收者匹配的過程中會被無視掉),也就是說TimeService必須保證能在解鎖中或者已解鎖之前能夠啓動。這樣就需要在xml中申明如下:

<application android:directBootAware="true">

至此,問題解決。

ps:問題源於系統做過一次裁剪,啓動優化加速,導致TIME_SET廣播過早的發出,而用戶還未解鎖或者解鎖中,這樣丟失TimeService丟失啓動機會。

參考:
android安全問題(六) 搶先接收廣播 - 內因篇之廣播接收器註冊流程
Android開發之旅: Intents和Intent Filters(理論部分)
Intent匹配規則以及解析框架深入分析
安卓廣播的底層實現原理
Android廣播發送接收機制
Direct Boot Mode
【Android話題-5.4應用相關】說說靜態廣播的註冊和收發原理
Android多用戶之UserManagerService源碼分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章