深刻Android系統(十)PMS-2-初始化的一些細節

permission文件的處理

PMS的構造方法中初始化了PermissionManagerService,經過PermissionManagerService.create()方法,相關調用以下:java

public static PermissionManagerInternal create(......) {
        ......
        new PermissionManagerService(context, defaultGrantCallback, externalLock);
        ......
    }
    PermissionManagerService(......) {
        ......
        mHandlerThread = new ServiceThread(TAG,
                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        Watchdog.getInstance().addThread(mHandler);
        ......
        SystemConfig systemConfig = SystemConfig.getInstance();
        mSystemPermissions = systemConfig.getSystemPermissions();
        ......
    }
複製代碼

上面列出來PermissionManagerService初始化的過程當中兩個操做:android

  • 註冊Watchdog監聽,此處列出它來主要是爲了呼應前面的章節。哈哈哈,重點是第二點
  • 經過SystemConfiggetSystemPermissions方法來獲取系統的Permission列表
    public SparseArray<ArraySet<String>> getSystemPermissions() {
        return mSystemPermissions;
    }
    複製代碼

而對於mSystemPermissions,是在SystemConfig的構造方法中完成的,相關代碼以下:api

//單例模式
    SystemConfig() {
        // 讀取 /system/etc/sysconfig 目錄下的 permission 文件
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
        // 讀取 /system/etc/permissions 目錄下的 permission 文件
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
        ......
        // 讀取 /vendor/etc/sysconfig 目錄下的 permission 文件
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag);
        // 讀取 /vendor/etc/permissions 目錄下的 permission 文件
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "permissions"), vendorPermissionFlag);
        ...// 省略 /oem、/odm、/product對應分區目錄下 readPermissions()
    }
複製代碼

不用說,核心是這個readPermissions()方法,看看:markdown

void readPermissions(File libraryDir, int permissionFlag) {
        // Read permissions from given directory.
        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
            return;// 若是目錄不存在或者libraryDir不是目錄,直接返回
        }
        if (!libraryDir.canRead()) {
            return;// 若是目錄不可讀,直接返回
        }
        File platformFile = null;
        // 循環能處理目錄文件
        for (File f : libraryDir.listFiles()) {
            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
                // 檢測到platform.xml文件,先記錄,循環中不作處理
                platformFile = f;
                continue;
            }
            if (!f.getPath().endsWith(".xml")) {
                // 跳過非xml文件
                continue;
            }
            if (!f.canRead()) {
                // 跳過不可讀文件
                continue;
            }
            readPermissionsFromXml(f, permissionFlag);// 解析xml
        }
        // 最後,解析platform.xml文件
        if (platformFile != null) {
            readPermissionsFromXml(platformFile, permissionFlag);
        }
    }
複製代碼

readPermissions()方法先檢測指定目錄下的xml文件,而後調用readPermissionsFromXml方法來解析文件並將解析結果填充到SystemConfig對應的數據結構中。數據結構

readPermissionsFromXml()方法內容以下:app

private void readPermissionsFromXml(File permFile, int permissionFlag) {
        FileReader permReader = new FileReader(permFile);
        ......
        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(permReader);
            int type;
            // 查找 START_TAG 或 END_DOCUMENT
            while ((type=parser.next()) != parser.START_TAG
                       && type != parser.END_DOCUMENT) {
                ;
            }
            // 從 START_TAG 開始解析
            if (type != parser.START_TAG) {
                throw new XmlPullParserException("No start tag found");
            }
            // 只針對<permissions/>和<config/>標籤進行解析
            if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
                throw new XmlPullParserException(...);
            }
            ......
            while (true) {
                ......
                String name = parser.getName();
                // 可以識別的標籤以下:
                if ("group".equals(name) && allowAll) {
                } else if ("permission".equals(name) && allowPermissions) {
                } else if ("assign-permission".equals(name) && allowPermissions) {
                } else if ("library".equals(name) && allowLibs) {
                } else if ("feature".equals(name) && allowFeatures) {
                } else if ("unavailable-feature".equals(name) && allowFeatures) {
                } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
                } else if ("allow-in-power-save".equals(name) && allowAll) {
                } else if ("allow-in-data-usage-save".equals(name) && allowAll) {
                } else if ("allow-unthrottled-location".equals(name) && allowAll) {
                } else if ("allow-implicit-broadcast".equals(name) && allowAll) {
                } else if ("app-link".equals(name) && allowAppConfigs) {
                } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
                } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
                } else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
                } else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) {
                } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name) && allowAppConfigs) {
                } else if ("disabled-until-used-preinstalled-carrier-app".equals(name) && allowAppConfigs) {
                } else if ("privapp-permissions".equals(name) && allowPrivappPermissions) {
                } else if ("oem-permissions".equals(name) && allowOemPermissions) {
                } else if ("hidden-api-whitelisted-app".equals(name) && allowApiWhitelisting) {
                } else {
                }
            }
        }
        ......
    }
複製代碼

if else中的字符串就是xml的關鍵標籤,解析時會分別保存到SystemConfig對應的final成員變量中,這部分就不貼源碼了哈ide

從整個解析過程來看,這些xml文件分紅了<permissions/><config/>兩個大標籤,而後又包含permissionlibrary等子標籤。函數

platform.xml爲例,咱們來看下相關內容結構:oop

<permissions>
    <permission name="android.permission.BLUETOOTH_ADMIN" >
        <group gid="net_bt_admin" />
    </permission>
    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    </permission>
    ......
    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />
    ......
    <library name="android.test.base" file="/system/framework/android.test.base.jar" />
    <library name="android.test.mock" file="/system/framework/android.test.mock.jar" />
    ......
<permissions/>
複製代碼

platform.xml上面主要由3塊內容:測試

  • 標籤<permission/>:將屬性name中字符串表示的權限賦予<group/>標籤中經過gid指定的用戶組
  • 標籤<assign-permission/>:將屬性name中字符串表示的權限賦予經過屬性uid指定的用戶
  • 標籤<library/>:指定系統自動加載的動態庫

Android 的不少功能都是經過所謂的permission字符串來保護的,應用中若是須要使用某項受保護的功能,須要在它的AndroidManifest文件中顯示聲明該功能對應的permission字符串。可是,有些功能只能由特定的用戶組使用,而在platform.xml文件中定義的就是這種規則

掃描應用目錄的過程

Android 5.0引入了Split APK機制,主要是爲了解決65536上限以及APK安裝包愈來愈大等問題。

Split APK機制能夠將一個APK拆分紅多個獨立APK。 在引入了Split APK機制後,APK有兩種分類:

  • Single APK:安裝文件爲一個完整的APK,即base APKAndroid稱其爲Monolithic
  • Mutiple APK:安裝文件在一個文件目錄中,其內部有多個被拆分的APK,這些APK由一個base APK和一個或多個split APK組成。Android稱其爲Cluster

scanDirLI

上面兩種格式的掃描邏輯,是在PMS的構造方法中,經過scanDirLI來實現。

scanDirLI代碼以下:

private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = scanDir.listFiles();
        ......
        // 建立 ParallelPackageParser apk 解析幫助類
        // ParallelPackageParser核心實際上是一個 ExecutorService
        // 當前目錄下全部的 apk 解析任務經過ExecutorService.submit來建立
        try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(...)) {
            // 遍歷查找指定目錄下的apk文件
            int fileCount = 0;
            for (File file : files) {
                // 判讀是否爲apk文件
                // 或者不是smdl2tmp打頭的文件夾
                final boolean isPackage = (isApkFile(file) || file.isDirectory())
                        && !PackageInstallerService.isStageName(file.getName());
                if (!isPackage) {
                    continue;
                }
                // 建立提交解析任務,解析符合條件的文件
                parallelPackageParser.submit(file, parseFlags);
                // 記錄任務數量
                fileCount++;
            }
            // 根據建立的任務數量,查看任務結果
            for (; fileCount > 0; fileCount--) {
                // 此方法是阻塞的,直到隊列中有結果添加進來
                ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
                // 對解析結果進行判斷
                Throwable throwable = parseResult.throwable;
                int errorCode = PackageManager.INSTALL_SUCCEEDED;
                if (throwable == null) {
                    // 若是是靜態共享庫,進行rename操做
                    ......
                    // 若是應用正常,執行進一步的掃描
                    if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
                        scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
                                    currentTime, null);
                    }
                }
                ...// 省略異常狀況處理
                // Delete invalid userdata apps
                if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
                        errorCode != PackageManager.INSTALL_SUCCEEDED) {
                    .......
                    removeCodePathLI(parseResult.scanFile);
                }
            }
        }
    }
複製代碼

根據上面的代碼咱們能夠劃分爲兩個階段:

  • scanDirLIparsePackage
    • APK解析階段
    • APK的解析是經過ParallelPackageParser類的submit()方法提交解析任務來實現的
  • scanDirLIscanPackageChildLI
    • 將解析好的APK添加到PMSmPackages

咱們逐一來分析

scanDirLIparsePackage

ParallelPackageParser類是Android封裝的一個線程池,核心實現是ExecutorService類。主要是爲了方便收集並行解析的結果。

解析任務的建立都是經過此類的submit()方法來完成的。

ParallelPackageParsersubmit()方法

咱們來簡單看下:

public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            ParseResult pr = new ParseResult();
            PackageParser pp = new PackageParser();
            pp.setSeparateProcesses(mSeparateProcesses);
            pp.setOnlyCoreApps(mOnlyCore);
            pp.setDisplayMetrics(mMetrics);
            pp.setCacheDir(mCacheDir);
            pp.setCallback(mPackageParserCallback);
            pr.scanFile = scanFile;
            pr.pkg = parsePackage(pp, scanFile, parseFlags);
            ......
            mQueue.put(pr);
            ......
        });
    }
    protected PackageParser.Package parsePackage(......) throws PackageParser.PackageParserException {
        return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
    }
複製代碼

函數中的submit()新建了一個解析線程,線程中建立了一個PackageParser對象,而後調用它的parsePackage方法來進行解析。

PackageParserparsePackage()方法

PackageParserparsePackage()方法以下:

public Package parsePackage(File packageFile, int flags, boolean useCaches) throws PackageParserException {
        ......
        // 根據是否爲directory執行不一樣的解析邏輯
        if (packageFile.isDirectory()) {
            // 解析一組apk
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            // 解析單個apk
            parsed = parseMonolithicPackage(packageFile, flags);
        }
        ......
    }
複製代碼

到這裏咱們能夠發現,真正的APK解析流程就在PackageParser中,你們能夠跟蹤下上面兩種狀況下的解析,以parseClusterPackage()爲例,咱們看下

parseClusterPackage()解析split應用

private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
        // 獲取應用目錄的 PackageLite 對象,這個對象中分開保存了目錄下的
        // base應用和其餘非核心應用
        final  PackageLite lite = parseClusterPackageLite(packageDir, 0);
        ......
        try {
            // 先裝載base應用資源
            final AssetManager assets = assetLoader.getBaseAssetManager();
            final File baseApk = new File(lite.baseCodePath);
            final Package pkg = parseBaseApk(baseApk, assets, flags);
            ......
            // 再裝載split應用資源
            if (!ArrayUtils.isEmpty(lite.splitNames)) {
                final int num = lite.splitNames.length;
                pkg.splitNames = lite.splitNames;
                pkg.splitCodePaths = lite.splitCodePaths;
                ......
                for (int i = 0; i < num; i++) {
                    final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
                    parseSplitApk(pkg, i, splitAssets, flags);
                }
            }
            pkg.setCodePath(packageDir.getCanonicalPath());
            pkg.setUse32bitAbi(lite.use32bitAbi);
            return pkg;
        }
        ......
    }
複製代碼
  • parseClusterPackage()方法中首先調用parseClusterPackageLite()方法對目錄下的APK進行初步分析
    • 主要區分basesplit應用:base應用只有一個。split應用能夠沒有或者多個
    • split應用的做用是用來保存資源和代碼
    • parseClusterPackageLite()方法會將分析結果經過PackageLite對象返回
  • parseClusterPackage()方法接下來:
    • base應用經過parseBaseApk()來進行分析
    • split應用經過parseSplitApk()來進行分析
    • 把分析結果存放到Package對象中

對於parseMonolithicPackage()方法省去了parseSplitApk的過程。但在解析時兩者都會執行到parseBaseApk()方法,而parseBaseApk()最後會執行到parseBaseApkCommon()方法中。

parseBaseApkCommon()解析AndroidManifest.xml

parseBaseApkCommon()其實主要是對AndroidManifest.xml的解析,簡要方法以下:

private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException {
        ......
        TypedArray sa = res.obtainAttributes(parser,
                com.android.internal.R.styleable.AndroidManifest);
        // 讀取 AndroidManifest 的sharedUserId信息
        String str = sa.getNonConfigurationString(
                com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
        ......
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            ......
            String tagName = parser.getName();
            ......
            if (tagName.equals(TAG_APPLICATION)) {
                ......
                // 解析 <application/> 標籤信息
                if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
                    return null;
                }
            }
            // 省略各類 AndroidManifest 的標籤解析
            ......
            else if (tagName.equals(TAG_PERMISSION)) {
                // 解析 permission 標籤
                if (!parsePermission(pkg, res, parser, outError)) {
                    return null;
                }
            }
            ......
        }
        ......
        return pkg;
    }
複製代碼

這個方法對AndroidManifest.xml的標籤進行解析,把解析結果存放到到Package對象中,並返回。

咱們在while循環中省略了不少AndroidManifest.xml的標籤,簡單介紹下應用常見的一些標籤:

  • <manifest/>AndroidManifest.xml的根元素。
    • 必須包含一個<application/>元素
    • 必須指定xlmns:android屬性:androidXML命名空間
    • 必須指定package屬性:應用的包名
    • 可選屬性versionCode:指定應用版本號,整數類型
    • 可選屬性versionName:顯示給用戶的版本號,字符串類型
  • <uses-permission/>:聲明某種權限。應用必須聲明可權限纔可使用對應的功能
  • <permission-group/>:定義權限組
  • <permission-tree/>:定義一顆權限樹,能夠經過PackageManager.addPermission插入Permission
  • <path-permission/>:爲ContentProvider下的數據目錄定義訪問權限
  • <application/>:應用配置的根元素,包含全部與應用有關的屬性配置
  • <activity/>Activity的聲明標籤
  • <activity-alias/>Activity的別名定義標籤
  • <service/>Service組件的聲明標籤
  • <provider/>ContentProvider的聲明標籤
  • <receiver/>Broadcast Receiver的聲明標籤
  • <intent-filter/><intent-filter/>表示Intent過濾的聲明
    • <intent-filter/>能夠放在<activity/><activity-alias/><service/><receiver/>等標籤下
    • <intent-filter/>必須包含有<action/>,用於描述Intent的名稱
    • 可選標籤<category/>,用於描述Intent的類別
    • 可選標籤<data/>,用於描述Intent須要處理的數據格式
  • <meta-data/>:用於儲存預約義的數據。
    • <meta-data/>能夠放在<activity/><activity-alias/><application/><service/><receiver/>等標籤下
    • <meta-data/>數據通常以鍵值對的形式出現,可使用ActivityInfoServiceInfoApplicationInfo隊形的metaData變量來訪問它們
  • <instrumentation/>:用來聲明Instrumentation測試類來監視應用的行爲
  • <uses-sdk/>:指定應用所需使用的Android SDK的最高版本、最低版本、目標版本。
  • <uses-configration/>:聲明應用須要的一些系統配置,一共有五種定義
    <uses-configuration android:reqFiveWayNav=["true" | "false"] android:reqHardKeyboard=["true" | "false"] android:reqKeyboardType=["undefined" | "nokeys" | "qwerty" | "twelvekey"] android:reqNavigation=["undefined" | "nonav" | "dpad" | "trackball" | "wheel"] android:reqTouchScreen=["undefined" | "notouch" | "stylus" | "finger"] />
    複製代碼
  • <uses-feture/>:聲明應用使用的軟件或硬件feature
  • <uses-library/>:指定應用須要使用的用戶庫,系統將會在應用運行時裝載這些庫
  • <support-screens/>:指定應用支持的屏幕種類

到這裏,Android已經把APK解析完成,並建立了一個完整的Package對象。那麼接下來就是經過scanPackageChildLI方法將解析好的Package對象添加到系統中的mPackages中了

scanDirLIscanPackageChildLI

前面已經講過,對於掃描成功的應用,會循環執行scanPackageChildLI()將其添加到系統中。

代碼以下:

private PackageParser.Package scanPackageChildLI(......){
        ......
        // 掃描當前應用
        PackageParser.Package scannedPkg = addForInitLI(pkg, parseFlags,
                scanFlags, currentTime, user);
        // 循環掃描子應用
        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
        for (int i = 0; i < childCount; i++) {
            PackageParser.Package childPackage = pkg.childPackages.get(i);
            addForInitLI(childPackage, parseFlags, scanFlags,
                    currentTime, user);
        }
        ......// 省略一個遞納入口
        return scannedPkg;
    }
複製代碼

scanPackageChildLI()方法最重要的操做就是經過addForInitLI()方法來對當前應用作進一步的解析,主要流程以下:

private PackageParser.Package addForInitLI(PackageParser.Package pkg, @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException {
        ......
        // 設置應用資源、代碼等相關的路徑
        pkg.setApplicationVolumeUuid(pkg.volumeUuid);
        pkg.setApplicationInfoCodePath(pkg.codePath);
        pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
        pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
        pkg.setApplicationInfoResourcePath(pkg.codePath);
        pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
        pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
        // 判斷系統應用是否須要升級
        synchronized (mPackages) {
            ......
            // 檢查應用是否屬於rename標籤
            renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
            ......
            // 檢查是否屬於update標籤
            disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(disabledPkgName);
            ......
        }
        // 判斷升級的重點是將當前package與mSettings中的進行比對
        // 咱們知道mSettings中的package數據基本上都是從platform.xml文件中解析出來的
        // 這種對比至關於當前掃描到的應用和其歷史信息的比對,這裏主要是檢查<renamed-package/>和<updated-package/>標籤
        ......
        // 掃描應用文件的簽名
        collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
        ......
        // 處理應用包名有衝突的狀況
        boolean shouldHideSystemApp = false;
        if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
                && !pkgSetting.isSystem()) {
            // 當前掃描到的應用是系統應用,可是也存在一個同名的普通應用
            // 先比較簽名
            if (...) {
                ......
                // 簽名不匹配,刪除掃描到系統的應用
                deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
                pkgSetting = null;
            } else if (newPkgVersionGreater) {
                ......
                // 簽名相同,當前系統應用版本高,移除普通應用
                InstallArgs args = createInstallArgsForExisting(...);
                synchronized (mInstallLock) {
                    args.cleanUpResourcesLI();
                }
            } else {
                // 簽名相同,當前系統應用版本低,須要記錄升級關係
                // 先把系統應用隱藏
                shouldHideSystemApp = true;
                ......
            }
        }
        // 調用 scanPackageNewLI 繼續掃描
        final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
                | SCAN_UPDATE_SIGNATURE, currentTime, user);

        if (shouldHideSystemApp) {
            synchronized (mPackages) {
                mSettings.disableSystemPackageLPw(pkg.packageName, true);
            }
        }
        return scannedPkg;
    }
複製代碼

又繼續調用了scanPackageNewLI()來掃描,咱們簡單看看作了什麼:

private PackageParser.Package scanPackageNewLI(......) throws PackageManagerException {
            ......
        synchronized (mPackages) {
            ......
            SharedUserSetting sharedUserSetting = null;
            if (pkg.mSharedUserId != null) {
                // 若是當前應用的sharedUserId不爲空的話
                // 將該sharedUserId添加到Settings的集合中
                sharedUserSetting = mSettings.getSharedUserLPw(
                        pkg.mSharedUserId, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
                ......
            }
            ......
            try {
                ......
                // 繼續調用 scanPackageOnlyLI 進行掃描操做
                // scanPackageOnlyLI 基本上就是組裝PackageSetting對象的過程
                // ScanResult 中會包含掃描解析出的PackageSetting對象
                final ScanResult result = scanPackageOnlyLI(request, mFactoryTest, currentTime);
                if (result.success) {
                    // 掃描成功後,將應用相關數據組織到系統中
                    commitScanResultsLocked(request, result);
                }
            }
            ......
        }
        return pkg;
    }
複製代碼

代碼中會先對應用的sharedUserId進行檢查,不爲空的話添加到SettingsmSharedUsers集合中。

接下來,調用scanPackageOnlyLI作進一步的掃描,掃描成功後會調用commitScanResultsLocked()package添加到PMSmPackages中。

scanPackageOnlyLIcommitScanResultsLocked()就不細講了哈,對這兩個方法留有印象就好啦。

咱們下面重點了解下PMS啓動後應用安裝的過程。下一篇深刻Android系統(十)PMS-3-應用安裝過程

相關文章
相關標籤/搜索