PackageManagerService源码分析二:开机应用扫描


前言

上一章我们以PackageManagerService装载结束为切入点,通过dump package结果分析了PMS部分类、以及类之间的关联关系。本章我们正向分析,以开机为切入点,带着结果,探索相关对象的装载流程。
注:笔者撰写本文的软件环境为android 13


一、PackageManagerService初始化

PackageManagerService服务是在SystemServer中实例化,PackageManagerService涉及到的工作流程较多,限于篇幅原因,本文主要分析两部分:1.读取应用历史记录 2. 应用扫描。入口代码如下所示:

//framework/base/service/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(...) {
   
   ...
   //1.读取应用历史记录
   mFirstBoot = !mSettings.readLPw(computer,
                    mInjector.getUserManagerInternal().getUsers(
                    /* excludePartial= */ true,
                    /* excludeDying= */ false,
                    /* excludePreCreated= */ false));
   ...
   //2.应用扫描
   mCacheDir = PackageManagerServiceUtils.preparePackageParserCache(mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
   PackageParser2 packageParser = mInjector.getScanningCachingPackageParser();
   mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds,
                    startTime);
   mInitAppsHelper.initNonSystemApps(packageParser, userIds, startTime);
   packageParser.close();
}

PackageManagerService源码分析一:从dump package源码,探索类之间关联关系一文中我们分析了PMS部分类的关联关系,本文通过如下两个流程,探索相关类的装载流程。

  • 应用状态读取
  • 应用扫描

二、readLPw

readLpw部分代码如下

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
boolean readLPw(@NonNull List<UserInfo> users) {
   
     FileInputStream str = null;
     //packages-backup.xml为packages.xml的备份文件
     //xml写入数据的时候为了防止写入失败(掉电等)
     if (mBackupSettingsFilename.exists()) {
   
         try {
   
             str = new FileInputStream(mBackupSettingsFilename);
             ...
         } catch (java.io.IOException e) {
   
             ...
         }
     }
     try {
   
          if (str == null) {
   
              if (!mSettingsFilename.exists()) {
   
                  ...
                  return false;
              }
              str = new FileInputStream(mSettingsFilename);
          }
          XmlPullParser parser = Xml.newPullParser();
          parser.setInput(str, StandardCharsets.UTF_8.name());

          int type;
          while ((type = parser.next()) != XmlPullParser.START_TAG
                  && type != XmlPullParser.END_DOCUMENT) {
   
              ;
          }

          if (type != XmlPullParser.START_TAG) {
   
              ...
              return false;
          }

          int outerDepth = parser.getDepth();
          while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                  && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
   
              if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
   
                  continue;
              }

              ...
          }
      } catch (XmlPullParserException e) {
   
          ...
      }
   //packages-stopped.xml为老版本上的文件,为了系统向下兼容,需要将mStoppedPackagesFilename数据迁移
   if (mBackupStoppedPackagesFilename.exists()
                || mStoppedPackagesFilename.exists()) {
   
            // Read old file
            readStoppedLPw();
            mBackupStoppedPackagesFilename.delete();
            mStoppedPackagesFilename.delete();
            // Migrate to new file format
            writePackageRestrictionsLPr(UserHandle.USER_SYSTEM);
        } else {
   
            for (UserInfo user : users) {
   
                //读取用户的package-restrictions.xml
                readPackageRestrictionsLPr(user.id);
            }
        }
        for (UserInfo user : users) {
   
        //读取应用权限
         mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
        }
}

结合readLPw相关代码,可以看到流程主要读取了如下几个文件:

  1. packages.xml:故名思义,记录包应用代码位置、权限、签名等信息
  2. packages.list:包信息列表,记录部分包信息
  3. packages-stopped.xml:记录被停用的包信息。新系统平台迁移,将信息转化为package-restrictions.xml存储
  4. package-restrictions.xml:记录被停用、偏好设置等用户行为产生应用包信息,常见的用户行为影响的应用为开机引导:开机引导流程走完,需要将应用设置为不可用,只有恢复出厂设置才能重新进入开机引导。
  5. runtime-permissions.xml:记录包应用权限信息

本小节主要分析 packages.xml、package-restrictions.xml、runtime-permissions.xml文件的解析。解析文件的部分时序图流程如下:
readLPw时序图

1. 解析 packages.xml

packages.xml文件部分内容如下所示,记录包应用代码位置、权限、签名等信息。

<packages>
   ...
   <permissions>
     <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
     ...
   </permissions>
    ...
    </package>
    <package name="com.blan.audiotest" codePath="/data/app/~~9WZY4gbCfZm0_Ig8mDxsPg==/com.blan.audiotest-9VKlHs9xCrkt3gttbPby5w==" nativeLibraryPath="/data/app/~~9WZY4gbCfZm0_Ig8mDxsPg==/com.blan.audiotest-9VKlHs9xCrkt3gttbPby5w==/lib" publicFlags="810073926" privateFlags="-1946152704" ft="18fad8c6390" ut="18fad8c67ee" version="1" userId="10134" packageSource="0" loadingProgress="1.0" domainSetId="733b2979-7f7d-44a8-82ec-34be8fb26899">
        ...
    </package>
    ...
     <shared-user name="android.uid.system" userId="1000">
        <sigs count="1" schemeVersion="3">
            <cert index="3" />
        </sigs>
    </shared-user>
    ...
     <domain-verifications>
        <active>
          <package-state packageName="com.android.networkstack.tethering" id="91fcd1a1-f52a-48ca-bb57-78a98cd83d40" />
          ...
        </active>
     </domain-verifications>   
     <domain-verifications-legacy> 
       <user-states packageName="com.android.providers.telephony">
            <user-state userId="0" state="0" />
            <user-state userId="10" state="0" />
        </user-states>
        ...
     </domain-verifications-legacy>
     ...
</packages>

该文件存储在系统data目录下,下一小节我们介绍package.xml文件的创建。

1.1 Package.xml文件实例

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
Settings(File dataDir, PermissionSettings permission,
            Object lock) {
   
    ...
    mSystemDir = new File(dataDir, "system");
    mSystemDir.mkdirs();
    ...
	mSettingsFilename = new File(mSystemDir, "packages.xml");
	mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
}

其中,dataDir的路径为Environment.getDataDirectory()为data目录,结合相关代码可知packages.xml和packages-backup.xml文件存储在/data/system目录中。packages-backup.xml可以理解为packages.xml的备份文件。下一小节介绍packages-backup.xml备份文件的创建。

1.2 packages-backup.xml文件创建

packages-backup.xml文件是在更新packages.xml文件时创建,创建方法如下所示:

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
void writeLPr() {
   
   ...
   if (mSettingsFilename.exists()) {
   
            if (!mBackupSettingsFilename.exists()) {
   
                if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
   
                    Slog.wtf(PackageManagerService.TAG,
                            "Unable to backup package manager settings, "
                            + " current changes will be lost at reboot");
                    return;
                }
            } else {
   
                mSettingsFilename.delete();
                Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
            }
    }
    ...
    mBackupSettingsFilename.delete();
    ...
}

可知packages-backup.xml备份文件是通过改名方式实现的,该文件在更新packages.xml前创建、更新时删除。文件备份策略是为了处理紧急下电、异常重启等场景下packages.xml更新异常引入的机制。下一节我们介绍文件的解析。

1.3 文件解析

1.3.1 packages-backup.xml存在

异常下电、重启开机后备份文件packages-backup.xml存在,开机后会优先解析该文件。

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
boolean readLPw(@NonNull List<UserInfo> users) {
   
        FileInputStream str = null;
        if (mBackupSettingsFilename.exists()) {
   
            try {
   
                str = new FileInputStream(mBackupSettingsFilename);
                mReadMessages.append("Reading from backup settings file\n");
                PackageManagerService.reportSettingsProblem(Log.INFO,
                        "Need to read from backup settings file");
                if (mSettingsFilename.exists()) {
   
                    // If both the backup and settings file exist, we
                    // ignore the settings since it might have been
                    // corrupted.
                    Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
                            + mSettingsFilename);
                    mSettingsFilename.delete();
                }
            } catch (java.io.IOException e) {
   
                // We'll try for the normal settings file.
            }
        }
}

1.3.2 packages.xml不存在

如果packages-backup.xml文件不存在,流程进入packages.xml文件解析,代码如下所示:

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
boolean readLPw(@NonNull List<UserInfo> users) {
   
   ...
   if (str == null) {
   
        if (!mSettingsFilename.exists()) {
   
            mReadMessages.append("No settings file found\n");
            PackageManagerService.reportSettingsProblem(Log.INFO,
                    "No settings file; creating initial state");
            // It's enough to just touch version details to create them
            // with default values
            findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
            findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
            return false;
        }
        str = new FileInputStream(mSettingsFilename);
    }
   ...
}

如果mSettingsFilename.exists()为false,即packages.xml文件不存在,则PackageManagerService会认为这是第一次开机,直接返回,不会进入后续其他文件解析流程。apk解析完成后,会创建packages.xml文件。

1.3.3 解析文件packages.xml

packages-backup.xml为packages.xml文件的备份文件,解析packages-backup.xml与packages.xml逻辑是一致的,部分代码如下所示:

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
boolean readLPw(@NonNull List<UserInfo> users) {
   
    ...
     int outerDepth = parser.getDepth();
     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 (tagName.equals("package")) {
   
             readPackageLPw(parser);
         } else if (tagName.equals("permissions")) {
   
             mPermissions.readPermissions(parser);
         } else if (tagName.equals("permission-trees")) {
   
             mPermissions.readPermissionTrees(parser);
         } else if (tagName.equals("shared-user")) {
   
             readSharedUserLPw(parser);
         } else if (tagName.equals("preferred-packages")) {
   
             // no longer used.
         } else if (tagName.equals("preferred-activities")) {
   
             // Upgrading from old single-user implementation;
             // these are the preferred activities for user 0.
             readPreferredActivitiesLPw(parser, 0);
         } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
   
             // TODO: check whether this is okay! as it is very
             // similar to how preferred-activities are treated
             readPersistentPreferredActivitiesLPw(parser, 0);
         } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
   
             // TODO: check whether this is okay! as it is very
             // similar to how preferred-activities are treated
             readCrossProfileIntentFiltersLPw(parser, 0);
         } else if (tagName.equals(TAG_DEFAULT_BROWSER)) {
   
             readDefaultAppsLPw(parser, 0);
         } else if (tagName.equals("updated-package")) {
   
             readDisabledSysPackageLPw(parser);
         } 
         ...
    }
    ...
}

解析packages.xml会实例化对象,部分对象在PackageManagerService源码分析一:从dump package源码,探索类之间关联关系一文中有提及。本小节介绍部分对象的创建。

1.3.3.1 readPackageLPw

解析package标签头,流程进入到readPackageLPw,创建PackageSetting。readPackageLPw部分代码如下:

private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
   
   ...
   try {
   
      // xml解析
      name = parser.getAttributeValue(null, ATTR_NAME);
      realName = parser.getAttributeValue(null, "realName");
      idStr = parser.getAttributeValue(null, "userId");
      uidError = parser.getAttributeValue(null, "uidError");
      sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
      codePathStr = parser.getAttributeValue(null, "codePath");
      if (name == null) {
   
               ...
       } else if (codePathStr == null) {
   
           PackageManagerService.reportSettingsProblem(Log.WARN,
                   "Error in package manager settings: <package> has no codePath at "
                           + parser.getPositionDescription());
       } else if (userId > 0) {
   
           packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                   new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
                   secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
                   pkgPrivateFlags, null /*usesStaticLibraries*/,
                   null /*usesStaticLibraryVersions*/, null /*mimeGroups*/);
           if (packageSetting == null) {
   
               PackageManagerService.reportSettingsProblem(Log.ERROR, "Failure adding uid "
                       + userId + " while parsing settings at "
                       + parser.getPositionDescription());
           } else {
   
               packageSetting.setTimeStamp(timeStamp);
               packageSetting.firstInstallTime = firstInstallTime;
               packageSetting.lastUpdateTime = lastUpdateTime;
           }
       } else if (sharedIdStr != null) {
   
           if (sharedUserId > 0) {
   
               packageSetting = new PackageSetting(name.intern(), realName, new File(
                       codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
                       primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                       versionCode, pkgFlags, pkgPrivateFlags, sharedUserId,
                       null /*usesStaticLibraries*/,
                       null /*usesStaticLibraryVersions*/,
                       null /*mimeGroups*/);
               packageSetting.setTimeStamp(timeStamp);
               packageSetting.firstInstallTime = firstInstallTime;
               packageSetting.lastUpdateTime = lastUpdateTime;
               mPendingPackages.add(packageSetting);
               if (PackageManagerService.DEBUG_SETTINGS)
                   Log.i(PackageManagerService.TAG, "Reading package " + name
                           + ": sharedUserId=" + sharedUserId + " pkg=" + packageSetting);
           } else {
   
               ...
           }
       } else {
   
          ...
       }
   } catch (NumberFormatException e) {
   
      ...
   }
   ...
}

流程中,根据解析信息不同,创建、存储PackageSetting分如下两种场景,

  1. userId > 0
    创建PackageSetting,将创建的对象添加到mPackages中。
  2. sharedIdStr != null
    创建PackageSetting,将创建的对象添加到mPendingPackages中。packages.xml解析完成后,遍历mPendingPackages。创建PackageSettings与SharedUserSetting依赖关系,将创建的对象添加到mPackages中。
1.3.3.2 readSharedUserLPw

解析shared-user标签头,流程进入到readSharedUserLPw,创建SharedUserSetting。

1.3.3.3 readDisabledSysPackageLPw

解析updated-package标签头,流程进入到readDisabledSysPackageLPw,创建PackageSetting,并将对象添加到mDisabledSysPackages中。

1.3.3.4 readPermissions

解析permissions标签头,流程进入到readPermissions,创建BasePermission,并将对象添加到mPermissions中。

2. 解析 package-restrictions.xml

packages.xml文件解析完成后,流程进入到package-restrictions.xml文件解析。android低版本系统中相关信息是存储在packages-stopped.xml,为了系统向下兼容,android高版本系统需要解析packages-stopped.xml,并将解析后的信息记录到package-restrictions.xml文件中存储。

2.1 解析packages-stopped.xml

packages-stopped.xml及其备份文件创建代码如下:

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
Settings(File dataDir, PermissionSettings permission,Object lock) {
   
    ...
	mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
	mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
	...
}

从文件创建代码可知:

  1. packages-stopped.xml、packages-stopped-backup.xml存储在/data/system目录中;
  2. 文件不根据用户做区分,即不支持多用户。

packages-stopped.xml及其备份文件的解析比较简单,不进行深入分析。

2.2 解析package-restrictions.xml

如果packages-stopped.xml及packages-stopped-backup.xml文件不存在,流程进入到package-restrictions.xml及其备份文件的解析。

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
private File getUserPackagesStateFile(int userId) {
   
     File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
     return new File(userDir, "package-restrictions.xml");
}

android支持多用户,PMS针对单个用户维护单独一个package-restrictions.xml文件。package-restrictions.xml文件部分内容如下。

<package-restrictions>
	...
    <pkg name="com.android.systemui" first-install-time="18fb2a6f020">
        <disabled-components>
            <item name="com.android.systemui.saicmotor.MainActivity" />
        </disabled-components>
    </pkg>
    <pkg name="com.saicmotor.hmi.setupwizard" ceDataInode="524853">
        <disabled-components>
            <item name="com.saicmotor.hmi.setupwizard.activity.WelcomeActivity" />
        </disabled-components>
    </pkg>
    <preferred-activities>
        ...
        <item name="com.android.car.dialer/.ui.TelecomActivity" match="100000" always="true" set="2">
            <set name="com.android.car.dialer/.ui.TelecomActivity" />
            <set name="com.google.android.car.kitchensink/.KitchenSinkActivity" />
            <filter>
                <action name="android.intent.action.DIAL" />
                <cat name="android.intent.category.DEFAULT" />
            </filter>
        </item>
        ...
    </preferred-activities>
    <default-apps />
</package-restrictions>

package-restrictions.xml有pkg、preferred-activities、default-apps等标签,本小节间要介绍pkg解析流程图。针对单个文件的解析入口为readPackageRestrictionsLPr方法,流程图如下:
在这里插入图片描述
针对单个用户部分行为,PMS实例化并记录在PackageUserStateImpl对象中。
方法4:解析禁用组件内容,通过该方法记录在PackageUserStateImpl中。调用pm命令禁止某个组件不可见,package-restrictions.xml体现如下:

<pkg name="com.saicmotor.hmi.setupwizard" ceDataInode="524853">
   <disabled-components>
       <item name="com.saicmotor.hmi.setupwizard.activity.WelcomeActivity" />
   </disabled-components>
</pkg>

方法5与之相反,这里不做赘述。
方法6:解析组件禁用包名调用者,通过该方法记录在PackageUserStateImpl中。使用pm命令禁止某个应用不可用,package-restrictions.xml文件中体现如下:

<pkg name="com.blan.audiotest" enabled="2" enabledCaller="shell:1000" first-install-time="18fae54e63d" />

3. 解析 runtime-permissions.xml

package-restrictions.xml文件解析完成后,流程进入到runtime-permissions.xml文件解析。入口方法为readStateForUserSync,流程图如下
在这里插入图片描述
该流程可以分为如下两个子流程。

3.1 解析runtime-permissions.xml

解析runtime-permissions.xml文件的入口方法为parseXml。runtime-permissions.xml存储在data/misc_de/*/apexdata/com.android.permission目录下,其中*代表用户identifier。runtime-permissions.xml文件部分内容如下:

<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<runtime-permissions version="8" fingerprint="autochips/full_ac8x_saic_zp/ac8x_saic_zp:11/RQ2A.210505.003/74:userdebug/test-keys?pc_version=300000000">
  ...
  <package name="com.saicmotor.hmi.chargesettings" />
  ...
  <package name="com.blan.cameratest">
    <permission name="android.permission.CAMERA" granted="true" flags="301" />
  </package>
  <package name="com.saicmotor.projection" />
  <shared-user name="android.media">
    <permission name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="3030" />
    <permission name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="3030" />
  </shared-user>
  ...
  <shared-user name="com.huawei.hms.kitframework.uid">
    <permission name="android.permission.READ_PHONE_STATE" granted="true" flags="b0" />
  </shared-user>
  <shared-user name="android.uid.se" />
...
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值