APK安裝流程詳解15——PMS中的新安裝流程下(裝載)補充

APK安裝流程系列文章整體內容如下:

本片文章的主要內容如下:

  • 1、PackageParser#setSeparateProcesses(String[] procs)方法解析
  • 2、PackageManagerService#shouldCheckUpgradeKeySetLP(PackageSetting, int) 方法解析
  • 3、PackageManagerService#checkUpgradeKeySetLP(PackageSetting, PackageParser.Package) 方法解析
  • 4、PackageManagerService#verifySignaturesLP(PackageSetting, PackageParser.Package)方法解析
  • 5、PackageDexOptimizer#performDexOp(PackageParser.Package, String[], String[], boolean, String,CompilerStats.PackageStats)方法解析
  • 6、args.doRename(res.returnCode, pkg, oldCodePath)方法解析
  • 7、startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg)方法解析

一、 PackageParser#setSeparateProcesses(String[] procs)方法解析

代碼位置在PackageManagerService的installPackageLI方法里面會調用到,代碼如下:
PackageManagerService.java

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
        ...
        PackageParser pp = new PackageParser();
        pp.setSeparateProcesses(mSeparateProcesses);
        pp.setDisplayMetrics(mMetrics);
        ...
    }

可以看到,這里構造了一個PackageParser對象,然后設置了mSeparateProcesses屬性。

mSeparateProcesses是一個數組,表示獨立的進程名列表,這個參數是在PackageManagerService的構造函數中調用到,以后會分析一下函數是在什么地方調用,所以看mSeparateProcesses的獲取過程:

代碼在PackageManagerService.java 1838行

        String separateProcesses = SystemProperties.get("debug.separate_processes");
        if (separateProcesses != null && separateProcesses.length() > 0) {
            if ("*".equals(separateProcesses)) {
                mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
                mSeparateProcesses = null;
                Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
            } else {
                mDefParseFlags = 0;
                mSeparateProcesses = separateProcesses.split(",");
                Slog.w(TAG, "Running with debug.separate_processes: "
                        + separateProcesses);
            }
        } else {
            mDefParseFlags = 0;
            mSeparateProcesses = null;
        }    

從系統屬性中讀取debug.separate_processes屬性,如果改屬性返回值不為空,表示設置了該屬性,否則系統未設置改屬性,如果值等于則mSeparateProcesses為空,如果不為,則逗號分隔該字符串,解析每個獨立的進程名,那個debug.separate_processes究竟有什么用?

那我們就來看下debug.separate_processes的作用:

separate_processes可以讓應用程序的組件運行在自己的進程里面,separate_processes一般有兩種設置:

  • 如果設置了"setprop debug.separate_processes",則將設置這個每個包中的每個進程。
  • 如果設置"setprop debug.separate_processes 'com.google.process.content,com.google.android.samples' "它只會影響項目清單中的指定進程("com.google.process.content,com.google.android.samples")。或者在AndroidManifest里面顯式的設置"android:process"標記。
PS:雖然這樣可以將一個進程拆分出來,或者多個進程組合成一個進程(他們必須來自同一個包)。它會強制所有受影響的組件在自己的.apk運行。

二、 PackageManagerService#shouldCheckUpgradeKeySetLP(PackageSetting, int) 方法解析

這個方法在PackageManagerService的installPackageLI方法里面被調用。代碼在代碼在PackageManagerService.java 12346行

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
        ...
        if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
             ...
        }
        ...
   }

那我們來看下shouldCheckUpgradeKeySetLP這個方法的內部實現
代碼在PackageManagerService.java 11807行

    private boolean shouldCheckUpgradeKeySetLP(PackageSetting oldPs, int scanFlags) {
        // Can't rotate keys during boot or if sharedUser.
         // 判斷是否可以進行升級驗證的條件
        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.sharedUser != null
                || !oldPs.keySetData.isUsingUpgradeKeySets()) {
            return false;
        }
        // app is using upgradeKeySets; make sure all are valid
        KeySetManagerService ksms = mSettings.mKeySetManagerService;
        // 獲取老的keySet數組
        long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
        for (int i = 0; i < upgradeKeySets.length; i++) {
            // 遍歷keySet數組,檢查是否有對應的密鑰集
            if (!ksms.isIdValidKeySetId(upgradeKeySets[i])) {
                // 如果對應的密鑰集合,說明簽名密鑰有問題,則返回false
                Slog.wtf(TAG, "Package "
                         + (oldPs.name != null ? oldPs.name : "<null>")
                         + " contains upgrade-key-set reference to unknown key-set: "
                         + upgradeKeySets[i]
                         + " reverting to signatures check.");
                return false;
            }
        }
     // 如果所有的密鑰都能對上,說明密鑰沒有問題,則返回true
        return true;
    }

通過注釋我們知道,方法主要檢查密鑰集合是否和老版本的一致,如果不一致,則返回false。如果一致則返回true。

三、 PackageManagerService#checkUpgradeKeySetLP(PackageSetting, PackageParser.Package) 方法解析

這個方法在PackageManagerService的installPackageLI方法里面被調用。代碼在代碼在PackageManagerService.java 12347行

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
        ...
        if (!checkUpgradeKeySetLP(ps, pkg)) {
             ...
        }
        ...
   }

那我們來看下checkUpgradeKeySetLP這個方法的內部實現
代碼在PackageManagerService.java 11829行

    private boolean checkUpgradeKeySetLP(PackageSetting oldPS, PackageParser.Package newPkg) {
        // Upgrade keysets are being used.  Determine if new package has a superset of the
        // required keys.
        // 如果升級KeySet,確保新的安裝包是否有超集的keys
        // 獲取舊版本的KeySet數組
        long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
        KeySetManagerService ksms = mSettings.mKeySetManagerService;
        // 遍歷KeySet數組
        for (int i = 0; i < upgradeKeySets.length; i++) {
             // 根據密鑰獲取公鑰
            Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
            if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
                 // 如果對應上 則返回true,
                return true;
            }
        }
        // 遍歷都沒有符合的,則返回false
        return false;
    }

這個方法內部主要檢查是否有匹配的公鑰,如果有則返回true,沒有則返回false。

四、 PackageManagerService#verifySignaturesLP(PackageSetting, PackageParser.Package)方法解析

這個方法在PackageManagerService的installPackageLI方法里面被調用。代碼在PackageManagerService.java 12355行

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
        ...
         verifySignaturesLP(pkgSetting, pkg);
        ...
   }

那我們來看下verifySignaturesLP這個方法的內部實現
代碼在PackageManagerService.java 11829行

    private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
            throws PackageManagerException {
         // 第一步
        if (pkgSetting.signatures.mSignatures != null) {
            // Already existing package. Make sure signatures match
            //如果有舊版本,則查看舊版本的簽名是否匹配
            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
                    == PackageManager.SIGNATURE_MATCH;
            if (!match) {
                match = compareSignaturesCompat(pkgSetting.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                match = compareSignaturesRecover(pkgSetting.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
                        + pkg.packageName + " signatures do not match the "
                        + "previously installed version; ignoring!");
            }
        }

        // Check for shared user signatures
        // 第二步
        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
           // 如果有共享用戶,則檢驗共享用戶的簽名
            // Already existing package. Make sure signatures match
            boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                    pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
            if (!match) {
                match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                        "Package " + pkg.packageName
                        + " has no signatures that match those in shared user "
                        + pkgSetting.sharedUser.name + "; ignoring!");
            }
        }

這個方法其實很簡單,分為兩個部分

  • 如果有老版本的簽名 則檢查老版本的簽名和新安裝包的簽名是否一致
  • 如果有共享用戶的簽名,則檢查共享用戶的簽名與新安裝包的簽名是否一致。

里面驗證簽名都是三重機制,如下:

  • 第一重校驗:調用compareSignatures方法,比較就的APK的簽名和新的APK簽名是否相同,如果返回值是PackageManager.SIGNATURE_MATCH,則通過并且不用后續校驗,沒有通過則進行第二重校驗。
  • 第二重校驗:調用compareSignaturesCompat方法,比較就的APK的簽名和新的APK簽名是否相同,如果返回值是PackageManager.SIGNATURE_MATCH,則通過并且不進行后續校驗,沒有通過則進行第二重校驗。
  • 第三重校驗:調用compareSignaturesRecover方法,比較舊的APK的簽名和新的APK簽名是否相同,如果返回值是PackageManager.SIGNATURE_MATCH,則通過并且不進行后續校驗,沒有通過則拋出異常,結束執行。

那我們就依次看下這三個方法

(一)、compareSignatures(Signature[] s1, Signature[] s2)方法解析

代碼在PackageManagerService.java 3951行

    /**
     * Compares two sets of signatures. Returns:
     * <br />
     * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
     * <br />
     * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
     * <br />
     * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
     * <br />
     * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
     * <br />
     * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
     */
    static int compareSignatures(Signature[] s1, Signature[] s2) {
        if (s1 == null) {
            return s2 == null
                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
        }

        if (s2 == null) {
            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
        }

        if (s1.length != s2.length) {
            return PackageManager.SIGNATURE_NO_MATCH;
        }

        // Since both signature sets are of size 1, we can compare without HashSets.
        if (s1.length == 1) {
            return s1[0].equals(s2[0]) ?
                    PackageManager.SIGNATURE_MATCH :
                    PackageManager.SIGNATURE_NO_MATCH;
        }

        ArraySet<Signature> set1 = new ArraySet<Signature>();
        for (Signature sig : s1) {
            set1.add(sig);
        }
        ArraySet<Signature> set2 = new ArraySet<Signature>();
        for (Signature sig : s2) {
            set2.add(sig);
        }
        // Make sure s2 contains all signatures in s1.
        if (set1.equals(set2)) {
            return PackageManager.SIGNATURE_MATCH;
        }
        return PackageManager.SIGNATURE_NO_MATCH;
    }

上代碼很簡單就是先做非空判斷,然后把兩個數組轉化成ArraySet,然后判斷兩個ArraySet是否相同,如果相同則返回PackageManager.SIGNATURE_MATCH,如果不相同則返回PackageManager.SIGNATURE_NO_MATCH

(二)、compareSignaturesCompat(PackageSignatures,PackageParser.Package)方法解析

如果上面的匹配不符合則說明當前不匹配,所我們要考慮是不是版本的的問題,所以就有了這個方法。

代碼在PackageManagerService.java 4004行

    /**
     * Used for backward compatibility to make sure any packages with
     * certificate chains get upgraded to the new style. {@code existingSigs}
     * will be in the old format (since they were stored on disk from before the
     * system upgrade) and {@code scannedSigs} will be in the newer format.
     */
    private int compareSignaturesCompat(PackageSignatures existingSigs,
            PackageParser.Package scannedPkg) {
        // 第一步
        // 更新安裝包名的簽名版本是否小于數據庫中簽名版本
        if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
            // 如果大于,則直接返回不匹配
            return PackageManager.SIGNATURE_NO_MATCH;
        }
       // 第二步
        ArraySet<Signature> existingSet = new ArraySet<Signature>();
        for (Signature sig : existingSigs.mSignatures) {
            existingSet.add(sig);
        }
        ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
        for (Signature sig : scannedPkg.mSignatures) {
            try {
                Signature[] chainSignatures = sig.getChainSignatures();
                for (Signature chainSig : chainSignatures) {
                    scannedCompatSet.add(chainSig);
                }
            } catch (CertificateEncodingException e) {
                scannedCompatSet.add(sig);
            }
        }
        // 第三步
        /*
         * Make sure the expanded scanned set contains all signatures in the
         * existing one.
         */
        if (scannedCompatSet.equals(existingSet)) {
            // Migrate the old signatures to the new scheme.
            // 簽名替換
            existingSigs.assignSignatures(scannedPkg.mSignatures);
            // The new KeySets will be re-added later in the scanning process.
            synchronized (mPackages) {
                mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
            }
            return PackageManager.SIGNATURE_MATCH;
        }
        // 如果最后不匹配則返回 不匹配
        return PackageManager.SIGNATURE_NO_MATCH;
    }

先來看下注釋:

這個方法主要是保證向后的兼容性,這樣可以確保證書鏈上的包可以升級到最新的版本。existingSigs是舊格式,因為它在升級前是在磁盤空間上,scansSigs是新的格式。

我將這個方法分為三個步驟

  • 第一步:判斷升級包的簽名版本是否小于當前系統中簽名的數據庫版本號,上面一層判斷已經不匹配才會走到這個方法里面,所以如果更新的安裝包的簽名版本大于當前數據庫中的簽名版本號,則一定是不匹配的。所以會返回PackageManager.SIGNATURE_NO_MATCH
  • 第二步:同樣是通過遍歷的方式把舊的簽名數組轉化為ArraySet對象existingSet,同時遍歷新的安裝包中每個簽名的簽名鏈,并把簽名鏈加入到ArraySet對象
  • 第三步:確保擴展的掃描集包含現有的所有簽名。如果scannedCompatSet和existingSet一致,則進行簽名替換,并且在mSettings.mKeySetManagerService刪除簽名。

PS:上文說的DatabaseVersion 其實是Settings.java的內部類

(三)、compareSignaturesRecover(PackageSignatures,PackageParser.Package)方法解析

如果上面兩個匹配規則都沒有匹配,我們考慮是不是在證書有過變動導致的匹配失敗,所以這個方法主要考慮是否恢復證書進行匹配
代碼在PackageManagerService.java 4046行

    private int compareSignaturesRecover(PackageSignatures existingSigs,
            PackageParser.Package scannedPkg) {
        // 第一步
        if (!isRecoverSignatureUpdateNeeded(scannedPkg)) {
            return PackageManager.SIGNATURE_NO_MATCH;
        }
        // 第二步
        String msg = null;
        try {
            if (Signature.areEffectiveMatch(existingSigs.mSignatures, scannedPkg.mSignatures)) {
                logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for "
                        + scannedPkg.packageName);
                return PackageManager.SIGNATURE_MATCH;
            }
        } catch (CertificateException e) {
            msg = e.getMessage();
        }

        logCriticalInfo(Log.INFO,
                "Failed to recover certificates for " + scannedPkg.packageName + ": " + msg);
        return PackageManager.SIGNATURE_NO_MATCH;
    }

我將上面的方法分為兩步:

  • 第一步:調用isCompatSignatureUpdateNeeded判斷是否有恢復的需求,這里隨帶說下isCompatSignatureUpdateNeeded方法。Android LOLLIPOP這個版本是一個時間窗口,會對證書進行修該。如果不用進行證書恢復,則整個這個方法就無意義了,直接返回PackageManager.SIGNATURE_NO_MATCH
  • 第二步:調用Signature的靜態方法areEffectiveMatch進行匹配,這個方法內部。由于在極少數情況下,證書可能會有錯誤的編碼,導致匹配失敗。這個方法就是避免這種情況的解決方案,代碼不多,大家有興趣可以自行查看。

五、PackageDexOptimizer#performDexOp(PackageParser.Package, String[], String[], boolean, String,CompilerStats.PackageStats)方法解析

代碼位置在PackageManagerService的installPackageLI方法里面會調用到,代碼如下:
PackageManagerService.java 12451行

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
        ...
           // Run dexopt before old package gets removed, to minimize time when app is unavailable
            int result = mPackageDexOptimizer
                    .performDexOpt(pkg, null /* instruction sets */, false /* forceDex */,false /* defer */, false /* inclDependencies */, true /* boot complete */);
        ...
    }

那我們就來看下PackageDexOptimizer#performDexOp方法
代碼在PackageDexOptimizer.java 73行

    /**
     * Performs dexopt on all code paths and libraries of the specified package for specified
     * instruction sets.
     *
     * <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on
     * {@link PackageManagerService#mInstallLock}.
     */
    int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
            boolean forceDex, boolean defer, boolean inclDependencies, boolean bootComplete) {
        ArraySet<String> done;
        if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
            done = new ArraySet<String>();
            done.add(pkg.packageName);
        } else {
            done = null;
        }
        synchronized (mPackageManagerService.mInstallLock) {
            final boolean useLock = mSystemReady;
             // 除了在啟動啟動時段,其他時段 mSystemReady一般為true
            if (useLock) {
                mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
                mDexoptWakeLock.acquire();
            }
            try {
                // 核心方法
                return performDexOptLI(pkg, instructionSets, forceDex, defer, bootComplete, done);
            } finally {
                if (useLock) {
                    mDexoptWakeLock.release();
                }
            }
        }
    }

有注釋,先來看下注釋

對指定包內的代碼和庫執行dexopt。

方法內部主要用mInstallLock來加鎖,然后調用performDexOptLI(PackageParser.Package, String[],String[], boolean, String,CompilerStats.PackageStats packageStats)方法

下面讓我們來看下這個方法

1、performDexOptLI(PackageParser.Package, String[],String[], boolean, String,CompilerStats.PackageStats packageStats)方法解析

代碼在PackageDexOptimizer.java 98行

    private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
            boolean forceDex, boolean defer, boolean bootComplete, ArraySet<String> done) {
        final String[] instructionSets = targetInstructionSets != null ?
                targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);

        if (done != null) {
            done.add(pkg.packageName);

            // 是否有一些共享庫的apk,也要進行dex優化
            //usesLibraries 保存著AndroidManifest中的<uses-library>標簽中android:required=true庫
            if (pkg.usesLibraries != null) {
                //  進行dexopt優化 
                performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer,
                        bootComplete, done);
            }
            // usesOptionalLibraries 保存著AndroidManifest中<uses-library>標簽中的 android:required=false的庫
            if (pkg.usesOptionalLibraries != null) {
                 //  進行dexopt優化 
                performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer,
                        bootComplete, done);
            }
        }

        // 沒有代碼的包直接跳過
        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
            return DEX_OPT_SKIPPED;
        }

        // 是否是虛擬機的安全模式
        final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
        //  是否是debug模式
        final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
        // 獲取所有代碼的路徑
        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
        boolean performedDexOpt = false;
        // There are three basic cases here:
        // 1.) we need to dexopt, either because we are forced or it is needed
        // 2.) we are deferring a needed dexopt
        // 3.) we are skipping an unneeded dexopt
        // 通過ArraySet 復制一份 數組
        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
        // 開始遍歷 數組
        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
            if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
                // 沒有強制優化或者已經dex優化過 直接continue
                continue;
            }
            // 遍歷所有 代碼路徑
            for (String path : paths) {
                final int dexoptNeeded;
                if (forceDex) {
                    // 如果是強制dex優化
                    dexoptNeeded = DexFile.DEX2OAT_NEEDED;
                } else {
                    try {
                        // 調用的DexFile的靜態方法獲取虛擬機對代碼的優化意圖
                        dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
                                dexCodeInstructionSet, defer);
                    } catch (IOException ioe) {
                        Slog.w(TAG, "IOException reading apk: " + path, ioe);
                        return DEX_OPT_FAILED;
                    }
                }
                // 如果不是強制優化且要求延遲優化,并且優化策略是不需要優化,則延遲優化
                if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                  
                    // We're deciding to defer a needed dexopt. Don't bother dexopting for other
                    // paths and instruction sets. We'll deal with them all together when we process
                    // our list of deferred dexopts.
                    // 把包放到延遲優化列表 內部是add到一個ArraySet中
                    addPackageForDeferredDexopt(pkg);
                     // 返回延遲優化
                    return DEX_OPT_DEFERRED;
                }
                 // 如果不是 沒必要優化,則意味著要做優化
                if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                    final String dexoptType;
                    String oatDir = null;
                   // 如果優化意圖是dex->oat
                    if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) {
                        dexoptType = "dex2oat";
                        try {
                            // 獲取 oat目錄
                            oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
                        } catch (IOException ioe) {
                            Slog.w(TAG, "Unable to create oatDir for package: " + pkg.packageName);
                            return DEX_OPT_FAILED;
                        }
                    } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
                        // 優化意圖為 補丁優化
                        dexoptType = "patchoat";
                    } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
                        // 優化意圖 為 用虛擬機的循環 補丁優化
                        dexoptType = "self patchoat";
                    } else {
                        throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
                    }

                    Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
                            + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
                            + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
                            + " oatDir = " + oatDir + " bootComplete=" + bootComplete);
                    // 獲取sharedGid
                    final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
                    // 調用mPackageManagerService的mInstaller的dexopt方法來進行優化
                    final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
                            !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet,
                            dexoptNeeded, vmSafeMode, debuggable, oatDir, bootComplete);

                    // Dex2oat might fail due to compiler / verifier errors. We soldier on
                    // regardless, and attempt to interpret the app as a safety net.
                    if (ret == 0) {
                        // 優化成功
                        performedDexOpt = true;
                    }
                }
            }

            // At this point we haven't failed dexopt and we haven't deferred dexopt. We must
            // either have either succeeded dexopt, or have had getDexOptNeeded tell us
            // it isn't required. We therefore mark that this package doesn't need dexopt unless
            // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
            // it.
            // 將他添加到已經優化過的緩存中
            pkg.mDexOptPerformed.add(dexCodeInstructionSet);
        }

        // If we've gotten here, we're sure that no error occurred and that we haven't
        // deferred dex-opt. We've either dex-opted one more paths or instruction sets or
        // we've skipped all of them because they are up to date. In both cases this
        // package doesn't need dexopt any longer.
        return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
    }

上面這個方法遍歷了APK的所有代碼路徑,根據解析得到了dexoptType,最后用installd來完成了dexopt工作,其中如果dexoptType為dex2oat時,會調用createOatDirIfSupported方法獲得oatdir。其他情況oatdir為空

createOatDirIfSupported方法很簡單,用Install在該目錄下創建一個目錄。我就不詳細講解了

關于installd的dexopt工作內容,我會在后續講解虛擬機的時候詳細講解。

六、args.doRename(res.returnCode, pkg, oldCodePath)方法解析

代碼位置在PackageManagerService的installPackageLI方法里面會調用到,代碼如下:
PackageManagerService.java 12461行

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
        ...
        if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
            res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
            return;
        }
        ...
    }

我們知道這里面的args指的是FileInstallArgs對象,所以args.doRename(res.returnCode, pkg, oldCodePath)方法就是FileInstallArgs#doRename(int, PackageParser.Package, String)方法

那我們就來看下FileInstallArgs#doRename(int, PackageParser.Package, String)方法
代碼在PackageManagerService.java 11115行

        boolean doRename(int status, PackageParser.Package pkg, String oldCodePath) {
             // 如果沒有成功,清理并返回改名事變
            if (status != PackageManager.INSTALL_SUCCEEDED) {
                cleanUp();
                return false;
            }
             // 獲取父目錄
            final File targetDir = codeFile.getParentFile();
            // 獲取舊的文件
            final File beforeCodeFile = codeFile;
            // 獲取目錄下的新的文件 調用getNextCodePath方法,后面會詳解講解
            final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);

            if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
            try {
                // 調用 Os的rename方法進行重命名
                Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
            } catch (ErrnoException e) {
                Slog.w(TAG, "Failed to rename", e);
                return false;
            }

            //  設置改名后文件的SELinux的上下文,后續會有在SELinux專題中詳細講解
            if (!SELinux.restoreconRecursive(afterCodeFile)) {
                Slog.w(TAG, "Failed to restorecon");
                return false;
            }

            // Reflect the rename internally
            codeFile = afterCodeFile;
            resourceFile = afterCodeFile;

            // Reflect the rename in scanned details
            // 重命名后一些變量也需要跟著變化
            pkg.codePath = afterCodeFile.getAbsolutePath();
            pkg.baseCodePath = FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile,
                    pkg.baseCodePath);
            pkg.splitCodePaths = FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile,
                    pkg.splitCodePaths);

            // Reflect the rename in app info
            pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
            pkg.applicationInfo.setCodePath(pkg.codePath);
            pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
            pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
            pkg.applicationInfo.setResourcePath(pkg.codePath);
            pkg.applicationInfo.setBaseResourcePath(pkg.baseCodePath);
            pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths);

            return true;
        }

上面這個方法,代碼不多,我已經加上注釋了,主要是就調用getNextCodePath方法來獲取新的apk的目錄名字,然后調用os的rename函數重命名,然后進行重命名后變量屬性的變更。

這里面涉及到一個重要方法,即getNextCodePath(File,String)方法,我們來看一下
代碼在PackageManagerService.java 11689行

    private File getNextCodePath(File targetDir, String packageName) {
        int suffix = 1;
        File result;
        do {
            result = new File(targetDir, packageName + "-" + suffix);
            suffix++;
        } while (result.exists());
        return result;
    }

代碼很簡單,就是獲取一個新的APK目錄名字在, 然后在APK的機上一個"-"+數字的后綴。

七、startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);方法解析

代碼位置在PackageManagerService的installPackageLI方法里面會調用到,代碼如下:
PackageManagerService.java 12466行

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
        ...
        startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
        ...
    }

那我們就來看下PackageManagerService#startIntentFilterVerifications(int, boolean,PackageParser.Package)方法

代碼在PackageManagerService.java 11115行

    private void startIntentFilterVerifications(int userId, boolean replacing,
            PackageParser.Package pkg) {
         // intentFilter的驗證組件
        if (mIntentFilterVerifierComponent == null) {
            Slog.w(TAG, "No IntentFilter verification will not be done as "
                    + "there is no IntentFilterVerifier available!");
            return;
        }
  
        // 獲取驗證的uid
        final int verifierUid = getPackageUid(
                mIntentFilterVerifierComponent.getPackageName(),
                (userId == UserHandle.USER_ALL) ? UserHandle.USER_OWNER : userId); 
        // 刪除what值為START_INTENT_FILTER_VERIFICATIONS的message,避免重復
        mHandler.removeMessages(START_INTENT_FILTER_VERIFICATIONS);
        // 創建一個 what值為START_INTENT_FILTER_VERIFICATIONS的Message
        final Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS);
        // 構造這個IFVerificationParams并把它賦值給Message的obj字段
        msg.obj = new IFVerificationParams(pkg, replacing, userId, verifierUid);
        // 發送一個Message
        mHandler.sendMessage(msg);
    }

這個方法內部主要是獲取了一個Message對象,然后構造了一個IFVerificationParams,并且把這個IFVerificationParams對象指向了Message的obj。然后發送了這個Message對象。

我們先來看下IFVerificationParams這個類

1、IFVerificationParams類

代碼在PackageManagerService.java 606行

    private static class IFVerificationParams {
        PackageParser.Package pkg;
        boolean replacing;
        int userId;
        int verifierUid;

        public IFVerificationParams(PackageParser.Package _pkg, boolean _replacing,
                int _userId, int _verifierUid) {
            pkg = _pkg;
            replacing = _replacing;
            userId = _userId;
            replacing = _replacing;
            verifierUid = _verifierUid;
        }
    }

哈哈,居然發現android 源碼的一個問題,它給replacing賦值了兩次

我們看到這個類其實就是一個包裝類,包裝了4個字段而已

IFVerificationParams類這個類看完,我們來看下what值為START_INTENT_FILTER_VERIFICATIONS的Message對應的處理邏輯

2、what值為START_INTENT_FILTER_VERIFICATIONS的Message對應的處理邏輯

代碼在PackageManagerService.java 1582行

        void doHandleMessage(Message msg) {
            switch (msg.what) {
                ...
                case START_INTENT_FILTER_VERIFICATIONS: {
                    IFVerificationParams params = (IFVerificationParams) msg.obj;
                    verifyIntentFiltersIfNeeded(params.userId, params.verifierUid,
                            params.replacing, params.pkg);
                    break;
                }
                ...
      }

我們看到在case START_INTENT_FILTER_VERIFICATIONS里面就做了兩件事:

  • 獲取IFVerificationParams 對象params
  • 調用 verifyIntentFiltersIfNeeded(int, int,boolean,PackageParser.Package)方法

那我們來看下verifyIntentFiltersIfNeeded方法的內部執行情況

3、PackageManagerService#verifyIntentFiltersIfNeeded(int, int,boolean,PackageParser.Package)方法解析

代碼在PackageManagerService.java 12501行

    private void verifyIntentFiltersIfNeeded(int userId, int verifierUid, boolean replacing,
            PackageParser.Package pkg) {
         // 獲取安裝包中所有activity的數量
        int size = pkg.activities.size();
        // 如果沒有activity則不需要驗證,直接返回
        if (size == 0) {
            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
                    "No activity, so no need to verify any IntentFilter!");
            return;
        }
      
        // 判斷 這個安裝包內是否設置了url的過濾限制
        final boolean hasDomainURLs = hasDomainURLs(pkg);
        // 如果沒有設置url過濾限制,則直接返回
        if (!hasDomainURLs) {
            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
                    "No domain URLs, so no need to verify any IntentFilter!");
            return;
        }

        if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Checking for userId:" + userId
                + " if any IntentFilter from the " + size
                + " Activities needs verification ...");

        int count = 0;
        final String packageName = pkg.packageName;

        synchronized (mPackages) {
            // If this is a new install and we see that we've already run verification for this
            // package, we have nothing to do: it means the state was restored from backup.
            // 不是提前,即是新安裝
            if (!replacing) {
                 // 如果是新安裝,我們只需要判斷是不是之前是不是驗證,過
                IntentFilterVerificationInfo ivi =
                        mSettings.getIntentFilterVerificationLPr(packageName);
                if (ivi != null) {
                   // 如果ivi不為null,則意味著驗證過,不需要繼續驗證了
                    if (DEBUG_DOMAIN_VERIFICATION) {
                        Slog.i(TAG, "Package " + packageName+ " already verified: status="
                                + ivi.getStatusString());
                    }
                    return;
                }
            }

            // If any filters need to be verified, then all need to be.
            //首先判斷是否需要intent驗證,即遍歷所有的activity,判斷每一個activity是否需要進行驗證,
            //只要有一個需要驗證,則需要進行驗證, 如果一個都沒有,則不需要驗證
            boolean needToVerify = false;
            for (PackageParser.Activity a : pkg.activities) {
                for (ActivityIntentInfo filter : a.intents) {
                    if (filter.needsVerification() && needsNetworkVerificationLPr(filter)) {
                        if (DEBUG_DOMAIN_VERIFICATION) {
                            Slog.d(TAG, "Intent filter needs verification, so processing all filters");
                        }
                        needToVerify = true;
                        break;
                    }
                }
            }

            // 如果需要驗證
            if (needToVerify) {
                final int verificationId = mIntentFilterVerificationToken++;
                for (PackageParser.Activity a : pkg.activities) {
                    for (ActivityIntentInfo filter : a.intents) {
                        // 如果需要驗證
                        if (filter.handlesWebUris(true) && needsNetworkVerificationLPr(filter)) {
                            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
                                    "Verification needed for IntentFilter:" + filter.toString());
                            // 把驗證加入到mIntentFilterVerifier里面
                            mIntentFilterVerifier.addOneIntentFilterVerification(
                                    verifierUid, userId, verificationId, filter, packageName);
                             // 需要驗證數量+1
                            count++;
                        }
                    }
                }
            }
        }

       // 如果驗證數量大于0,開啟驗證
        if (count > 0) {
            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Starting " + count
                    + " IntentFilter verification" + (count > 1 ? "s" : "")
                    +  " for userId:" + userId);
            mIntentFilterVerifier.startVerifications(userId);
        } else {
            if (DEBUG_DOMAIN_VERIFICATION) {
                Slog.d(TAG, "No filters or not all autoVerify for " + packageName);
            }
        }
    }

這個方法內部首先判斷這個安裝包中的Activity的個數,如果一個Activity都沒有,則不需要驗證。然后用獲取是否設置url驗證。如果沒設置,同樣不需要驗證。如果經歷了前面的兩重驗證, 還沒返回則說明activity的個數大于0,并且有url驗證。這時候還要考慮一種情況,即新安裝且已經檢驗過了。所以再進行判斷是新安裝且已經安裝過的情況。最后開始遍歷安裝包的每一個activity,判斷是否有驗證的設置。如果連一個activity的驗證設置都沒有,則不需要驗證。如果有驗證設置則將驗證設置添加到mIntentFilterVerifier中,并給count+1。最后如果count>0,則說明有驗證設置,最后調用mIntentFilterVerifier.startVerifications(userId)這行代碼進行驗證

那我們就來看下mIntentFilterVerifier.startVerifications(userId)這行代碼的內部執行邏輯

4、IntentFilterVerifier#startVerifications(int)方法解析

首先我們知道IntentFilterVerifier是一個接口
代碼在PackageManagerService.java 622行

    private interface IntentFilterVerifier<T extends IntentFilter> {
        boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId,
                                               T filter, String packageName);
        void startVerifications(int userId);
        void receiveVerificationResponse(int verificationId);
    }

既然是一個接口,我們要找到它的具體實現類
PackageManagerService.java的構造函數里面有對mIntentFilterVerifier進行初始化

代碼在PackageManagerService.java 2335行

    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
            ...
            mIntentFilterVerifier = new IntentVerifierProxy(mContext,
                    mIntentFilterVerifierComponent);
            ...
    }

所以我們知道了mIntentFilterVerifier其實就是IntentVerifierProxy對象

那我們來看下IntentVerifierProxy的startVerifications(int)方法的具體實現
代碼在PackageManagerService.java 644行

        @Override
        public void startVerifications(int userId) {
            // Launch verifications requests
            // 獲取總體驗證的數量
            int count = mCurrentIntentFilterVerifications.size();
            // 開始遍歷
            for (int n=0; n<count; n++) {
                // 首先獲取驗證id
                int verificationId = mCurrentIntentFilterVerifications.get(n);
                // 根據驗證id 獲取對應的IntentFilter
                final IntentFilterVerificationState ivs =
                        mIntentFilterVerificationStates.get(verificationId);
                // 獲取包名
                String packageName = ivs.getPackageName();
                 // 根據驗證獲取其對應的filters
                ArrayList<PackageParser.ActivityIntentInfo> filters = ivs.getFilters();
                // 獲取filters的數量,方便后續的遍歷
                final int filterCount = filters.size();
                // 創建ArraySet
                ArraySet<String> domainsSet = new ArraySet<>();
               // 開始遍歷filters
                for (int m=0; m<filterCount; m++) {
                    // 獲取其對應的具體某一個 PackageParser.ActivityIntentInfo
                    PackageParser.ActivityIntentInfo filter = filters.get(m);
                     // 把PackageParser.ActivityIntentInfo添加到domainsSet中
                    domainsSet.addAll(filter.getHostsList());
                }
                //把ArraySet轉化為ArrayList
                ArrayList<String> domainsList = new ArrayList<>(domainsSet);
                synchronized (mPackages) {
                     // 根據包名獲取其對應的PackageSetting,然后調用setIntentFilterVerificationInfo把其對應的IntentFilterVerificationInfo添加到PackageSetting中去
                    if (mSettings.createIntentFilterVerificationIfNeededLPw(
                            packageName, domainsList) != null) {
                        // 延遲寫入
                        scheduleWriteSettingsLocked();
                    }
                }
                // 發送驗證廣播
                sendVerificationRequest(userId, verificationId, ivs);
            }
           // 清空
            mCurrentIntentFilterVerifications.clear();
        }

注釋已經很清楚了,關注下最后的一個方法sendVerificationRequest方法

代碼在PackageManagerService.java 673行

        private void sendVerificationRequest(int userId, int verificationId,
                IntentFilterVerificationState ivs) {

            Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
            verificationIntent.putExtra(
                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
                    verificationId);
            verificationIntent.putExtra(
                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
                    getDefaultScheme());
            verificationIntent.putExtra(
                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
                    ivs.getHostsString());
            verificationIntent.putExtra(
                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
                    ivs.getPackageName());
            verificationIntent.setComponent(mIntentFilterVerifierComponent);
            verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

            UserHandle user = new UserHandle(userId);
            mContext.sendBroadcastAsUser(verificationIntent, user);
            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
                    "Sending IntentFilter verification broadcast");
        }

我們看到這個方法里面什么也沒做,就是發送了一個廣播。由于廣播內面牽扯的太多了,我就不再繼續深入了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容