android12 开机启动PMS

1. 起点:SystemServer

PMS(PackageManagerService) 的启动始于 SystemServerstartBootstrapServices() 方法。这个方法用于启动一系列关键的基础服务。

csharp 复制代码
// 文件:frameworks/base/services/java/com/android/server/SystemServer.java
private void startBootstrapServices() {
    // 1. 启动 Installer 服务
    Installer installer =  mSystemServiceManager.startService(Installer.class);
    
    // 2. 检查设备加密状态,决定是否只加载核心应用
   // Only run "core" apps if we're encrypting the device.
   String cryptState = VoldProperties.decrypt().orElse("");
   if (ENCRYPTING_STATE.equals(cryptState)) {
      mOnlyCore = true;
   } else if (ENCRYPTED_STATE.equals(cryptState)) {
      Slog.w(TAG, "Device encrypted - only parsing core apps");
      mOnlyCore = true;
   }

    // 3. 调用 PMS 的 main() 方法,创建 PMS 实例
   
  try {
     Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
     mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
            domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF,
            mOnlyCore);
 } finally {
    Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
 }
    
     // 4. 首次启动标记
     mFirstBoot = mPackageManagerService.isFirstBoot();
    // ...
}

这段代码清晰地展示了PMS启动前的三个关键动作:

  • 启动 Installer 服务Installer 是 PMS 与底层 installd 守护进程通信的桥梁,负责执行创建应用目录、文件拷贝等实际操作。PMS 依赖于它,因此必须率先启动。
  • 检查加密状态 :通过读取 vold.decrypt 系统属性来判断设备是否处于加密或正在加密的状态。如果是,则设置 mOnlyCore = true,这意味着 PMS 只会解析系统核心应用,以在解密完成前快速启动系统。
  • 调用 main() 方法 :这是启动 PMS 的核心步骤,传入了 ContextInstaller 实例、工厂测试模式和加密状态标志。

2. 入口与注册:PMS.main() 方法

PackageManagerService.main() 方法是 PMS 的静态入口,它的逻辑相对直接。

java

java 复制代码
// 文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // 1. 调用 PMS 的构造函数,完成核心初始化
    
PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest,
        Build.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG, Build.VERSION.SDK_INT,
        Build.VERSION.INCREMENTAL);

    
    // 2. 向 ServiceManager 注册 Binder 服务 "package"
    ServiceManager.addService("package", m);
    
    // 3. 注册另一个服务 "package_native"
    final PackageManagerNative pmn = m.new PackageManagerNative();
    ServiceManager.addService("package_native", pmn);
    
    return m;
}

main 方法承担了两个核心职责:

  • 创建 PMS 实例 :通过 new PackageManagerService(...) 调用其构造函数。所有核心的、复杂的初始化逻辑都在构造函数中完成。
  • 注册 Binder 服务 :将创建好的 PMS 实例注册到 ServiceManager 中。注册后,其他系统服务和应用程序就能通过 "package" 这个字符串获取到它的 Binder 代理,从而进行跨进程通信(IPC)。此外,还会注册一个 "package_native" 服务,用于提供原生接口。

3. 核心:PMS 构造函数中的复杂初始化

PMS 的构造函数是其启动流程中最复杂、最耗时的部分。它主要按顺序完成以下工作。

3.1. 第一阶段:准备工作与加载持久化信息

构造函数的开头会进行一系列初始化,并加载之前持久化的包信息。

java

ini 复制代码
// 文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // ... 初始化成员变量 ...
    
    // 1. 创建 Settings 对象,它管理 packages.xml 等文件
    
    mSettings = injector.getSettings();

    // 添加系统共享用户 ID
    mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
            ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PRIVILEGED);
     ...
    
    // 2. 从磁盘读取持久化的包信息
    
mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(
        /* excludePartial= */ true,
        /* excludeDying= */ false,
        /* excludePreCreated= */ false));
t.traceEnd();

mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions);

if (!mOnlyCore && mFirstBoot) {
    requestCopyPreoptedFiles(mInjector);
}

    // ...
}
  • 初始化 SettingsSettings 类虽然名字和系统设置无关,但它负责管理所有已安装应用的核心信息,并与 /data/system/packages.xml 等关键文件交互。
  • 加载持久化数据mSettings.readLPw() 方法会解析 packages.xml 等文件,将之前系统已知的应用信息(如包名、版本、UID、权限等)加载到内存中。这为后续的扫描工作提供了基础。

3.2. 第二阶段:扫描 APK 文件

这是PMS启动过程中最核心、最耗时的阶段,即扫描文件系统中的APK文件,解析其内容。

java

scss 复制代码
// 文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // ...
    // 1. 扫描系统目录system/framework,system/app,system/priv-app
    for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
       final ScanPartition partition = mDirsToScanAsSystem.get(i);
       if (partition.getOverlayFolder() == null) {
           continue;
        }
       scanDirTracedLI(partition.getOverlayFolder(), systemParseFlags,
            systemScanFlags | partition.scanFlag, 0,
            packageParser, executorService);
     }

     scanDirTracedLI(frameworkDir, systemParseFlags,
        systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
        packageParser, executorService);
       ...

    for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
       final ScanPartition partition = mDirsToScanAsSystem.get(i);
       if (partition.getPrivAppFolder() != null) {
           scanDirTracedLI(partition.getPrivAppFolder(), systemParseFlags,
                systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
                packageParser, executorService);
        }
          scanDirTracedLI(partition.getAppFolder(), systemParseFlags,
            systemScanFlags | partition.scanFlag, 0,
            packageParser, executorService);
     }

    // ... 扫描其他系统分区目录(如 /vendor, /product 等)...
    
    // 2. 如果 mOnlyCore == false,扫描用户数据目录
    if (!onlyCore) {
        scanDirTracedLI(new File(Environment.getDataDirectory(), "app"), ...);
        // ... 扫描其他用户数据目录 ...
    }
    // ...
}



// 文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void scanDirTracedLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    // 1. 开始追踪,标签带有目录名,如 "scanDirTracedLI_app"
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDirTracedLI_" + dir.getName());
    try {
        // 2. 核心工作委托给 scanDirLI 执行
        scanDirLI(dir, parseFlags, scanFlags, currentTime);
    } finally {
        // 3. 确保追踪结束
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}
  • 扫描系统目录 :PMS 会首先扫描一系列系统分区下的目录,例如 /system/framework(框架资源)、/system/app(系统应用)、/system/priv-app(特权应用)等。
  • 扫描用户目录 :如果设备未加密(mOnlyCore == false),PMS 会继续扫描用户数据目录,主要是 /data/app,这里面存放着用户安装的所有第三方应用。
arduino 复制代码
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    // 1. 获取目录下的所有文件
    final File[] files = dir.listFiles();
    // 2. 创建并发解析器(内部持有线程池)
    try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(...)) {
        // 3. 遍历文件,提交解析任务
        for (File file : files) {
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            if (!isPackage) {
                continue; // 跳过非APK文件
            }
            // 将 APK 解析任务提交到线程池
            parallelPackageParser.submit(file, parseFlags);
        }
        // 4. 准备收集结果(进入下一阶段)
        // ... (见阶段三)
    }
}
  • ParallelPackageParser:它是并发解析引擎,在构造时创建了一个固定大小的线程池(通常为 CPU 核心数)。
  • parallelPackageParser.submit() :内部会将 APK 文件包装成任务,最终调用 PackageParser2.parsePackage() 来解析 AndroidManifest.xml,生成一个临时的 PackageParser.Package 对象。此阶段不阻塞,会快速遍历完所有文件
java 复制代码
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(...)) {
        // ... 提交所有任务 (阶段二) ...

        // 1. 循环获取已完成的解析结果
        for (int i = 0; i < fileCount; i++) {
            // take() 是一个阻塞方法,等待下一个任务完成
            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
            
            // 2. 如果解析失败(如APK损坏),记录错误并跳过
            if (parseResult.pkg == null) {
                // ... 错误处理 ...
                continue;
            }
            
            // 3. 核心:将解析结果交给 scanPackageChildLI 进行后续处理
            scanPackageChildLI(parseResult.pkg, parseResult.scanFile, 
                    parseFlags, scanFlags, currentTime, null);
        }
    }
}
  • parallelPackageParser.take() :这是一个阻塞队列的取出操作。它会等待线程池中任何一个任务完成,然后立即返回结果,而不是等所有任务全部完成。
  • scanPackageChildLI() :这里是解析后处理 的真正入口,它接收的是已经解析好的 PackageParser.Package 对象(包含了四大组件等全部信息)

scanPackageChildLI 负责将解析出的 Package 对象进行系统级验证、资源分配和最终注册。这是 APK 从"文件"变为"系统可识别应用"的关键一步。

java

ruby 复制代码
// 文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private PackageSetting scanPackageChildLI(PackageParser.Package pkg, ...) {
    // 1. 处理 Split APK 逻辑(在 user 版本通常无效)
    // ...
    
    // 2. 核心:调用 addForInitLI,将应用信息纳入 PMS 管理
    addForInitLI(pkg, ...);
    
    // 3. 返回该应用的设置信息
    return pkg.mScanResult;
}

addForInitLI 内部会执行以下最重要的系统操作:

java

csharp 复制代码
// addForInitLI 方法中的核心逻辑(伪代码示意)
private void addForInitLI(PackageParser.Package pkg, ...) {
    // 1. 分配或复用 Linux UID(用户ID)
    final SharedUserSetting sharedUser = mSettings.getSharedUserLPw(...);
    if (sharedUser != null) {
        pkg.mSharedUserId = sharedUser.userId;
    } else {
        // 创建新的 UID
    }
    
    // 2. 将四大组件注册到 PMS 的全局索引中
    // 注册 Activity
    mActivities.add(pkg.activities);
    // 注册 Service
    mServices.add(pkg.services);
    // 注册 Receiver
    mReceivers.add(pkg.receivers);
    // 注册 Provider
    mProviders.add(pkg.providers);
    
    // 3. 将包名与 Package 对象存入全局 mPackages 映射
    mPackages.put(pkg.packageName, pkg);
}

关键点

  • UID 分配:确保每个应用有唯一的 Linux 身份,是应用沙箱隔离的基础。
  • 组件注册 :将四大组件信息分别存入 mActivitiesmServicesmReceiversmProviders 这四个 ArrayList 中。这就是为什么我们调用 queryIntentActivities() 等 API 时,PMS 能快速返回匹配的组件列表。
  • 全局索引mPackages 是一个 ArrayMap<String, PackageParser.Package>,通过包名可以 O(1) 获取应用完整信息。

3.3. 第三阶段:收尾与后续优化

扫描完成后,PMS 会进行一些收尾工作。

java

scss 复制代码
// 文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // ...
    
   // 1. 根据扫描结果更新应用的依赖关系,清理无效数据(如被卸载的应用残留)
    updateAllSharedLibrariesLPw(null);
    pruneSharedLibrariesLPw();
   
    // 2. 将内存中的最新状态写回磁盘(持久化)
    mSettings.writeLPr();

}
  • 更新与清理:根据扫描到的最新APK信息,更新内存中的数据,处理应用依赖,并清理掉不再存在的应用数据。
  • 持久化状态 :最后,mSettings.writeLPr() 方法会将内存中的包信息重新写回到 packages.xml 等文件中,确保下次开机时能快速加载。
相关推荐
Days20507 小时前
GPT-Image-2 国风美学人设生成提示词分享
android·gpt
浮江雾7 小时前
Flutter第十节-----Flutter布局与组件全解析
android·开发语言·前端·学习·flutter·入门
Jomurphys8 小时前
Compose 适配 - 自适应布局(窗口大小类 WindowSizeClasses、列表详情、辅助窗格)
android·compose
plainGeekDev9 小时前
synchronized → Coroutines
android·java·kotlin
sun0077009 小时前
gtest 行覆盖率、函数覆盖率、分支覆盖率 . 一般这3个要达到多少?算达标
android
施棠海9 小时前
设计稿一键变成可运行的Android页面:Pixel2XML全流程UI开发框架实战(附完整源码)
android·ui·架构
Jomurphys9 小时前
Compose 适配 - 分辨率(密度)
android·compose
__Witheart__10 小时前
获取产品对应文件夹
android
__Witheart__10 小时前
版型关联的设备树查询方式
android·rockchip