1. 程式人生 > >Android Fk:PKMS(1)-PackageManagerService的Binder架構及初始化

Android Fk:PKMS(1)-PackageManagerService的Binder架構及初始化

Android Fk:PKMS(1)-PackageManagerService的Binder架構及初始化

一、PKMS的概述及其Binder架構

1. PKMS的基本功能:

  PackageManagerService是Android的核心服務,負責系統中的Package的管理,應用的安裝,解除安裝,資訊查詢等。
  手機平臺的Android程式碼PKMS為了優化或者新功能更改了很多,本文主要基於Android 7.1.1_r6原始碼進行程式碼分析。

2.PKMS的架構:

  PKMS家族主要的組成,摘自鄧凡平的《深入理解android卷2》第四章,該圖高度概括了PackageManagerService的家族組成:
這裡寫圖片描述

2.1 IPackageManager類

  IPackageManager是由aidl檔案生成的介面類,原始碼中無IPackageManager.java,該檔案是編譯時由IPackageManager.aidl(/frameworks/base/core/java/android/content/pm/IPackageManager.aidl)生成的。
  檢視整理後該類的原始碼,主要看實現結構,可以看到生成的IPackageManager結構如上圖所示:

//out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content/pm/IPackageManager.java
public interface IPackageManager extends android.os.IInterface { public static abstract class Stub extends android.os.Binder implements android.content.pm.IPackageManager { private static final java.lang.String DESCRIPTOR = "android.content.pm.IPackageManager"; public Stub() { this
.attachInterface(this, DESCRIPTOR); } public static android.content.pm.IPackageManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof android.content.pm.IPackageManager))) { return ((android.content.pm.IPackageManager) iin); } return new android.content.pm.IPackageManager.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: {...} case TRANSACTION_checkPackageStartable: {...} case TRANSACTION_isPackageAvailable: {...} .... } ... } ... private static class Proxy implements android.content.pm.IPackageManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public void checkPackageStartable(java.lang.String packageName, int userId) throws android.os.RemoteException { ... mRemote.transact(Stub.TRANSACTION_checkPackageStartable, _data, _reply, 0); } @Override public boolean isPackageAvailable(java.lang.String packageName, int userId) throws android.os.RemoteException { ... mRemote.transact(Stub.TRANSACTION_isPackageAvailable, _data, _reply, 0); ... } } } ... }

2.2 PackageManagerService

  PackaManagerService是服務的實現類,繼承自IPackageManager.Stub類,因此實際上它是一個Binder;
  PackageManager是個抽象類,對IPackageManager介面類中的業務函式進行了封裝,Client通過Context的getPackageManager()函式獲得一個PackageManager的物件pm,通過pm去呼叫介面公開的方法;
  舉個例子:
  通過PackageManager物件呼叫服務方法:

//packages/apps/PackageInstaller/src/com/android/packageinstaller/utils/AppUtils.java
 public static String getAppLableByPkgName(Context context , String pkgName){
        String lable = "";
        PackageManager pm = context.getPackageManager();
        try {
            ApplicationInfo info = pm.getApplicationInfo(pkgName, 0);
            lable = info.loadLabel(pm).toString();
        }catch (PackageManager.NameNotFoundException e){
            e.printStackTrace();
        }
        return lable;
    }

2.3 ApplicationPackageManager

   pm實際是PackageManager的子類ApplicationPackageManager型別,ApplicationPackageManager類對抽象類PackageManager中的抽象方法進行了具體實現,pm呼叫介面方法也是通過ApplicationPackageManager中的方法去實現。

//frameworks/base/core/java/android/app/ContextImpl.java
  @Override
    public PackageManager getPackageManager() {
        if (mPackageManager != null) {
            return mPackageManager;
        }
        //得到PKMS的服務代理
        IPackageManager pm = ActivityThread.getPackageManager();
        if (pm != null) {
            // Doesn't matter if we make more than one instance.
            //構造成一個ApplicationPackageManager型別的物件返回
            return (mPackageManager = new ApplicationPackageManager(this, pm));
        }
        return null;
    }

2.4 ApplicationPackageManager通過PKMSProxy型別的成員變數與Binder驅動通訊

  ApplicationPackageManager是通過其一個IPackageManager變數mPM來和Binder驅動進行通訊的,從mPM的賦值來看mPM實際上一個IPackManager.Stub.Proxy型別的物件,例如:

//frameworks/base/core/java/android/app/ApplicationPackageManager.java
    @Override
    public String[] getPackagesForUid(int uid) {
        try {
            return mPM.getPackagesForUid(uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

  構造ApplicationPackageManager時賦值為IPackageManager型別的引數給mPm:

    ApplicationPackageManager(ContextImpl context,
                              IPackageManager pm) {
        mContext = context;
        mPM = pm;
    }

  我們看新建ApplicationPackageManager例項的地方:

//frameworks/base/core/java/android/app/ActivityThread.java
try {
         ii = new ApplicationPackageManager(null, getPackageManager())
                        .getInstrumentationInfo(data.instrumentationName, 0);
} catch (PackageManager.NameNotFoundException e) {
           throw new RuntimeException( "Unable to find instrumentation info for: " + data.instrumentationName);
}

  接著看ActivityThread中的getPackageManager()方法:

//frameworks/base/core/java/android/app/ActivityThread.java
    public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            return sPackageManager;
        }
        IBinder b = ServiceManager.getService("package");//得到PKMS的BinderProxy物件
        sPackageManager = IPackageManager.Stub.asInterface(b);//得到PKMS的IPackageManager.Stub.Proxy物件
        return sPackageManager;
    }

  從之前的Java Binder分析中我們知道這裡的通過IPackageManager.Stub類的asInterface()得到了PKMS的服務物件IPackageManager.Stub.Proxy(BinderProxy(BpBinder(handle))),上面提供的IPackageManager.java的程式碼中也有asInterface()方法的實現;
  PKMS的Binder架構如下:
這裡寫圖片描述
  從圖中可以看出通過aidl技術,Client通過PKMS的proxy去跨程序呼叫到Server端的Stub,底層依然是依靠Binder機制進行支撐;
  Client獲取PackageManagerService的代理物件過程:
  
    這裡寫圖片描述
  通過一層一層的封裝,Client呼叫PKMS的過程最終是通過獲得IPackageManager.Stub.Proxy類物件進行方法呼叫的;

二、PKMS的啟動

  PKMS是系統核心服務,由SystemServer建立啟動:

public final class SystemServer {
    private PackageManagerService mPackageManagerService;
    private void run() {
        ...
        startBootstrapServices();
          startOtherServices();
        ...
    }

    private void startBootstrapServices() {
        ...
        mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
        mFirstBoot = mPackageManagerService.isFirstBoot();
        mPackageManager = mSystemContext.getPackageManager();
        if (!mOnlyCore) {
             boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
                           false);
                   if (!disableOtaDexopt) {
                         try {
                             OtaDexoptService.main(mSystemContext, mPackageManagerService);
                         } catch (Throwable e) {...} finally {...}
                     }
          }
        ...
    }

    private void startOtherServices() {
      if (!mOnlyCore) {
                Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "UpdatePackagesIfNeeded");
                try {
                    mPackageManagerService.updatePackagesIfNeeded();
                } catch (Throwable e) {...}
            }

      ...

      try {
            mPackageManagerService.systemReady();//PMS ok
      } catch (Throwable e) {
            reportWtf("making Package Manager Service ready", e);
      }
    }
}

  PKMS的main函式分析:

    public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        // Self-check for initial settings.
        PackageManagerServiceCompilerMapping.checkProperties();
        PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);
        m.enableSystemUserPackages();
        ServiceManager.addService("package", m);
        return m;
    }

  我們看到PKMS的main函式主要做了一些系統屬性的檢查,然後構造了一個PKMS的例項,enable一些系統使用者的package,然後將構造出的PKMS和PKMS的名字註冊至ServiceManager中;
其中最重要的部分就是PKMS的建構函式中的操作了。
  PKMS的建構函式程式碼比較長,我們選取重要的幾個步驟進行分析:

1. 構造Setting類

 mSettings = new Settings(mPackages);
 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);

 首先來看下Settings的建構函式:

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
Settings(Object lock) {
        this(Environment.getDataDirectory(), lock);
    }

Settings(File dataDir, Object lock) {
        mLock = lock;

        mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

        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 = 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);

        final File kernelDir = new File("/config/sdcardfs");
        mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;

        // Deprecated: Needed for migration
        //註釋來看,這兩個檔案應該是不再建議使用的
        mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
    }

  我們看到Settings的建構函式主要工作就是建立了系統資料夾,一些包管理的檔案:
   packages.xml和packages-backup.xml為一組,用於描述系統所安裝的Package資訊,其中packages-backup.xml是packages.xml的備份
   packages-list用於描述系統中存在的所有非系統自帶的apk資訊及UID大於10000的apk。當這些APK有變化時,PKMS就會更新該檔案;
  我們可以到手機對應目錄找的到這些檔案,可以pull出來看看。

這裡寫圖片描述

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

    SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
        ...
        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;
    }

  addSharedUserLPw函式將name和SharedUserSetting物件加到mSharedUsers的列表中,這裡我們主要關心兩點,一是ShareUserSetting的架構、二是ShareUserSetting的作用:

1.2.1. ShareUserSetting的架構:

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

這裡寫圖片描述

1.2.2. SharedUserId作用:

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

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

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

"android.uid.system"  SYSTEM_UID = 1000;
"android.uid.phone"  PHONE_UID = 1001;
"android.uid.log"  LOG_UID = 1007;
"android.uid.nfc"  NFC_UID = 1027;
"android.uid.bluetooth"  BLUETOOTH_UID = 1002;
"android.uid.shell"   SHELL_UID = 2000;

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

  小結一下,這一部分主要構造了mSetting例項,初始化了一些檔案,添加了一些特殊的使用者的名字和ID之間的對應關係。
  這裡寫圖片描述

2. 獲取系統配置儲存值本地變數

 SystemConfig systemConfig = SystemConfig.getInstance();
 mGlobalGids = systemConfig.getGlobalGids();//取出全域性groupid儲存到PKMS的全域性變數中
 mSystemPermissions = systemConfig.getSystemPermissions();//取出系統許可權儲存到PKMS的全域性變數中
 mAvailableFeatures = systemConfig.getAvailableFeatures();//取出可用的feature儲存在PKMS的全域性變數中

  看到這部分主要是獲得SystemConfig例項,利用SystemConfig例項獲取到系統配置儲存到PKMS本地的全域性變數中。
  先看下SystemConfig的建構函式:

    SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
        //從odm目錄下讀取sysconfig和permission目錄下的檔案
        int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
        //從oem目錄下讀取sysconfig和permission目錄下的檔案讀取定製是否支援某個feature
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
        //Remove vulkan specific features
        if (SystemProperties.getBoolean("persist.graphics.vulkan.disable", false)) {
            removeFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL);
            removeFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION);
        }
    }

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

pollux:/ # cd system/etc/permissions/
pollux:/system/etc/permissions # ls -1
...
android.hardware.bluetooth.xml
android.hardware.bluetooth_le.xml
android.hardware.camera.flash-autofocus.xml
android.hardware.camera.front.xml
...
platform.xml
...

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

//frameworks/base/core/java/com/android/server/SystemConfig.java
void readPermission(XmlPullParser parser, String name)
            throws IOException, XmlPullParserException {

        final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false);
        //將permission標籤中的Permission封裝成PermissionEntry實體
        final PermissionEntry perm = new PermissionEntry(name, perUser);
        mPermissions.put(name, perm);//將name和實體以鍵值對的方式存入mPermissions全域性變數中

        int outerDepth = parser.getDepth();
        int type;
        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();
            if ("group".equals(tagName)) {
                String gidStr = parser.getAttributeValue(null, "gid");
                if (gidStr != null) {
                    //將groupid加入到permissionentry實體中的gid中,即permission的name,實體與groupid對應上了
                    int gid = Process.getGidForName(gidStr);
                    perm.gids = appendInt(perm.gids, gid);
                } else {
                    Slog.w(TAG, "<group> without gid at "
                            + parser.getPositionDescription());
                }
            }
            XmlUtils.skipCurrentTag(parser);
        }
    }

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

這裡寫圖片描述

小結:
  PKMS建立的SystemConfig負責解析系統的xml配置檔案,儲存至SystemConfig的對應資料結構的全域性變數中;這部分PKMS從SystemConfig中取出GlobalGids,SystemPermissions和AvailableFeatures儲存至PKMS中相應的全域性變數中。

3. HandlerThread執行緒啟動

 mHandlerThread = new ServiceThread(TAG,
     Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
 mHandlerThread.start();
 mHandler = new PackageHandler(mHandlerThread.getLooper());
 mProcessLoggingHandler = new ProcessLoggingHandler();
 //將該handler加入到Watchdog監測中,安裝應用可能會有大量的I/O操作會比較耗時
 //因此這裡的WATCHDOG_TIMEOUT設定為了10min,一般為60s或30s
 Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);

  這裡啟動了一個名為TAG即PackageManager的Handler執行緒,該執行緒是PKMS的工作執行緒,PKMS的各種操作都將利用這裡的mHandler分發至該HandlerThread去分別處理:

//frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
 class PackageHandler extends Handler {
    public void handleMessage(Message msg) {
        try {
             doHandleMessage(msg);
        } finally {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        }
    }
    void doHandleMessage(Message msg) {
         switch (msg.what) {
               //具體操作方法
               ...
         }
    }

 }

  PKMS的功能涉及系統中所有包的管理及系統中所有元件的查詢工作,工作分量相當重,因此開一個Handler執行緒作為PKMS的工作執行緒十分有必要。

4. 重要變數的賦值

   在data/下建立相應目錄,儲存路徑到對應的全域性變數

File dataDir = Environment.getDataDirectory();
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
mRegionalizationAppInstallDir = new File(dataDir, "app-regional");
//針對Android系統中多使用者場景
sUserManager = new UserManagerService(context, this, mPackages);

  mFirstBoot變數賦值:

 mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

  mIsUpgrade變數的賦值:

mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);

  將SystemConfig中儲存的許可權配置資訊和library資訊取出放置PKMS中的全域性變數mSettings和mSharedLibraries中:

 // Propagate permission configuration in to package manager.
            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);
                }
            }
   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));
             }

5. 確保外部庫sharelibrary優化dexopt

            final List<String> allInstructionSets = InstructionSets.getAllInstructionSets();
            final String[] dexCodeInstructionSets =
                    getDexCodeInstructionSets(
                            allInstructionSets.toArray(new String[allInstructionSets.size()]));

            /**
             * Ensure all external libraries have had dexopt run on them.
             */
            if (mSharedLibraries.size() > 0) {
                // NOTE: For now, we're compiling these system "shared libraries"
                // (and framework jars) into all available architectures. It's possible
                // to compile them only when we come across an app that uses them (there's
                // already logic for that in scanPackageLI) but that adds some complexity.
                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                    for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
                        final String lib = libEntry.path;
                        if (lib == null) {
                            continue;
                        }

                        try {
                            // Shared libraries do not have profiles so we perform a full
                            // AOT compilation (if needed).
                            int dexoptNeeded = DexFile.getDexOptNeeded(
                                    lib, dexCodeInstructionSet,
                                    getCompilerFilterForReason(REASON_SHARED_APK),
                                    false /* newProfile */);
                            if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                                mInstaller.dexopt(lib, Process.SYSTEM_UID, dexCodeInstructionSet,
                                        dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/,
                                        getCompilerFilterForReason(REASON_SHARED_APK),
                                        StorageManager.UUID_PRIVATE_INTERNAL,
                                        SKIP_SHARED_LIBRARY_CHECK);
                            }
                        } catch (FileNotFoundException e) {
                            Slog.w(TAG, "Library not found: " + lib);
                        } catch (IOException | InstallerException e) {
                            Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "
                                    + e.getMessage());
                        }
                    }
                }
            }

  這裡通過例項mInstaller去呼叫dexopt去做ShareLibrary檔案的優化,mInstaller是SystemService啟動PKMS時傳入的Installd的代理物件,最終將由installd在底層實現,這裡主要講述初始化的流程,所以不多關注詳細的優化過程,將在後面文章中進行詳細描述。

6. 掃描檔案apk資料夾

  下面到PKMS初始化過程中的重頭戲,PKMS在開機後需要將系統中所有的package資訊統計管理起來,首先掃描系統的資料夾:

// Collect vendor overlay packages.
            // (Do this before scanning any apps.)
            // For security and version matching reason, only consider
            // overlay packages if they reside in VENDOR_OVERLAY_DIR.
            File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
            scanDirTracedLI(vendorOverlayDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

            // Find base frameworks (resource packages without code).
            scanDirTracedLI(frameworkDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED,
                    scanFlags | SCAN_NO_DEX, 0);

            // Collected privileged system packages.
            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
            scanDirTracedLI(privilegedAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

            // Collect ordinary system packages.
            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
            scanDirTracedLI(systemAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

            // Collect all vendor packages.
            File vendorAppDir = new File("/vendor/app");
            try {
                vendorAppDir = vendorAppDir.getCanonicalFile();
            } catch (IOException e) {
                // failed to look up canonical path, continue with original one
            }
            scanDirTracedLI(vendorAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

            // Collect all OEM packages.
            final File oemAppDir = new File(Environment.getOemDirectory(), "app");
            scanDirTracedLI(oemAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

  這一部分是PMKS初始化的重量級部分,從code中看到這裡掃描的資料夾有
  “/vendor/overlay”,
  “framework”,
  “system/priv-app”,
  “system/app”,
  “/vendor/app”,
  “oem/app”
  掃描資料夾的操作會一步調一步最終呼叫到scanPackageDirtyLI()函式,在這個函式中PKMS將package中的元件管理起來從而實現系統應用的安裝過程,如圖:
這裡寫圖片描述
 從本圖中我們主要知道兩點,第一,PackageParser將package進行徹底的解析,第二,PKMS將上面解析得到的資料統計到自身變數中用於管理;
 PackageParser的解析過程和scanPackageDirtyLI()方法中的操作是十分重要的,學習瞭解對日常包管理有很大的幫助,本篇不多敘述,後面將詳細學習該部分內容再更新;
系統目錄下的應用system目錄下,每次開機都進行掃描一次,經過掃描與解析package,將所有系統應用的元件資訊註冊至PKMS,即系統應用的安裝過程;
  平時我們push系統應用至system目錄下時,需要重啟才能生效也就是因為重啟時做了system目錄下應用的掃描解析,即push到system下對應目錄下的新的apk會在開機時安裝。

7.掃描三方應用目錄

接下來會對三方應用的安裝目錄進行掃描解析:

  if (!mOnlyCore) {
       scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
       scanDirTracedLI(mDrmAppPrivateInstallDir, mDefParseFlags
                        | PackageParser.PARSE_FORWARD_LOCK,
                        scanFlags | SCAN_REQUIRE_KNOWN, 0);
       scanDirLI(mEphemeralInstallDir, mDefParseFlags
                        | PackageParser.PARSE_IS_EPHEMERAL,
                        scanFlags | SCAN_REQUIRE_KNOWN, 0);
   }

  掃描三方的apk,這裡的mAppInstallDir即是”data/app/”目錄,注意到這裡的scanFlags或上了SCAN_REQUIRE_KNOWN,這裡是為了處理一些使用者手動安裝的系統應用OTA升級時出現的一些問題,這裡不做細節分析。
  由上面掃描系統apk目錄中的分析可知,這裡掃描三方應用目錄,通過PackageParser的解析,最終將這些目錄下的apk的資訊統計至PKMS的本地變數中。

8.核心應用首次開機等情況下需要dexopt優化

   如果是首次開機或者OTA升級之後我們需要對新的應用進行dexopt優化:

   if ((isFirstBoot() || isUpgrade() || VMRuntime.didPruneDalvikCache()) && !onlyCore) {
                long start = System.nanoTime();
                List<PackageParser.Package> coreApps = new ArrayList<>();
                for (PackageParser.Package pkg : mPackages.values()) {
                    if (pkg.coreApp) {
                        coreApps.add(pkg);
                    }
                }
                int[] stats = performDexOptUpgrade(coreApps, false,
                        getCompilerFilterForReason(REASON_CORE_APP));
                final int elapsedTimeSeconds =
                        (int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);
                MetricsLogger.histogram(mContext, "opt_coreapps_time_s", elapsedTimeSeconds);

   }

  從程式碼中performDexOptUpgrade()跟下去我們知道最終會呼叫mInstaller.dexopt()的方法,而mInsatller是PKMS初始化前面步驟中由systemServer賦值過來的Installd的關聯物件,用於和Installd通訊,最終會呼叫到Insatlld中的對應優化方法,這裡不做過多敘述,之後會詳細學習Installd;

9.清理記憶體收尾

  最後又是一些重要的本地變數進行初始化賦值,PKMS初始化的操作大概已經完成,主動呼叫GC回收記憶體:

// Now after opening every single application zip, make sure they
// are all flushed.  Not really needed, but keeps things nice and
// tidy.
Runtime.getRuntime().gc();

三、總結

  1. 本文主要學習了PKMS的程式碼結構及其初始化的大概過程,初始化過程中重要的操作還有很多,無法一一列出,日後遇到具體問題再具體分析;
  2. PKMS事實上是一個binder(PKMS繼承自IPackageManager.Stub,而IPackageManager.Stub繼承自Binder),PKMS執行在SystemServer程序,而SystemServer程序在init的時候就起了一個Binder執行緒去監聽Binder驅動,因此PKMS通過SystemServer程序實現與Binder驅動的聯動;
  3. Client端通過獲取PKMS的服務代理物件IPackageManager.Stub.Proxy,Proxy和Stub都實現了IPackageManager的介面,Client呼叫Proxy中的介面方法,通過Proxy中的BinderProxy物件傳遞經過Binder驅動呼叫到服務端的Binder中的方法,即Stub中的介面實現,PKMS是Stub的子類,Stub中的介面方法在子類中具體實現,如下圖總結:
    這裡寫圖片描述

  4. PKMS初始化時的主要操作:
    a.PKMS中重要的本地變數初始化賦值
    b.庫及Apk進行dexopt優化
    c.掃描apk目錄,統計元件資訊至PKMS中

  5. PKMS掃描目錄的目的:PKMS在掃描apk的目錄時會使用PackageParser類對apk的androidManifest.xml檔案進行解析,儲存到Package型別的變數中,然後通過PKMS的scanPackageDirtyLI()方法將解析後的元件資料統計到PKMS的本地變數中,用於管理查詢呼叫,當系統中任意某個apk的package發生改變時,如解除安裝,升級等操作都會更新package的統計資料到PKMS,PKMS正是基於擁有系統中所有Package的資訊才能勝任Package管理者的角色;
  6. 注意不同目錄下掃描不同,PackageParser在解析apk包的時候對於不同安裝目錄下的apk解析規則是不同的,其中有很多重要的細節,這也正是adb push和adb install不同方式的安裝應用可能有不同效果的原因所在;
  7. 對於系統應用安裝的認識,當掃描解析完系統apk的目錄,將元件資料統計到PKMS管理起來,這個過程即可以看作是系統目錄下apk的安裝流程,即系統應用的開機安裝流程;

參考部落格:

Android7.0 PackageManagerService (2) PKMS建構函式的主要工作:
http://blog.csdn.net/Gaugamela/article/details/52637814

PackageManagerService的啟動過程分析
http://blog.csdn.net/yuanzeyao/article/details/42215521

深入理解PackageManagerService:
http://blog.csdn.net/u013598111/article/details/49278851

PackageManagerService(Android5.1)深入分析(一)建構函式
http://blog.csdn.net/kc58236582/article/details/50498131

PackageManagerService分析
http://www.jianshu.com/p/0b0d6f684580