天天看點

【qcom msm8953 android712】rtc 調試分析續

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

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

【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的标志就會是如下:

這個标志最終會傳入上面提到的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中申明如下:

至此,問題解決。

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

參考:

android安全問題(六) 搶先接收廣播 - 内因篇之廣播接收器注冊流程

Android開發之旅: Intents和Intent Filters(理論部分)

Intent比對規則以及解析架構深入分析

安卓廣播的底層實作原理

Android廣播發送接收機制

Direct Boot Mode

【Android話題-5.4應用相關】說說靜态廣播的注冊和收發原理

Android多使用者之UserManagerService源碼分析