APK安裝流程詳解6——PackageManagerService啟動前奏

weixin_34107955發表於2017-10-28
APK安裝流程系列文章整體內容如下:

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

  • 1、Settings類簡介
  • 2、SystemConfig類簡介
  • 3、ServiceThread類與PackageHandler類簡介
  • 4、PackageManagerServcie的systemReady方法簡介
  • 5、PackageManagerServcie的performBootDexOpt方法簡介
  • 6、PackageManagerService啟動的預熱
  • 7、關於shared UID相關問題
  • 8、PackageManagerService方法名中"LI"、"LP"、"LPw"、"LPr"的含義
  • 9、@GuardBy、@SystemApi、@hide Android註解簡介

一、Settings類簡介

由於在後面講解PackageManager流程啟動的時候會 涉及到Setting類,我們就先預熱下
Settings.java原始碼地址

1、重要成員變數

  • private final File mSettingsFilename:代表的是"/data/system/packages.xml"檔案
  • private final File mBackupSettingsFilename:代表的是"/data/system/packages_backup/xml"檔案,這個檔案不一定存在,如果存在,因為他是備份檔案,如果它存在,則說明上次更新packages.xml檔案出錯了。
  • private final File mPackageListFilename:代表的是"/data/system/pcakages.list"檔案
  • final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>():是一個ArrayMap的結構,key是包名,value是PackageSetting。PackageSetting主要包含了一個APP的基本資訊,如安裝位置,lib位置等資訊。
  • final ArrayMap<String, SharedUserSetting> mSharedUsers =new ArrayMap<String, SharedUserSetting>():表示的是一個ArrayMap,key是類似於"android.ui.system"這樣的欄位,在Android中每一個應用都有一個UID,兩個相同的UID的應用可以執行在同一個程式中,所以為了讓兩個應用執行在一個程式中,往往會在AndroidManifest.xml檔案中設定shareUserId這個屬性,這個屬性就是一個字串,但是我們知道Linux系統中一個uid是一個整型,所以為了將字串和整形對應起來,就有了的ShareUserSetting型別,剛才說key是shareUserId這個屬性的值,那麼值就是SharedUserSetting型別了,ShareUserdSetting中除了name(其實就是key),uid對應Linux系統的uid,還有一個列表欄位,記錄了當前系統中有相同的shareUserId的應用。
  • final ArrayMap<String, BasePermission> mPermissions=new ArrayMap<String, BasePermission>():代表的是主要儲存的是"/system/etc/permissions/platform.xml"中的permission標籤內容,因為Android系統是基於Linux系統,所以也有使用者組的概念,在platform.xml中定義了一些許可權,並且制定了哪些使用者具有這些許可權,一旦一個應用屬於某一個使用者組,那麼它就擁有了這個使用者組的所有許可權
  • final ArrayMap<String, BasePermission> mPermissionTrees=new ArrayMap<String, BasePermission>():代表的是"packages.xml"檔案的permission-trees標籤

2、先來看下它的建構函式

程式碼在Settings.java 342行

    Settings(Object lock) {
        this(Environment.getDataDirectory(), lock);
    }

    Settings(File dataDir, Object lock) {
        mLock = lock;
        // 建立RuntimePermissionPersistence物件,其中 RuntimePermissionPersistence是Settings的內部類
        mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
        //初始化mSystemDir
        mSystemDir = new File(dataDir, "system");
        mSystemDir.mkdirs();
        // 設定許可權
        FileUtils.setPermissions(mSystemDir.toString(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG
                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                -1, -1);
        // 初始化mSettingsFilename、mBackupSettingsFilename、mPackageListFilename,請注意他們的檔案目錄
        mSettingsFilename = new File(mSystemDir, "packages.xml");
        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
        mPackageListFilename = new File(mSystemDir, "packages.list");
        FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);

        // Deprecated: Needed for migration
        // 通過註釋 ,知道下面兩個檔案目錄是不推薦使用的
        mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
    }
  • 我們看到Settings的建構函式主要工作就是建立了系統資料夾,一些包管理的檔案:
    • packages.xml和package-backup.xml為一組,用於描述系統所安裝的Package資訊,其中packages-backup.xml是package.xml的備份。
    • packages-list用於描述系統中存在的所有非系統自帶的apk資訊及UID大於10000的apk。當這些APK有變化時,PackageManagerService就會更新該檔案

3、重要方法SharedUserSetting addSharedUserLPw(String, int, int, int)方法

新增特殊使用者名稱稱和UID並關聯

    SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
        // 獲取SharedUserSetting物件,其中mSharedUsers是ArrayMap<String, SharedUserSetting> 來儲存的
        SharedUserSetting s = mSharedUsers.get(name);
        if (s != null) {
            if (s.userId == uid) {
                return s;
            }
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared user, keeping first: " + name);
            return null;
        }
        //如果在ArrayMap中沒有找到對應的SharedUserSetting
        s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
        s.userId = uid;
        if (addUserIdLPw(uid, s, name)) {
            // 將 name和SharedUserSetting物件儲存到mShareUsers的一個ArrayMap中
            mSharedUsers.put(name, s);
            return s;
        }
        return null;
    }

通過上面程式碼可知,Setting中有一個mSharedUsers成員,該成員儲存的是字串和SharedUserSetting鍵值對,通過addSharedUserLPw(String,int,int, int)方法將name和SharedUserSetting物件加到mSharedUsers列表中,這裡我們主要關心兩點,一是ShareUserSetting的架構、二是ShareUserSetting的作用:

3.1、舉例說明

在SystemUI的AndroidManifest.xml裡面,如下所示

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

在xml中,宣告瞭一個名為android:sharedUserId的屬性,其值為"android.uid.system"。shareUserId看起來和UID有關,確實如此,它有兩個作用:

  • 1 兩個或者多個宣告瞭同一種shareUserIds的APK可以共享彼此的資料,並且可以執行在同一個程式中。
  • 2 通過宣告特定的shareUserId,該APK所在程式將被賦予指定的UID。例如,本例中的SystemUI宣告瞭system的uid,執行SystemUI程式就可共享system使用者所對應的許可權(實際上就是將該程式的uid設定為system的uid)

除了在AndroidManifest.xml中宣告shareUserId外,APK在編譯時還必須使用對應的證照進行簽名。例如本例中的SystemUI,在Android.mk中需要額外宣告LOCAL_CERTIFICATE := platform,如此,才可獲得指定的UID。

還需要有三點需要引起大家注意:

  • xml中的sharedUserId屬性指定了一個字串,它是UID的字串描述,故對應資料結構中也應該有一個字串,這樣就把程式碼和XML中的屬性聯絡起來。
  • 在Linxu系統中,真正的UID是一個整形,所以該資料結構中必然有一個整形變數。
  • 多個Package可以宣告同一個shareUserId,因此資料結構必然會儲存那些宣告瞭相同的sharedUserId的Package的某些資訊。

3.2、ShareUserSetting的架構

PackageManagerService的建構函式中建立了一個Settings的例項mSettings,mSettings中有三個成員變數mSharedUsers,mUserIds,mOtherUserIds。addSharedUserLPw()方法都涉及到了這三個成員變數,看到PackageManagerService中建立了Settings的例項物件mSettings,addSharedUserLPw()函式是對mSetting成員變數mShareUsers進行操作。mShareUsers是以String型別的name為key,ShareUserSetting為value的ArrayMap,SharedUserSetting中的成員變數packages時一個PackageSetting型別的ArraySet;PackageSetting繼承自PackageSettingBase,我們可以看到PackageSetting儲存著package的多種資訊

5713484-c7acf17cd8cbd947.png
ShareUserSetting的架構1.png
5713484-17dea9556c978996.png
ShareUserSetting的架構2.png

3.3、SharedUserId作用

我們在系統應用的AndroidManifest.xml中

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
    coreApp="true"
    package="com.android.settings"
    android:sharedUserId="android.uid.system"
    android:versionCode="1"
    android:versionName="1.0">
</manifest>

這裡的android:shareUserId的屬性對應著ShareUserSetting中的name,上面的addSharedUserLPw函式將shareUserId name和一個int 型別的UID對應起來,UID的定義在Process.java中

   //系統程式使用的UID/GID,值為1000
   publicstatic final int SYSTEM_UID = 1000;
   //Phone程式使用的UID/GID,值為1001
   publicstatic final int PHONE_UID = 1001;
   //shell程式使用的UID/GID,值為2000
   publicstatic final int SHELL_UID = 2000;
   //使用LOG的程式所在的組的UID/GID為1007
   publicstatic final int LOG_UID = 1007;
   //供WIF相關程式使用的UID/GID為1010
   publicstatic final int WIFI_UID = 1010;
  //mediaserver程式使用的UID/GID為1013
   publicstatic final int MEDIA_UID = 1013;
   //設定能讀寫SD卡的程式的GID為1015
   publicstatic final int SDCARD_RW_GID = 1015;
   //NFC相關的程式的UID/GID為1025
   publicstatic final int NFC_UID = 1025;
   //有許可權讀寫內部儲存的程式的GID為1023
   publicstatic final int MEDIA_RW_GID = 1023;
   //第一個應用Package的起始UID為10000
   publicstatic final int FIRST_APPLICATION_UID = 10000;
   //系統所支援的最大的應用Package的UID為99999
   publicstatic final int LAST_APPLICATION_UID = 99999;
   //和藍芽相關的程式的GID為2000
   publicstatic final int BLUETOOTH_GID = 2000;

shareUserId與UID相關,作用是:

  • 1、兩個或多個APK或者程式宣告瞭一種shareUserId的APK可以共享彼此的資料,並可以執行在同一程式中(相當於程式是系統的使用者,某些程式可以歸為同一使用者使用,相當於Linux系統的GroupId)。
  • 2、通過宣告特定的sharedUserId,該APK所在的程式將被賦予指定的UID,將被賦予該UID特定的許可權。

3.4、總結

一圖勝千言


5713484-62dd6104baf8ede0.png
mSetting與ShareUserSetting.png

二、SystemConfig類簡介

SystemConfig.java原始碼地址
SystemCofig代表系統配置資訊

1、SystemConfig的建構函式

程式碼在SystemConfig.java 147行

    SystemConfig() {
        // Read configuration from system
        // 從系統中讀取配置資訊  目錄是etc/sysconfig
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), false);
        // Read configuration from the old permissions dir
         // 從系統中讀取配置資訊  目錄是etc/permissions 
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), false);
        // Only read features from OEM config
        //從oem 目錄下讀取sysconfig和permission目錄下的檔案
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), true);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), true);
    }

SystemConfig的建構函式中主要通過readPermission函式將對應目錄下的xml檔案中定義的各個節點讀取出來儲存到SystemConfig成員變數中。第一個引數對應檔案目錄;第二個引數是從xml檔案中解析內容的範圍,比如對於system目錄,是全部解析:ALLOW_ALL。我們到system/etc/permission目錄下可以看到很多xml類的配置檔案,如下:

android.hardware.bluetooth.xml
android.hardware.bluetooth_le.xml
android.hardware.camera.flash-autofocus.xml
android.hardware.camera.front.xml
...
platform.xml
...

這些都是編譯時從framwork指定位置拷貝過來的(framework/native/data/etc/)

readPermission(File,int)方法內部呼叫readPermissionsFromXml()方法來解析xml中各個節點,其中xml涉及到的標籤內容有feature、library、permission、assign-permission等,這些標籤的內容都將解析出來儲存到SystemConfig的對應資料結構的全域性變數中以便以後查詢管理。festure標籤用來描述裝置是否支援硬體特性;library用於指定系統庫,當應用程式執行時,系統會為程式載入一些必要的庫,permission用於將permission與gid關聯,系統會為程式載入一些必要庫,permission用於將permission與gid關聯,assign-permission將system中描述的permission與uid關聯等等;其中解析permission呼叫了readPermission()方法進行許可權的解析

總結下SystemConfig()初始化時解析的xml檔案節點及對應的全域性變數

如下圖


5713484-008d430e2fb78ac5.png
SystemConfig.png

三、ServiceThread類與PackageHandler類簡介

1、ServiceThread類

ServiceThread.java原始碼地址
程式碼很簡單如下:

/**
 * Special handler thread that we create for system services that require their own loopers.
 */
public class ServiceThread extends HandlerThread {
    private static final String TAG = "ServiceThread";

    private final boolean mAllowIo;

    public ServiceThread(String name, int priority, boolean allowIo) {
        super(name, priority);
        mAllowIo = allowIo;
    }

    @Override
    public void run() {
        Process.setCanSelfBackground(false);

        // For debug builds, log event loop stalls to dropbox for analysis.
        if (!mAllowIo && StrictMode.conditionallyEnableDebugLogging()) {
            Slog.i(TAG, "Enabled StrictMode logging for " + getName() + " looper.");
        }
        super.run();
    }
}

通過註釋我們知道,這個類其實是System server建立的執行緒,由於它是繼承自HandlerThread,所以它有一個自己的Looper。

2、PackageHandler類

PackageHandler是PackageManagerService的內部類。

它的程式碼比較多,我就不貼上了,程式碼在PackageManagerService.java 1099行

PackageManageService啟動的時候會將PackageHandler和ServiceThread進行繫結。ServiceThread其實就是PackageManageService的工作執行緒,PackageManageService的各種操作都將利用PackageHandler分發到HandlerThread去處理。

四、PackageManagerServcie的systemReady方法簡介

程式碼在PackageManagerService.java 14658行

    @Override
    public void systemReady() {
        mSystemReady = true;

        // Read the compatibilty setting when the system is ready.
        boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
                mContext.getContentResolver(),
                android.provider.Settings.Global.COMPATIBILITY_MODE, 1) == 1;
        PackageParser.setCompatibilityModeEnabled(compatibilityModeEnabled);
        if (DEBUG_SETTINGS) {
            Log.d(TAG, "compatibility mode:" + compatibilityModeEnabled);
        }

        int[] grantPermissionsUserIds = EMPTY_INT_ARRAY;
        synchronized (mPackages) {
            // Verify that all of the preferred activity components actually
            // exist.  It is possible for applications to be updated and at
            // that point remove a previously declared activity component that
            // had been set as a preferred activity.  We try to clean this up
            // the next time we encounter that preferred activity, but it is
            // possible for the user flow to never be able to return to that
            // situation so here we do a sanity check to make sure we haven't
            // left any junk around.
            ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>();
            for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
                PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
                removed.clear();
                for (PreferredActivity pa : pir.filterSet()) {
                    if (mActivities.mActivities.get(pa.mPref.mComponent) == null) {
                        removed.add(pa);
                    }
                }
                if (removed.size() > 0) {
                    for (int r=0; r<removed.size(); r++) {
                        PreferredActivity pa = removed.get(r);
                        Slog.w(TAG, "Removing dangling preferred activity: "
                                + pa.mPref.mComponent);
                        pir.removeFilter(pa);
                    }
                    mSettings.writePackageRestrictionsLPr(
                            mSettings.mPreferredActivities.keyAt(i));
                }
            }

            for (int userId : UserManagerService.getInstance().getUserIds()) {
                if (!mSettings.areDefaultRuntimePermissionsGrantedLPr(userId)) {
                    grantPermissionsUserIds = ArrayUtils.appendInt(
                            grantPermissionsUserIds, userId);
                }
            }
        }

         // 多使用者服務
        sUserManager.systemReady();

        // If we upgraded grant all default permissions before kicking off.
        // 升級所有已獲的預設許可權
        for (int userId : grantPermissionsUserIds) {
            mDefaultPermissionPolicy.grantDefaultPermissions(userId);
        }

        // Kick off any messages waiting for system ready
        // 處理所有等待系統準備就緒的訊息
        if (mPostSystemReadyMessages != null) {
            for (Message msg : mPostSystemReadyMessages) {
                msg.sendToTarget();
            }
            mPostSystemReadyMessages = null;
        }

        // Watch for external volumes that come and go over time
         // 觀察外部儲存裝置
        final StorageManager storage = mContext.getSystemService(StorageManager.class);
        storage.registerListener(mStorageListener);

        mInstallerService.systemReady();
        mPackageDexOptimizer.systemReady();
        MountServiceInternal mountServiceInternal = LocalServices.getService(
                MountServiceInternal.class);
        mountServiceInternal.addExternalStoragePolicy(
                new MountServiceInternal.ExternalStorageMountPolicy() {
            @Override
            public int getMountMode(int uid, String packageName) {
                if (Process.isIsolated(uid)) {
                    return Zygote.MOUNT_EXTERNAL_NONE;
                }
                if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) {
                    return Zygote.MOUNT_EXTERNAL_DEFAULT;
                }
                if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
                    return Zygote.MOUNT_EXTERNAL_DEFAULT;
                }
                if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
                    return Zygote.MOUNT_EXTERNAL_READ;
                }
                return Zygote.MOUNT_EXTERNAL_WRITE;
            }

            @Override
            public boolean hasExternalStorage(int uid, String packageName) {
                return true;
            }
        });
    }

五、PackageManagerServcie的performBootDexOpt方法簡介

程式碼在PackageManagerService.java 6024行

    @Override
    public void performBootDexOpt() {

        // 確保只有system或者 root uid有許可權執行該方法
        enforceSystemOrRoot("Only the system can request dexopt be performed");

        // Before everything else, see whether we need to fstrim.
        try {

            // 執行在同一個程式,此處拿到MountServcie服務端
            IMountService ms = PackageHelper.getMountService();
            if (ms != null) {

               //處於更新狀態,則執行fstrim 
                final boolean isUpgrade = isUpgrade();
                boolean doTrim = isUpgrade;
                if (doTrim) {
                    Slog.w(TAG, "Running disk maintenance immediately due to system update");
                } else {

                     // interval預設值為3天
                    final long interval = android.provider.Settings.Global.getLong(
                            mContext.getContentResolver(),
                            android.provider.Settings.Global.FSTRIM_MANDATORY_INTERVAL,
                            DEFAULT_MANDATORY_FSTRIM_INTERVAL);
                    if (interval > 0) {
                        final long timeSinceLast = System.currentTimeMillis() - ms.lastMaintenance();
                        if (timeSinceLast > interval) {
                            // 距離他上次fstrim時間超過3天,則執行fstrim
                            doTrim = true;
                            Slog.w(TAG, "No disk maintenance in " + timeSinceLast
                                    + "; running immediately");
                        }
                    }
                }
                if (doTrim) {
                    if (!isFirstBoot()) {
                        try {
                            ActivityManagerNative.getDefault().showBootMessage(
                                    mContext.getResources().getString(
                                            R.string.android_upgrading_fstrim), true);
                        } catch (RemoteException e) {
                        }
                    }
                    // 此處ms是指MountServcie,該過程傳送訊息H_FSTRIM給handler,然後再向vold傳送fstrim命令
                    ms.runMaintenance();
                }
            } else {
                Slog.e(TAG, "Mount service unavailable!");
            }
        } catch (RemoteException e) {
            // Can't happen; MountService is local
        }

        final ArraySet<PackageParser.Package> pkgs;
        synchronized (mPackages) {
             // 清空延遲執行dexopt操作的app,獲取dexopt操作的app集合
            pkgs = mPackageDexOptimizer.clearDeferredDexOptPackages();
        }

        if (pkgs != null) {
            // Sort apps by importance for dexopt ordering. Important apps are given more priority
            // in case the device runs out of space.
            ArrayList<PackageParser.Package> sortedPkgs = new ArrayList<PackageParser.Package>();
            // Give priority to core apps.
            for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
                PackageParser.Package pkg = it.next();
                 // 將pkgs中的核心app新增到sortedPkgs
                if (pkg.coreApp) {
                    if (DEBUG_DEXOPT) {
                        Log.i(TAG, "Adding core app " + sortedPkgs.size() + ": " + pkg.packageName);
                    }
                    sortedPkgs.add(pkg);
                    it.remove();
                }
            }
            // Give priority to system apps that listen for pre boot complete.
             // 獲取監聽PRE_BOOT_COMPLETE的系統app集合
            Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
            ArraySet<String> pkgNames = getPackageNamesForIntent(intent);
            for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
                PackageParser.Package pkg = it.next();
                // 將pkg監聽PRE_BOOT_COMPLETE的app新增到sortedPkgs
                if (pkgNames.contains(pkg.packageName)) {
                    if (DEBUG_DEXOPT) {
                        Log.i(TAG, "Adding pre boot system app " + sortedPkgs.size() + ": " + pkg.packageName);
                    }
                    sortedPkgs.add(pkg);
                    it.remove();
                }
            }
            // Filter out packages that aren't recently used.
             // 獲取pkgs中最近一週使用過的app
            filterRecentlyUsedApps(pkgs);
            // Add all remaining apps.
            for (PackageParser.Package pkg : pkgs) {
                if (DEBUG_DEXOPT) {
                    Log.i(TAG, "Adding app " + sortedPkgs.size() + ": " + pkg.packageName);
                }

                // 將最近一週的app新增到sortedPkgs
                sortedPkgs.add(pkg);
            }

            // If we want to be lazy, filter everything that wasn't recently used.
            if (mLazyDexOpt) {

                filterRecentlyUsedApps(sortedPkgs);
            }
            int i = 0;
            int total = sortedPkgs.size();
            File dataDir = Environment.getDataDirectory();
            long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
            if (lowThreshold == 0) {
                throw new IllegalStateException("Invalid low memory threshold");
            }
            for (PackageParser.Package pkg : sortedPkgs) {
                long usableSpace = dataDir.getUsableSpace();
                if (usableSpace < lowThreshold) {
                    Log.w(TAG, "Not running dexopt on remaining apps due to low memory: " + usableSpace);
                    break;
                }
                performBootDexOpt(pkg, ++i, total);
            }
        }
    }

本方法的主要功能:

當處於升級或者三天未執行fstrim,則本地會是否會執行fstrim操作,對sortedPkgs中的app執行dexopt優化,其中包含:

  • mDeferredDexOpt中的核心app
  • mDeferredDexopt中監聽PRE_BOOT_COMPLETE的app
  • mDeferredDexOpt中最近一週使用過的app

上面方法中涉及了2個核心方法:

  • private void filterRecentlyUsedApps(Collection<PackageParser.Package>) 方法
  • private void performBootDexOpt(PackageParser.Package, int, int)方法

下面我們就來依次看下:

1、filterRecentlyUsedApps(Collection<PackageParser.Package>) 方法

程式碼在PackageManagerService.java 6133行

    private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs) {
        // Filter out packages that aren't recently used.
        //
        // The exception is first boot of a non-eng device (aka !mLazyDexOpt), which
        // should do a full dexopt.
        if (mLazyDexOpt || (!isFirstBoot() && mPackageUsage.isHistoricalPackageUsageAvailable())) {
            int total = pkgs.size();
            int skipped = 0;
            long now = System.currentTimeMillis();
            for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
                PackageParser.Package pkg = i.next();
                // 過濾最近使用過的app
                long then = pkg.mLastPackageUsageTimeInMills;
                if (then + mDexOptLRUThresholdInMills < now) {
                    if (DEBUG_DEXOPT) {
                        Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
                              ((then == 0) ? "never" : new Date(then)));
                    }
                    i.remove();
                    skipped++;
                }
            }
            if (DEBUG_DEXOPT) {
                Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
            }
        }
    }

這個方法主要是就是過濾掉最近使用過的app,過濾條件就是篩選mDexOptLRUThresholdInMills時間。不同版本的條件不同:

  • 對於Eng版本,則只會對30分鐘之內使用過的app執行優化
  • 對於使用者版本,則會將使用者最近以後組使用過的app執行優化

2、performBootDexOpt(PackageParser.Package, int, int)方法

程式碼在PackageManagerService.java 6176行

    private void performBootDexOpt(PackageParser.Package pkg, int curr, int total) {
        if (DEBUG_DEXOPT) {
            Log.i(TAG, "Optimizing app " + curr + " of " + total + ": " + pkg.packageName);
        }
        if (!isFirstBoot()) {
            try {
                ActivityManagerNative.getDefault().showBootMessage(
                        mContext.getResources().getString(R.string.android_upgrading_apk,
                                curr, total), true);
            } catch (RemoteException e) {
            }
        }
        PackageParser.Package p = pkg;
        synchronized (mInstallLock) {
            mPackageDexOptimizer.performDexOpt(p, null /* instruction sets */,
                    false /* force dex */, false /* defer */, true /* include dependencies */,
                    false /* boot complete */);
        }
    }

我們看到這個方法其實內部很簡單,主要就是呼叫mPackageDexOptimizer的performDexOpt方法,那我們就來看下這個方法的具體實現

2.1、performBootDexOpt(PackageParser.Package, int, int)方法

程式碼在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;
            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();
                }
            }
        }
    }

2.2、performBootDexOpt(PackageParser.Package, int, int)方法

程式碼在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;
            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();
                }
            }
        }
    }

通過這個方法我們知道,其實它本質是呼叫performDexOptLI(PackageParser.Package,String[],String[],boolean.String,CompilerStats.PackageStats)方法

2.3、performDexOptLI(PackageParser.Package,String[],String[],boolean.String,CompilerStats.PackageStats)方法

程式碼在PackageDexOptimizer.java 98行

    private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
            String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter,
            CompilerStats.PackageStats packageStats) {
        final String[] instructionSets = targetInstructionSets != null ?
                targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);

        if (!canOptimizePackage(pkg)) {
            return DEX_OPT_SKIPPED;
        }

        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);

        boolean isProfileGuidedFilter = DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter);
        // If any part of the app is used by other apps, we cannot use profile-guided
        // compilation.
        if (isProfileGuidedFilter && isUsedByOtherApps(pkg)) {
            checkProfiles = false;

            targetCompilerFilter = getNonProfileGuidedCompilerFilter(targetCompilerFilter);
            if (DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) {
                throw new IllegalStateException(targetCompilerFilter);
            }
            isProfileGuidedFilter = false;
        }

        // If we're asked to take profile updates into account, check now.
        boolean newProfile = false;
        if (checkProfiles && isProfileGuidedFilter) {
            // Merge profiles, see if we need to do anything.
            try {
                newProfile = mInstaller.mergeProfiles(sharedGid, pkg.packageName);
            } catch (InstallerException e) {
                Slog.w(TAG, "Failed to merge profiles", e);
            }
        }

        final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
        final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;

        boolean performedDexOpt = false;
        boolean successfulDexOpt = true;

        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
            for (String path : paths) {
                int dexoptNeeded;
                try {
                    dexoptNeeded = DexFile.getDexOptNeeded(path,
                            dexCodeInstructionSet, targetCompilerFilter, newProfile);
                } catch (IOException ioe) {
                    Slog.w(TAG, "IOException reading apk: " + path, ioe);
                    return DEX_OPT_FAILED;
                }
                dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
                if (PackageManagerService.DEBUG_DEXOPT) {
                    Log.i(TAG, "DexoptNeeded for " + path + "@" + targetCompilerFilter + " is " +
                            dexoptNeeded);
                }

                final String dexoptType;
                String oatDir = null;
               // 判斷類別
                switch (dexoptNeeded) {
                    case DexFile.NO_DEXOPT_NEEDED:
                        continue;
                    case DexFile.DEX2OAT_NEEDED:
                        // 需要把dex轉化為oat
                        dexoptType = "dex2oat";
                        oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
                        break;
                    case DexFile.PATCHOAT_NEEDED:
                        dexoptType = "patchoat";
                        break;
                    case DexFile.SELF_PATCHOAT_NEEDED:
                        dexoptType = "self patchoat";
                        break;
                    default:
                        throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded);
                }

                String sharedLibrariesPath = null;
                if (sharedLibraries != null && sharedLibraries.length != 0) {
                    StringBuilder sb = new StringBuilder();
                    for (String lib : sharedLibraries) {
                        if (sb.length() != 0) {
                            sb.append(":");
                        }
                        sb.append(lib);
                    }
                    sharedLibrariesPath = sb.toString();
                }
                Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
                        + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
                        + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
                        + " target-filter=" + targetCompilerFilter + " oatDir = " + oatDir
                        + " sharedLibraries=" + sharedLibrariesPath);
                // Profile guide compiled oat files should not be public.
                final boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
                final int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
                final int dexFlags = adjustDexoptFlags(
                        ( isPublic ? DEXOPT_PUBLIC : 0)
                        | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
                        | (debuggable ? DEXOPT_DEBUGGABLE : 0)
                        | profileFlag
                        | DEXOPT_BOOTCOMPLETE);

                try {
                    long startTime = System.currentTimeMillis();

                    // 核心程式碼
                    mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
                            dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
                            sharedLibrariesPath);
                    performedDexOpt = true;

                    if (packageStats != null) {
                        long endTime = System.currentTimeMillis();
                        packageStats.setCompileTime(path, (int)(endTime - startTime));
                    }
                } catch (InstallerException e) {
                    Slog.w(TAG, "Failed to dexopt", e);
                    successfulDexOpt = false;
                }
            }
        }

        if (successfulDexOpt) {
            // If we've gotten here, we're sure that no error occurred. We've either
            // dex-opted one or 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;
        } else {
            return DEX_OPT_FAILED;
        }
    }

通過上面程式碼我們知道這塊程式碼最後是呼叫的mInstaller的dexopt方法,關於Installer我們將會在下一篇文章中詳細講解

六、PackageManagerService啟動的預熱

PackageManagerService 在啟動時會掃描所有APK檔案和Jar包,然後把它們的資訊讀取出來,儲存在記憶體中,這樣系統執行時就能迅速找到各種應用和元件的資訊。掃描過程中如果遇到沒有優化過的檔案,還要執行轉化工作。(Android 5.0是odex格式,Android 5.0之後是oat格式)。啟動後,PackageManagerService將提供安裝包的資訊查詢服務以及應用的安裝和解除安裝服務。

PackageManagerService中有一些規則比較難以理解,而在程式碼中有很大的區域在描述這些規則,這些規則設計安裝包的關係。所以說如果能理解它們的關係,對於後續理解PackageManagerService的啟動有很大的幫助

(一)、應用分類

Android 中應用可以簡單地分成兩大類:"系統應用"和"普通應用"

  • 1、系統應用是指位於/system/app 或者 /System/priv-app 目錄下的應用。priv-app目錄是從Android 4.4開始出現的目錄,它存放的是一些系統底層的應用,如Settin、SystemUI等。/system/app中存放的是一些系統級別的應用,如:Phone、Contact等。在PackageManagerService 中,所謂的system 應用包括這兩個目錄下的應用,而所謂的private應用就是指 /priv-app 目錄下的應用。
  • 2、普通應用就是使用者安裝的應用,位於目錄/data/app下。普通應用也可以安裝在SD上,但是系統應用不可以。

(二)、幾種特殊情況的

1、系統應用升級不在同一個目錄裡面

通常情況下系統應用是不能刪除的,但是可以升級。升級的方法是安裝一個包名相同,但是如果更高的版本號的應用在/data/app目錄下。對於系統中這種的升級情況,Android會在/data/system/package.xml檔案中用<update-package>記錄被覆蓋的系統應用資訊。

2、兩個包名的情況

我們知道應用的包名通常是AndroidManifest.xml裡面用"package"屬性來指定,同時還可以用<original-package>來指定原始包的名字。但是,這樣的一個應用執行時,看到的包名是什麼?答案是兩種都有可能。如果安裝的裝置中不存在和原始報名相同的系統應用,看到的包名將是package屬性定義的名稱。如果裝置上還存在低版本的,而且包名和原始包名不相同的應用,這樣雖然最後安裝執行的是新安裝的應用,但是我們看到應用的名稱還是原始的包名,因為,這樣也構成了升級關係。Android會在package.xml中用標籤<rename-package>記錄這種改名的情況。

####### 3、其他內容

每個應用還有儲存資料的目錄,位於/data/data/packageName/目錄下。資料目錄中常見的兩個子目錄:shared_prefs目錄中儲存的是應用的設定檔案,database目錄中儲存的是應用的資料庫檔案

七、關於shared UID相關問題

(一)、Android中的UID、GID與GIDS的簡介

說ShardUID就不能不說下Android中的UID、GID與GIDS

Android 是一個許可權分離的作業系統。這是因為Android是基於Linux系統的許可權管理機制,通過為每一個Application分配不同的uid和gid,從而使得不同的Application之間的私有資料和訪問達到隔離的目的。Android的許可權分離的基礎是Linux已有的uid、gid、gids基礎上的。

1、UID

在Android系統上安裝一個應用程式,就會為他分配一個UID,其中普通的Android APP的UID是從10000開始分配的。而10000以下則是系統UID。

2、GID

對於普通的應用程式來說,GID等於UID。由於每個應用程式的UID和GID不相同,因為不管是Native層還是Java層都能夠達到保護私有資料的作用。

3、GIDS

GIDS是由框架在Application安裝過程中生成的,與Application申請的許可權有關。如果Application申請相應的許可權被granted,而且其中有對應的GIDS,那麼這個Application的GIDS將包含這個GIDS

(二)、shared UID

假設app A要和app B 共享一個UID,那麼 app B 不需要配置 Share UID,它會獲得一個普通的UID,需要配置 Share UID 的app A ,這時候系統會將 app B的 UID分配給 app A,這樣就達到了共享 UID的目的。所以 兩個app 有相同的 UID,並不意味著 它們會執行在同一個程式中。一個 app 執行在哪一個程式,一般是由它們 package名稱和 UID來決定的,也就是說,UID相同,但是package名稱不同的兩個 app是執行在兩個不同的程式中的。如果兩個app,具有相同的UID和包名相同,加上簽名相同,一般認為是一個應用程式,即覆蓋安裝。
系統給每一個app都分配一個 UID 是用來控制許可權的,因為兩個程式具有相同的UID,就意味著它們有相同的許可權,可以進行資源共享。相同的UID的資源共享只是針對Linux檔案系統的訪問全許可權控制,不同程式間的資料是無法共享訪問的。

八、PackageManagerService方法名中"LI"、"LP"、"LPw"、"LPr"的含義

(一)、結尾是"LI"、"LP"、"LPw"、"LPr"的方法

Android 7.0多了一個"LIF"方法,我也加上去了,主要是Android 6.0的部分"LI"方法變更為"LIF"方法。


5713484-b53f9d5915849d85.png
方法.png

(二) L、I、P、w、r的含義

要想明白方法名中 LI、LIF、LPw、LPr的含義需要首先了解PackageManagerService內部使用的兩個鎖。因為LI、LIF、LPw 、LPr中的"L" 指的是Lock,而後面跟的"I"和"P"指的是兩個鎖,"I" 表示 mInstallLock 同步鎖。"P"表示 mPackage 同步鎖。為什麼我會這麼說,大家請看下面程式碼

程式碼在PackageManagerService.java 469行

// Lock for state used when installing and doing other long running
// operations.  Methods that must be called with this lock held have
// the suffix "LI".
final Object mInstallLock = new Object();
// Keys are String (package name), values are Package.  This also serves
// as the lock for the global state.  Methods that must be called with
// this lock held have the prefix "LP".
@GuardedBy("mPackages")
final ArrayMap<String, PackageParser.Package> mPackages =
        new ArrayMap<String, PackageParser.Package>();

先來簡單的翻譯一下注釋:

  • 在安裝和執行其他耗時操作的鎖定狀態。必須使用此鎖定呼叫的方法,該方法字尾帶有"LI"
  • 鍵(key)是String型別,值(value)是包。這也是全域性的鎖,必須使用這個鎖來呼叫具有字首"LP"的方法

所以說:

  • mPackage 同步鎖:是指操作mPackage時,用synchronized (mPackages) {}保護起來。mPackage同步鎖用來保護記憶體中已經解析的包資訊和其他相關狀態。mPackage同步鎖是細顆粒度的鎖,只能短時間支有這個鎖,因為掙搶這個鎖的請求很多。短時間持有mPackage鎖,可以讓其他請求等待的時間短些
  • mInstallLock同步鎖:是指安裝APK的時候,對安裝的處理要用用synchronized (mInstallLock) {}保護起來。mInstallLock 用來保護所有對 installd 的訪問。installd通常包含對應用資料的繁重操作。

PS:由於installd 是單執行緒的,並且installd的操作通常很慢,所以在已經持有mPackage同步鎖的時候,不要再去請求mInstallLock 同步鎖。反之,在已經持有mInstallLock 同步鎖的時候,可以去請求mPackage同步鎖

  • r 表示讀
  • w 表示寫

(三) 、LI、LIF、LPw、LPr的含義

方法名 使用方式
xxxLI() 必須先持有mInstallLock的鎖
xxxLP() 必須先持有mPackage的鎖
xxxLIF() 必須先持有mInstallLock的鎖,並且正在被修改的包(package) 必須被凍結(be frozen)
xxxLPr() 必須先持有mPackages鎖,並且只用於讀操作
xxxLPw() 必須先持有 mPackage所以,並且只用於寫操作

(四) 、總結

簡單的總結下就是
上面中的"L"代表lock的首字母L,”I“表示InstallLock的首字母,"P"表示package的首字母,"r"表示read的首字母,”w“表示write的首字母

九、@GuardBy、@SystemApi、@hide Android註解簡介

(一)、@GuardBy註解

在閱讀PackageManagerService.java的原始碼時,裡面有一個重要的成員變數mInstaller,它使用了@GuardBy註解

如下,程式碼在PackageManagerService.java 448行

    @GuardedBy("mInstallLock")
    final Installer mInstaller;

它類似於Java關鍵字——synchronized關鍵字,但使用代替鎖。可以這樣使用這個註解

public class demo {
  @GuardedBy("this")
  public String string;
}

正如我們看到的,用法是@GuardedBy(lock),這意味著有保護的欄位或者方法只能線上程持有鎖時被某些執行緒訪問。我們可以將鎖指定為以下型別:

  • this:在其類中定義欄位的物件的固有鎖。
  • classs-name.this:對於內部類,可能有必要消除"this"的歧義;class-name.this指定允許你指定"this"引用的意圖。
  • itself:僅供參考欄位;欄位引用的物件。
  • field-name:鎖定物件由欄位名指定的(例項或靜態)欄位引用。
  • class-name.field-name:鎖物件由class-name.field-name指定的靜態欄位引用
  • method-name():鎖定物件通過呼叫命名的nil-ary方法返回。
  • class-name:指定類的Class物件做鎖定物件

說白了,就是告知開發者,被@GuardedBy 註解標註的變數要用同步鎖保護起來

舉例說明

public class BankAccount {
  private Object personInfo = new Object();

  @GuardedBy("personInfo")
  private int amount;
}

在上面的程式碼中,當有人獲取了個人資訊的同步鎖時,可以訪問金額,因此BankAccount中的金額由個人資訊保護。

(二)、@SystemApi、@PrivateApi與@hide註解簡介

在閱讀PackageManager.java的原始碼時,裡面有會大量用到兩個註解@SystemApi和@hide註解

如下,程式碼在PackageManager.java 448行

     /**
     * Listener for changes in permissions granted to a UID.
     *
     * @hide
     */
    @SystemApi
    public interface OnPermissionsChangedListener {

        /**
         * Called when the permissions for a UID change.
         * @param uid The UID with a change.
         */
        public void onPermissionsChanged(int uid);
    }

@SystemApi 其實是@PrivateApi的別名;使用@hide標記的API可以不使用@SystemApi進行標記;但是當使用@SystemApi標記的API則必須使用@hide標記,在Android原始碼中,有兩種型別的API無法通過標準的SDK進行訪問。

@SystemApi和@hide的區別:

  • 隱藏的方法(使用@hide修飾的)仍然可以通過Java反射機制進行訪問;@hide標示只是javadoc的一部分(也是Android doc的一部分),所以@hide修飾符僅僅指示了method/class/field不會被暴露到API中。
  • 使用@SystemApi修飾的method/class/field,無法通過java反射機制進行訪問(會觸發invocationTargetException異常)

上一篇文章 APK安裝流程詳解5——PackageInstallerService和Installer
上一篇文章 APK安裝流程詳解7——PackageManagerService的啟動流程(上)

相關文章