android系統啟動之PMS啟動原始碼解析

吳鎮佳發表於2016-10-12

PakageManagerService的啟動流程圖

_2016_10_12_10_58_13

1.PakageManagerService概述

PakageManagerService是android系統中一個核心的服務,它負責系統中Package的管理,應該程式的安裝、解除安裝等。後面PakageManagerService簡稱PMS。

2.SystemServer啟動PackageManagerService

我之前的ATA文章有說到,SystemServer程式是Zygote孵化出的第一個程式,該程式主要的工作是啟動android系統服務程式,其中包括PackageManagerService服務,SystemServer啟動PMS關鍵原始碼如下:


    private void startBootstrapServices() {
        //...
         //呼叫PMS的main函式
         mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
         //判斷本次是否為初次啟動,當Zygote或者SystemServer退出時,init會再次啟動它們,所以這裡
         //的firstBoot指的是開機後的第一次啟動
        mFirstBoot = mPackageManagerService.isFirstBoot();
        mPackageManager = mSystemContext.getPackageManager();
      //...
    } 

關鍵點

  • PMS的main函式,該函式是PKM的核心。

3.PMS的main方法

PackageManagerService的主要功能是,掃描Android系統中幾個目標資料夾的APK,建立對應的資料結構來管理Package資訊、四大元件資訊、許可權資訊等各種資訊。例如PKMS解析APK包中的AndroidMainfest.xml,並根據其中宣告的Activity標籤來建立對應的物件並加以保管。PMS的main方法的程式碼如下:

 public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        //new 一個PackageManagerService物件
        PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);
        //PKM註冊到ServiceManager上。ServiceManager相當於安卓系統服務的DNS伺服器
        ServiceManager.addService("package", m);
        return m;
 }

該方法看似很簡單,只有幾行程式碼,然而執行事件卻比較長,這是因為PMS在其建構函式中做了很多的“重體力活”,這也是android啟動速度慢的主要因素之一。安裝的應用越多,系統啟動開機時間越長。
PMS建構函式的主要工作流程

  • 掃描目標資料夾之前的準備工作。
  • 掃描目標資料夾。
  • 掃描之後的工作。

4.PMS的前期準備工作

4.1探究Setting


public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
                SystemClock.uptimeMillis());

        if (mSdkVersion <= 0) {
            Slog.w(TAG, "**** ro.build.version.sdk not set!");
        }

        mContext = context;
        //是否在工廠測試模式下,假定為false
        mFactoryTest = factoryTest;
        mOnlyCore = onlyCore;
        //如果此係統是“eng”版,掃描Package後,不對package做dex優化
        mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
        //用於儲存與螢幕相關的一些屬性,例如螢幕的寬高解析度等。
        mMetrics = new DisplayMetrics();
        mSettings = new Settings(mPackages);
        //第一個引數是字串“android.uid.system”;第二個是SYSTEM_UID,其值為1000,
        //第三個是FLAG_SYSTEM標誌,用於標識系統Package。
        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
                
4.1.1Android系統中UID/GID

UID為使用者ID的縮寫,GID為使用者組ID的縮寫,這兩個概念均與Linux系統中程式的許可權管理有關。一般來說,每一個程式都有一個對應的UID,表示該程式屬於哪個使用者,不同使用者有不同許可權。一個程式也可分屬不同的使用者組,每個使用者組都有對應的許可權。
在android系統中,系統定義的UID/GID在Process.java檔案中,關鍵原始碼如下所示


     /**
     * Defines the UID/GID under which system code runs.
     */
    public static final int SYSTEM_UID = 1000;

    /**
     * Defines the UID/GID under which the telephony code runs.
     */
    public static final int PHONE_UID = 1001;

    /**
     * Defines the UID/GID for the user shell.
     * @hide
     */
    public static final int SHELL_UID = 2000;

    /**
     * Defines the UID/GID for the log group.
     * @hide
     */
    public static final int LOG_UID = 1007;

    /**
     * Defines the UID/GID for the WIFI supplicant process.
     * @hide
     */
    public static final int WIFI_UID = 1010;

    /**
     * Defines the UID/GID for the mediaserver process.
     * @hide
     */
    public static final int MEDIA_UID = 1013;

    /**
     * Defines the UID/GID for the DRM process.
     * @hide
     */
    public static final int DRM_UID = 1019;

    /**
     * Defines the UID/GID for the group that controls VPN services.
     * @hide
     */
    public static final int VPN_UID = 1016;

    /**
     * Defines the UID/GID for the NFC service process.
     * @hide
     */
    public static final int NFC_UID = 1027;

    /**
     * Defines the UID/GID for the Bluetooth service process.
     * @hide
     */
    public static final int BLUETOOTH_UID = 1002;

    /**
     * Defines the GID for the group that allows write access to the internal media storage.
     * @hide
     */
    public static final int MEDIA_RW_GID = 1023;

    /**
     * Access to installed package details
     * @hide
     */
    public static final int PACKAGE_INFO_GID = 1032;

    /**
     * Defines the UID/GID for the shared RELRO file updater process.
     * @hide
     */
    public static final int SHARED_RELRO_UID = 1037;

    /**
     * Defines the start of a range of UIDs (and GIDs), going from this
     * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
     * to applications.
     */
    public static final int FIRST_APPLICATION_UID = 10000;

    /**
     * Last of application-specific UIDs starting at
     * {@link #FIRST_APPLICATION_UID}.
     */
    public static final int LAST_APPLICATION_UID = 19999;

    /**
     * First uid used for fully isolated sandboxed processes (with no permissions of their own)
     * @hide
     */
    public static final int FIRST_ISOLATED_UID = 99000;

    /**
     * Last uid used for fully isolated sandboxed processes (with no permissions of their own)
     * @hide
     */
    public static final int LAST_ISOLATED_UID = 99999;
4.1.2 探究SharedUserSetting

Setting中有一個mShareUsers成員,該成員儲存的是字串變數name與SharedUserSetting健值對。


SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
        //mSharedUsers是一個HashMap.key為字串,值為ShareUserSetting物件
        SharedUserSetting s = mSharedUsers.get(name);
        if (s != null) {
            if (s.userId == uid) {
                return s;
            }
           //...
                       return null;
        }
        建立一個SharedUserSetting物件,並設定為userid為uid
        s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
        s.userId = uid;
        if (addUserIdLPw(uid, s, name)) {
            mSharedUsers.put(name, s);
            return s;
        }
        return null;
    }
    

例如在SystemUI.apk的AndroidManifest.xml檔案中,有關鍵程式碼:

<mainfest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.systemui"
        coreApp="true"
        android:sharedUserId="android.uid.system"
        android:process="system">
        ....

在該標籤中,宣告瞭一個android:sharedUserId的屬性,其值為“android.uid.system”。sharedUserId和UID有關,它的作用是

  • 兩個或者多個宣告瞭同一種sharedUserid的APK可共享彼此的資料,並且可執行在同一程式中。
  • 通過宣告特定的sharedUserId,該APK所在的程式將被賦予指定UID。

例如SystemUI宣告瞭system的uid,執行SystemUI的程式就可享有system使用者所對應的許可權了,實際上就是將該程式的UID設定為system的uid了

接下來分析addUserIdLPw的功能,它主要就是將SharedUserSettings物件儲存到對應的陣列中,程式碼如下

private boolean addUserIdLPw(int uid, Object obj, Object name) {
        //uid不能超出限制,Android對uid進行歸納,系統APK所在程式小於10000
        //應用APK所在程式的uid從10000開始
        if (uid > Process.LAST_APPLICATION_UID) {
            return false;
        }
        //FIRST_APPLICATION_UID = 10000,屬於應用APK
        if (uid >= Process.FIRST_APPLICATION_UID) {
            int N = mUserIds.size();
            //計算索引,其值是uid和FIRST_APPLICATION_UID的差
            final int index = uid - Process.FIRST_APPLICATION_UID;
            while (index >= N) {
                mUserIds.add(null);
                N++;
            }
            //如果索引位置不為空,返回
            if (mUserIds.get(index) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate user id: " + uid
                        + " name=" + name);
                return false;
            }
            //mUserIds儲存應用Package的uid,obj是SharedUserSettings
            mUserIds.set(index, obj);
        } else {
            if (mOtherUserIds.get(uid) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate shared id: " + uid
                                + " name=" + name);
                return false;
            }
            mOtherUserIds.put(uid, obj);
        }
        return true;
    }
    

4.2 XML檔案掃描

接下來是掃描系統目錄下與系統許可權相關的xml檔案,將其存放到PKM中,關鍵原始碼如下:

        // 獲取系統相關的許可權,它主要是解析系統目錄下xml檔案,獲得裝置相關的許可權
        SystemConfig systemConfig = SystemConfig.getInstance();
        mGlobalGids = systemConfig.getGlobalGids();
        mSystemPermissions = systemConfig.getSystemPermissions();
        mAvailableFeatures = systemConfig.getAvailableFeatures();

        synchronized (mInstallLock) {
        // writer
        synchronized (mPackages) {
            //建立一個ThreadHandler物件,實際就是建立一個帶訊息佇列迴圈處理的執行緒,
            //該執行緒的工作是:程式的安裝和解除安裝等。
            mHandlerThread = new ServiceThread(TAG,
                    Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
            mHandlerThread.start();
            //以ThreadHandler執行緒的訊息迴圈(Looper物件)作為引數new一個
            //PackageHandler,因此該Handler的handlemessage方法將執行在此執行緒上
            mHandler = new PackageHandler(mHandlerThread.getLooper());
            Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
            // /data目錄
            File dataDir = Environment.getDataDirectory();
            // /data/data目錄
            mAppDataDir = new File(dataDir, "data");
            // /data/app目錄
            mAppInstallDir = new File(dataDir, "app");
            // /data/app-lib目錄
            mAppLib32InstallDir = new File(dataDir, "app-lib");
            // /data/app-asec目錄            
            mAsecInternalPath = new File(dataDir, "app-asec").getPath();
            // /data/user目錄            
            mUserAppDataDir = new File(dataDir, "user");
            // /data/app-private目錄            
            mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
            //new一個UserManager物件,目前沒有什麼作用,但其前途不可限量。
            //google設想,未來手機將支援多個User,每個User安裝自己的應用
            //該功能為android智慧手機推向企業使用者打下基礎
            sUserManager = new UserManagerService(context, this,
                    mInstallLock, mPackages);

            // 獲取系統相關的許可權,它主要是解析系統目錄下xml檔案,獲得裝置相關的許可權
            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
                    = systemConfig.getPermissions();
            for (int i=0; i<permConfig.size(); i++) {
                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
                BasePermission bp = mSettings.mPermissions.get(perm.name);
                if (bp == null) {
                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
                    mSettings.mPermissions.put(perm.name, bp);
                }
                if (perm.gids != null) {
                    bp.setGids(perm.gids, perm.perUser);
                }
            }
            //獲得系統的Libraries,也就是系統的一些jar
            ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
            for (int i=0; i<libConfig.size(); i++) {
                mSharedLibraries.put(libConfig.keyAt(i),
                        new SharedLibraryEntry(libConfig.valueAt(i), null));
            }

            mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

            mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
                    mSdkVersion, mOnlyCore);

            String customResolverActivity = Resources.getSystem().getString(
                    R.string.config_customResolverActivity);
            if (TextUtils.isEmpty(customResolverActivity)) {
                customResolverActivity = null;
            } else {
                mCustomResolverComponentName = ComponentName.unflattenFromString(
                        customResolverActivity);
            }

            long startTime = SystemClock.uptimeMillis();

進一步我們再觀察SystemConfig是如何解析系統許可權xml檔案的,在SystemConfig的建構函式中,它會去分別讀取etc目錄下的sysconfig,permissions,sysconfig目錄下的檔案。

        SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), false);
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), false);
        // Only read features from OEM config
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), true);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), true);
    }

我們看看到底這些目錄下放著什麼樣的檔案,例如/etc/permissions目錄下的檔案如下圖:
Screenshot_2016_10_10_15_35_50

我們再開啟第一個檔案來探究,沒錯,這個檔案代表藍芽許可權,表示該裝置支援藍芽。具體程式碼如下

<?xml version="1.0" encoding="utf-8"?>

<permissions>
    <feature name="android.hardware.bluetooth" />
</permissions>

總結一下PMS的前期工作,其實就是掃描並解析XML檔案,將其中的資訊儲存到特定的資料結構中。

5.PMS掃描Package

第二個階段的工作主要是掃描系統中的APK,由於需要逐個掃描apk檔案,因此手機上安裝的程式越多,PKM的工作量越大,系統啟動速度越慢,也就是開機時間越長。

5.1系統庫的dex優化

以下的程式碼主要是對系統庫BOOTCLASSPATH指定,或platform.xml定義,或者/system/frameworks目錄下的jar
和apk包進行一次檢查,該dex優化的優化.dex優化後會在相應的目錄生成.odex檔案。/system/frameworks如下圖:
Screenshot_2016_10_11_19_12_30
Screenshot_2016_10_11_19_41_03
關鍵原始碼如下:

            // Set flag to monitor and not change apk file paths when
            // scanning install directories.
            //定義掃描引數
            final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
            //用於儲存已經dex優化過或者不需要優化的包
            final ArraySet<String> alreadyDexOpted = new ArraySet<String>();

            /**
             * Add everything in the in the boot class path to the
             * list of process files because dexopt will have been run
             * if necessary during zygote startup.
             */
             //獲取java啟動類庫的路徑,在init.rc檔案中通過BOOTCLASSPATH環境變數輸出
             //主要是/system/framework/下的系統jar包
            final String bootClassPath = System.getenv("BOOTCLASSPATH");
            final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
            if (bootClassPath != null) {
                String[] bootClassPathElements = splitString(bootClassPath, `:`);
                //迴圈遍歷/system/framework/下的系統jar包的絕對路徑,新增到alreadyDexOpted
                for (String element : bootClassPathElements) {
                    alreadyDexOpted.add(element);
                }
            } 
            if (mSharedLibraries.size() > 0) {
                //...
                 if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                                alreadyDexOpted.add(lib);
                                //dex優化
                                mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);        
                                           
                }
            }

            //...
           
            File frameworkDir = new File(Environment.getRootDirectory(), "framework");
            //framework-res.apk定義了系統常用的資源,還有幾個重要的Activity,如長按Power鍵彈出選擇框
            //不需要dex優化
            alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");

            alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
        
            //掃描framework/下的apk或者jar進行dex優化
            String[] frameworkFiles = frameworkDir.list();
            if (frameworkFiles != null) {
                // TODO: We could compile these only for the most preferred ABI. We should
                // first double check that the dex files for these commands are not referenced
                // by other system apps.
                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                    for (int i=0; i<frameworkFiles.length; i++) {
                        File libPath = new File(frameworkDir, frameworkFiles[i]);
                        String path = libPath.getPath();
                        // 跳過已經存在的包
                        if (alreadyDexOpted.contains(path)) {
                            continue;
                        }
                        // 不是apk或者jar的不做處理
                        if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
                            continue;
                        }
                        int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
                        if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                              //dex優化
                           mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
                            }
                        
                    }
                }
            }

5.2掃描系統的APK

對apk或者jar進行dex優化後,現在PKM進入了重點階段,掃描系統的APK,每一個APK對應一個Package物件,主要是掃描APK的AndroidManifest.xml,解析application標籤及其子標籤actvity、service、recever等,也就是android的四大元件,解析後將它們儲存到Package對應的資料結構中,最後將它們註冊到PKM中,要掃描以下幾個目錄:

  • /system/frameworks:該目錄下的檔案都是系統庫,例如service.jar、framework.jar、framework-res.apk。不過只掃描framework-res.apk檔案
  • /system/app:該目錄下全是預設的系統應用和廠商特定的APK檔案,例如Buletooth.apk、和SystemUI.apk等

解析AndroidManifest.xml關鍵的原始碼如下:

private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
            String[] outError) throws XmlPullParserException, IOException {
            
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            //解析<application>
            if (tagName.equals("application")) {
                if (foundApp) {
                    if (RIGID_PARSER) {
                        outError[0] = "<manifest> has more than one <application>";
                        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                        return null;
                    } else {
                        Slog.w(TAG, "<manifest> has more than one <application>");
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                }

                foundApp = true;
                //解析<application>及其子標籤
                if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
                    return null;
                }
            //解析<application>
            } else if (tagName.equals("overlay")) {

            } else if (tagName.equals("key-sets")) {
                
            } else if (tagName.equals("permission-group")) {
               
            } else if (tagName.equals("permission")) {
              
            } else if (tagName.equals("permission-tree")) {
            //解析<uses-permission>
            } else if (tagName.equals("uses-permission")) {
             
            } else if (tagName.equals("uses-permission-sdk-m")
                    || tagName.equals("uses-permission-sdk-23")) {
                
            } else if (tagName.equals("uses-configuration")) {
              
            } else if (tagName.equals("uses-feature")) {
              
            } else if (tagName.equals("feature-group")) {
               
            } else if (tagName.equals("uses-sdk")) {
        
            } else if (tagName.equals("supports-screens")) {
                         
            } else if (tagName.equals("instrumentation")) {
                
            } else if (tagName.equals("original-package")) {
             
            } else if (tagName.equals("adopt-permissions")) {
                         
            } else if (tagName.equals("uses-gl-texture")) {
                
            } else if (tagName.equals("compatible-screens")) {
                
            } else if (tagName.equals("supports-input")) {
                
            } else if (tagName.equals("eat-comment")) {
                  
            } else if (RIGID_PARSER) {
                

            } else {
               
            }
        }
}

具體看一下怎麼解析application標籤下的四大元件的,依次解析activity,receiver,service,provider,其中可以發現,receiver被當成activity來解析了,PKM通過PackageParser類將解析後的四大元件儲存到對應資料結構中,也就是存放到PackageParser的activities,receivers,providers,services物件中。關鍵原始碼如下:

private boolean parseBaseApplication(Package owner, Resources res,
            XmlPullParser parser, AttributeSet attrs, int flags, String[] outError){
            //...
 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            //activity標籤
            if (tagName.equals("activity")) {
                //解析activity標籤
                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
                        owner.baseHardwareAccelerated);
                   //新增activity到owner.activities中
                owner.activities.add(a);
            //receiver標籤
            } else if (tagName.equals("receiver")) {
                //解析receiver標籤,receiver其實被當成Activity來解析了。
                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
                if (a == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
            //新增activity到owner.activities中
                owner.receivers.add(a);
            //service標籤
            } else if (tagName.equals("service")) {
                //解析service標籤
                Service s = parseService(owner, res, parser, attrs, flags, outError);
                if (s == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
            //新增service到owner.services中
                owner.services.add(s);
            //provider標籤
            } else if (tagName.equals("provider")) {
                Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
                if (p == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }

                owner.providers.add(p);

            } else if (tagName.equals("activity-alias")) {
            } else if (parser.getName().equals("meta-data")) {
                
            } else if (tagName.equals("uses-library")) {
               

            } else if (tagName.equals("uses-package")) {
            }
        }            
}

在PackageParser掃描完一個APK後,此時系統已經根據APK中的AndroidMainifest.xml,建立了一個Package物件,下一步是將該Package加入到系統中。此時呼叫scanPackageDirtyLI方法,scanPackageDirtyLI首先會對packageName為“android”的apk做單獨的處理,該apk其實就是framework-res.apk,它包含了幾個常見的activity

  • ChooserActivity:當startActivity有多個Acitvity符合時,系統會彈出此Acitivity,由使用者選擇合適的應用來處理
  • ShutDownActivity:關機前彈出的選擇對話方塊
  • RingtonePickerAcitivity:鈴聲選擇Activity

scanPackageDirtyLI關鍵程式碼如下:

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
        final File scanFile = new File(pkg.codePath);
        if (pkg.applicationInfo.getCodePath() == null ||
                pkg.applicationInfo.getResourcePath() == null) {
            // Bail out. The resource and code paths haven`t been set.
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                    "Code and resource paths haven`t been set correctly");
        }

        if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
            pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
        } else {
            // Only allow system apps to be flagged as core apps.
            pkg.coreApp = false;
        }

        if ((parseFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
            pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
        }

        if (mCustomResolverComponentName != null &&
                mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
            setUpCustomResolverActivity(pkg);
        }

        if (pkg.packageName.equals("android")) {
            synchronized (mPackages) {
                if (mAndroidApplication != null) {
                   //...  
                }

                //儲存該package資訊
                mPlatformPackage = pkg;
                pkg.mVersionCode = mSdkVersion;
                //儲存該package的ApplicationInfo
                mAndroidApplication = pkg.applicationInfo;

                if (!mResolverReplaced) {
                    //mResolveActivity為ChooserActivity資訊的ActivityInfo
                    mResolveActivity.applicationInfo = mAndroidApplication;
                    mResolveActivity.name = ResolverActivity.class.getName();
                    mResolveActivity.packageName = mAndroidApplication.packageName;
                    mResolveActivity.processName = "system:ui";
                    mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
                    mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
                    mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
                    mResolveActivity.theme = R.style.Theme_Holo_Dialog_Alert;
                    mResolveActivity.exported = true;
                    mResolveActivity.enabled = true;
                    //mResolveInfo用於儲存系統解析Intent後得到的結果資訊,在從PKM查詢滿足某個Intent的
                    //Activity時,返回的就是ResolveInfo,再根據ResolveInfo的activityInfo的資訊得到                          //Activity
                    mResolveInfo.activityInfo = mResolveActivity;
                    mResolveInfo.priority = 0;
                    mResolveInfo.preferredOrder = 0;
                    mResolveInfo.match = 0;
                    mResolveComponentName = new ComponentName(
                            mAndroidApplication.packageName, mResolveActivity.name);
                }
            }
        }
 }

“android“該Package與系統有非常重要的作用,這裡儲存特殊處理儲存該Package的資訊,主要是為了提高執行過程中的效率,例如ChooserActivity使用的地方非常多。
接下里scanPackageDirtyLI方法會對系統其它的Package做處理,關鍵原始碼如下:

//mPackages用於儲存系統內的所有Package,以packageName為key
 if (mPackages.containsKey(pkg.packageName)
                || mSharedLibraries.containsKey(pkg.packageName)) {
           //...
                   }

        if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
            if (mExpectingBetter.containsKey(pkg.packageName)) {
                logCriticalInfo(Log.WARN,
                        "Relax SCAN_REQUIRE_KNOWN requirement for package " + pkg.packageName);
            } else {
                PackageSetting known = mSettings.peekPackageLPr(pkg.packageName);
                if (known != null) {
                    if (DEBUG_PACKAGE_SCANNING) {
                        Log.d(TAG, "Examining " + pkg.codePath
                                + " and requiring known paths " + known.codePathString
                                + " & " + known.resourcePathString);
                    }
                    if (!pkg.applicationInfo.getCodePath().equals(known.codePathString)
                            || !pkg.applicationInfo.getResourcePath().equals(known.resourcePathString)) {
                        throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
                                "Application package " + pkg.packageName
                                + " found at " + pkg.applicationInfo.getCodePath()
                                + " but expected at " + known.codePathString + "; ignoring.");
                    }
                }
            }
        }

        // Initialize package source and resource directories
        File destCodeFile = new File(pkg.applicationInfo.getCodePath());
        File destResourceFile = new File(pkg.applicationInfo.getResourcePath());

        SharedUserSetting suid = null;
        PackageSetting pkgSetting = null;

        if (!isSystemApp(pkg)) {
            // Only system apps can use these features.
            pkg.mOriginalPackages = null;
            pkg.mRealPackage = null;
            pkg.mAdoptPermissions = null;
        }
        //...
        final String pkgName = pkg.packageName;

        final long scanFileTime = scanFile.lastModified();
        final boolean forceDex = (scanFlags & SCAN_FORCE_DEX) != 0;
        //確定執行該package的程式名,一般用package作為程式名
        pkg.applicationInfo.processName = fixProcessName(
                pkg.applicationInfo.packageName,
                pkg.applicationInfo.processName,
                pkg.applicationInfo.uid);

        File dataPath;
        if (mPlatformPackage == pkg) {
            // The system package is special.
            dataPath = new File(Environment.getDataDirectory(), "system");

            pkg.applicationInfo.dataDir = dataPath.getPath();

        } else {
            // This is a normal package, need to make its data directory.
            //該函式返回data/data/packageName
            dataPath = Environment.getDataUserPackageDirectory(pkg.volumeUuid,
                    UserHandle.USER_OWNER, pkg.packageName);

            boolean uidError = false;
            if (dataPath.exists()) {
                //..
            } else {
                if (DEBUG_PACKAGE_SCANNING) {
                    if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
                        Log.v(TAG, "Want this data dir: " + dataPath);
                }
                //該方法呼叫installer傳送install命令,其實就是在/data/data/目錄下建立packageName目錄
                //然後為系統所有的user安裝此apk
                int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
                        pkg.applicationInfo.seinfo);
                //安裝錯誤
                if (ret < 0) {
                    // Error from installer
                    throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
                            "Unable to create data dirs [errorCode=" + ret + "]");
                }

            }

            pkgSetting.uidError = uidError;
        }

        final String path = scanFile.getPath();
        final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
        //在/data/data/pageName/lib下建立和CPU型別對應的目錄,例如ARM平臺的事arm/,MIP平臺的事mips/
        if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
            derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);

            //系統package的native庫統一放在/system/lib下,
            //所以系統不會提取系統package目錄apk包中的native庫
            if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
                    pkg.applicationInfo.primaryCpuAbi == null) {
                setBundledAppAbisAndRoots(pkg, pkgSetting);
                setNativeLibraryPaths(pkg);
            }

        } else {
            if ((scanFlags & SCAN_MOVE) != 0) {
            //
            setNativeLibraryPaths(pkg);
        }
        //...
        if ((scanFlags & SCAN_NO_DEX) == 0) {
            //對該APK做dex優化
            int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instruction sets */,
                    forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, false /* inclDependencies */);
      //如果該apk已經存在,要先殺掉該APK的程式
         if ((scanFlags & SCAN_REPLACING) != 0) {
            killApplication(pkg.applicationInfo.packageName,
                        pkg.applicationInfo.uid, "replace pkg");
        }
        //在此之前,四大元件資訊都是Package物件的私有的,在這裡把它們註冊到PKM內部的財產管理物件中。
        //這樣,PKMS就可對外提供統一的元件資訊。
        synchronized (mPackages) {
                ...
            //註冊該Package中的provider到PKM的mProviders上
            int N = pkg.providers.size();
            StringBuilder r = null;
            int i;
            for (i=0; i<N; i++) {
                PackageParser.Provider p = pkg.providers.get(i);
                p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        p.info.processName, pkg.applicationInfo.uid);
                mProviders.addProvider(p);
                p.syncable = p.info.isSyncable;
                if (p.info.authority != null) {
                   //...           
                    }
            //註冊該Package中的service到PKM的mServices上
            N = pkg.services.size();
            r = null;
            for (i=0; i<N; i++) {
                PackageParser.Service s = pkg.services.get(i);
                s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        s.info.processName, pkg.applicationInfo.uid);
                mServices.addService(s);
                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(` `);
                    }
                    r.append(s.info.name);
                }
            }
            if (r != null) {
                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Services: " + r);
            }
            //註冊該Package中的receiver到PKM的mReceivers上
            N = pkg.receivers.size();
            r = null;
            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 ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(` `);
                    }
                    r.append(a.info.name);
                }
            }
            if (r != null) {
                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Receivers: " + r);
            }
            //註冊該Package中的activity到PKM的mActivities上
            N = pkg.activities.size();
            r = null;
            for (i=0; i<N; i++) {
                PackageParser.Activity a = pkg.activities.get(i);
                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        a.info.processName, pkg.applicationInfo.uid);
                mActivities.addActivity(a, "activity");
                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(` `);
                    }
                    r.append(a.info.name);
                }
            }
            if (r != null) {
                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Activities: " + r);
            }
            //...
          return pkg;
    }

5.3掃描非系統apk

在PackageManagerService建構函式掃描完系統apk後,接下來就是掃描非系統apk,這些apk在/data/app或者/data/app-private中。如下圖:
Screenshot_2016_10_11_19_41_42
下面是關鍵原始碼,scanDirLI已經在前面分析過了。跟系統apk的呼叫過程差不多。

        if (!mOnlyCore) {
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
                scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

                scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                        scanFlags | SCAN_REQUIRE_KNOWN, 0);
        }

5.4掃描結果儲存到檔案中

在PackageManagerService建構函式收尾階段,PMS將前面收集的資訊再整理一次,將已安裝的apk資訊寫到package.xml、pacakage.list和package-stopped.xml中

            //整理更新Permisssion的資訊
             updatePermissionsLPw(null, null, updateFlags);
            //...

            //將資訊寫到package.xml,pacakage.list和package-stopped.xml中
            mSettings.writeLPr();

            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
                    SystemClock.uptimeMillis());

            mRequiredVerifierPackage = getRequiredVerifierLPr();
            mRequiredInstallerPackage = getRequiredInstallerLPr();

        } // synchronized (mPackages)
        } // synchronized (mInstallLock)

        //gc
        Runtime.getRuntime().gc();
  • packages.xml:系統對程式安裝,解除安裝和更新等操作時會更新該檔案,PMS掃描完目標資料夾後建立該檔案,儲存了Package相關的資訊。
  • packages.list:儲存著系統中所有的非系統自帶的APK資訊,程式安裝,解除安裝和更新會更新該檔案。
  • packages-stoped.xml:儲存系統中被使用者強制停止的Package的資訊。

5.4掃描系統和非系統apk總結

PKM在這個過程中工作任務非常繁重,要建立很多的物件,所以它是一個耗時耗記憶體的操作,從流程來看,PKM在這個過程中無非是掃描XML或者APK檔案,但是其中涉及的資料結構及它們的關係較為複雜。


相關文章