Android 10.0 PackageManagerService(一)工作原理及启动流程-[Android取经之路]

摘要:PackageManagerService是Android系统核心服务之一,在Android中的非常重要,主要负责APK、jar包等的管理。

阅读本文大约需要花费50分钟。

文章的内容主要还是从源码进行分析,虽然又臭又长,但是如果想要学习Android系统源码,这是必要走的路,没有捷径。

相对于碎片学习,我更倾向于静下心来花费1个小时认真的学习一段内容。

文章首发微信公众号:IngresGe

专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!

欢迎关注我的公众号!

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路] 系列文章:

《系统启动篇》

  1. Android系统架构
  2. Android是怎么启动的
  3. Android 10.0系统启动之init进程
  4. Android10.0系统启动之Zygote进程
  5. Android 10.0 系统启动之SystemServer进程
  6. Android 10.0 系统服务之ActivityMnagerService
  7. Android10.0系统启动之Launcher(桌面)启动流程
  8. Android10.0应用进程创建过程以及Zygote的fork流程
  9. Android 10.0 PackageManagerService(一)工作原理及启动流程
  10. Android 10.0 PackageManagerService(二)权限扫描
  11. Android 10.0 PackageManagerService(三)APK扫描
  12. Android 10.0 PackageManagerService(四)APK安装流程

《日志系统篇》

  1. Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
  2. Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
  3. Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
  4. Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​

《PackageManagerService系列文章》

  1. Android 10.0 PackageManagerService(一)工作原理及启动流程
  2. Android 10.0 PackageManagerService(二)权限扫描
  3. Android 10.0 PackageManagerService(三)APK扫描
  4. Android 10.0 PackageManagerService(四)APK安装流程

《Binder通信原理》

  1. Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
  2. Android10.0 Binder通信原理(二)-Binder入门篇
  3. Android10.0 Binder通信原理(三)-ServiceManager篇
  4. Android10.0 Binder通信原理(四)-Native-C\C++实例分析
  5. Android10.0 Binder通信原理(五)-Binder驱动分析
  6. Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
  7. Android10.0 Binder通信原理(七)-Framework binder示例
  8. Android10.0 Binder通信原理(八)-Framework层分析
  9. Android10.0 Binder通信原理(九)-AIDL Binder示例
  10. Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
  11. Android10.0 Binder通信原理(十一)-Binder总结

  《HwBinder通信原理》

  1. HwBinder入门篇-Android10.0 HwBinder通信原理(一)
  2.  HIDL详解-Android10.0 HwBinder通信原理(二)
  3. HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
  4. HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
  5. HwServiceManager篇-Android10.0 HwBinder通信原理(五)
  6. Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
  7. Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
  8. JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
  9. JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
  10. HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
  11. HwBinder原理总结-Android10.0 HwBinder通信原理(十一)

《编译原理》

  1. 编译系统入门篇-Android10.0编译系统(一)
  2. 编译环境初始化-Android10.0编译系统(二)
  3. make编译过程-Android10.0编译系统(三)
  4. Image打包流程-Android10.0编译系统(四)
  5. Kati详解-Android10.0编译系统(五)

1.概述

    PackageManagerService是Android系统核心服务之一,在Android中的非常重要,主要负责的功能如下:

  1. 解析 AndroidManifest.xml,主要包括AndroidManifest中节点信息的解析和target-name的分析和提炼

  2. 扫描本地文件,主要针对apk,主要是系统应用、本地安装应用等等。这部分会在下面仔细讲解。

  3. 管理本地apk,主要包括安装、删除等等。

下面称PackageManagerService为PKMS。

2.核心源码

/frameworks/base/core/java/android/app/ApplicationPackageManager.java
/frameworks/base/services/java/com/android/server/SystemServer.java
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
/frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java
/frameworks/base/services/core/java/com/android/server/pm/Installer.java
/frameworks/base/services/core/java/com/android/server/pm/Settings.java
/frameworks/base/services/core/java/com/android/server/pm/permission/BasePermission.java
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
/frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
/frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
/frameworks/base/core/java/android/content/pm/IPackageManager.aidl
/frameworks/base/core/java/android/content/pm/PackageManager.java
/frameworks/base/core/java/com/android/server/SystemConfig.java

3.架构

3.1 PKMS 启动过程

3.2 PKMS 继承关系

3.3 权限管理

3.4 APK扫描

    扫描APK的AndroidManifest.xml中的各个标签信息,

例如"application"、"overlay"、"permission"、"uses-permission"等信息。

再针对各个标签的子标签进程扫描,

例如application会扫描"activity"、"receiver"、"service"、"provider"等信息

后面会详细讲解扫描过程

4.启动过程

4.1 启动过程

PKMS服务由SystemServer进行启动,在SystemServer中startBootstrapServices()启动PKMS服务,再调用startOtherServices()进行dex优化,磁盘管理等功能,并让PKMS进入systemready状态。

启动调用栈如下图所示:

 

4.1.1 [SystemServer.java] startBootstrapServices()

说明:startBootstrapServices()首先启动Installer服务,也就是安装器,随后判断当前的设备是否处于加密状态,如果是则只是解析核心应用,接着调用PackageManagerService的静态方法main来创建pms对象

(1)启动Installer服务

(2)获取设备是否加密(手机设置密码),如果设备加密了,则只解析"core"应用

(3)调用PKMS main方法初始化PackageManagerService,其中调用PackageManagerService()构造函数创建了PKMS对象

(4)如果设备没有加密,操作它。管理A/B OTA dexopting。

源码:


private void startBootstrapServices() {
    ...
    //(1)启动Installer
    //阻塞等待installd完成启动,以便有机会创建具有适当权限的关键目录,如/data/user。
    //我们需要在初始化其他服务之前完成此任务。
    Installer installer = mSystemServiceManager.startService(Installer.class);
    mActivityManagerService.setInstaller(installer);
    ...
    //(2)获取设别是否加密(手机设置密码),如果设备加密了,则只解析"core"应用,mOnlyCore = true,后面会频繁使用该变量进行条件判断
    String cryptState = VoldProperties.decrypt().orElse("");
    if (ENCRYPTING_STATE.equals(cryptState)) {
        Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
        mOnlyCore = true;
    } else if (ENCRYPTED_STATE.equals(cryptState)) {
        Slog.w(TAG, "Device encrypted - only parsing core apps");
        mOnlyCore = true;
    }
    
    //(3)调用main方法初始化PackageManagerService,参考[4.1.3]
    mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

    //PKMS是否是第一次启动
    mFirstBoot = mPackageManagerService.isFirstBoot();
    
    //(4)如果设备没有加密,操作它。管理A/B OTA dexopting。
    if (!mOnlyCore) {
        boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
                false);
        OtaDexoptService.main(mSystemContext, mPackageManagerService);
    }
    ...
}

4.1.2 [SystemServer.java] 

startOtherServices()

说明:

(5)执行 updatePackagesIfNeeded ,完成dex优化;

(6)执行 performFstrimIfNeeded ,完成磁盘维护;

(7)调用systemReady,准备就绪。

源码:


private void startOtherServices() {
    ...
    if (!mOnlyCore) {
        ...
        //(5)如果设备没有加密,执行performDexOptUpgrade,完成dex优化;[参考4.3]
        mPackageManagerService.updatePackagesIfNeeded();
    }
    ...
    //(6) 最终执行performFstrim,完成磁盘维护,[参考4.4]
    mPackageManagerService.performFstrimIfNeeded();
    ...
    //(7)PKMS准备就绪,[参考4.5]
    mPackageManagerService.systemReady();
    ...
}

4.1.3 [PackageManagerService.java] main()

说明:

main函数主要工作:

(1)检查Package编译相关系统属性

(2)调用PackageManagerService构造方法

(3)启用部分应用服务于多用户场景

(4)往ServiceManager中注册”package”和”package_native”。

源码:

public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // (1)检查Package编译相关系统属性
    PackageManagerServiceCompilerMapping.checkProperties();

    //(2)调用PackageManagerService构造方法,参考[4.2]
    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    //(3)启用部分应用服务于多用户场景
    m.enableSystemUserPackages();
    
    //(4)往ServiceManager中注册”package”和”package_native”。
    ServiceManager.addService("package", m);
    final PackageManagerNative pmn = m.new PackageManagerNative();
    ServiceManager.addService("package_native", pmn);
    return m;
}

PKMS初始化时的核心部分为PackageManagerService()构造函数的内容,我们下面就来分析该流程

4.2 PKMS构造函数分析

PKMS的构造函数中由两个重要的锁(mInstallLock、mPackages) 和5个阶段构成,下面会详细的来分析这些内容。

mInstallLock :用来保护所有安装apk的访问权限,此操作通常涉及繁重的磁盘数据读写等操作,并且是单线程操作,故有时候会处理很慢。

此锁不会在已经持有mPackages锁的情况下火的,反之,在已经持有mInstallLock锁的情况下,立即获取mPackages是安全的

mPackages:用来解析内存中所有apk的package信息及相关状态。

5个阶段:

阶段1:BOOT_PROGRESS_PMS_START

阶段2:BOOT_PROGRESS_PMS_SYSTEM_SCAN_START

阶段3:BOOT_PROGRESS_PMS_DATA_SCAN_START

阶段4:BOOT_PROGRESS_PMS_SCAN_END

阶段5:BOOT_PROGRESS_PMS_READY

    PKMS服务也是通过binder进行通信,IPackageManager.aidl由工具转换后自动生成binder的服务端IPackageManager.Stub和客户端IPackageManager.Stub.Proxy,具体关系如图:

Binder服务端:PackageManagerService继承于IPackageManager.Stub;

Binder客户端:ApplicationPackageManager(简称APM)的成员变量mPM继承于IPackageManager.Stub.Proxy; 本身APM是继承于PackageManager对象。

4.2.1 [PackageManagerService.java]

说明:IPackageManager.Stub是IPackageManager.aidl自动生成的,正好也说明了PKMS是service端的,通过binder交互

源码:

public class PackageManagerService extends IPackageManager.Stub
        implements PackageSender {
}

4.2.2 [PackageManagerService.java]   PackageManagerService()

说明:PackageManagerService构造函数

源码:

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
        ...
        //阶段1:BOOT_PROGRESS_PMS_START, 参考[4.2.3]
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
                SystemClock.uptimeMillis());

        //阶段2:BOOT_PROGRESS_PMS_SYSTEM_SCAN_START ,参考[4.2.4]
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
                    startTime);
        ...
        
        //阶段3:BOOT_PROGRESS_PMS_DATA_SCAN_START ,参考[4.2.5]
        if (!mOnlyCore) {
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
        }
        ...
        //阶段4:BOOT_PROGRESS_PMS_SCAN_END ,参考[4.2.6]
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
                    SystemClock.uptimeMillis());
        ...
        //阶段5:BOOT_PROGRESS_PMS_READY,参考[4.2.7]
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
                    SystemClock.uptimeMillis());
}

4.2.3 阶段1:BOOT_PROGRESS_PMS_START

主要工作:

(1)构造 DisplayMetrics ,保存分辨率等相关信息;

(2)创建Installer对象,与installd交互;

(3)创建mPermissionManager对象,进行权限管理;

(4)构造Settings类,保存安装包信息,清除路径不存在的孤立应用,主要涉及/data/system/目录的packages.xml,packages-backup.xml,packages.list,packages-stopped.xml,packages-stopped-backup.xml等文件。

(5)构造PackageDexOptimizer及DexManager类,处理dex优化;

(6)创建SystemConfig实例,获取系统配置信息,配置共享lib库;

(7)创建PackageManager的handler线程,循环处理外部安装相关消息。

源码:

public PackageManagerService(...) {
    LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
                SystemClock.uptimeMillis());
    mContext = context;

    mFactoryTest = factoryTest; // 一般为false,即非工厂生产模式
    mOnlyCore = onlyCore; //标记是否只加载核心服务
    mMetrics = new DisplayMetrics(); // 分辨率配置
    mInstaller = installer; //保存installer对象

    //创建提供服务/数据的子组件。这里的顺序很重要,使用到了两个重要的同步锁:mInstallLock、mPackages
    synchronized (mInstallLock) {
    synchronized (mPackages) {
        // 公开系统组件使用的私有服务
        // 本地服务
        LocalServices.addService(
                PackageManagerInternal.class, new PackageManagerInternalImpl());
        // 多用户管理服务
        sUserManager = new UserManagerService(context, this,
                new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
        mComponentResolver = new ComponentResolver(sUserManager,
                LocalServices.getService(PackageManagerInternal.class),
                mPackages);
        // 权限管理服务
        mPermissionManager = PermissionManagerService.create(context,
                mPackages /*externalLock*/);
        mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
        
        //创建Settings对象
        mSettings = new Settings(Environment.getDataDirectory(),
                mPermissionManager.getPermissionSettings(), mPackages);
    }
    }
    
    // 添加system, phone, log, nfc, bluetooth, shell,se,networkstack 这8种shareUserId到mSettings;
    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);
    mSettings.addSharedUserLPw("android.uid.se", SE_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    ...
    // DexOpt优化
    mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                "*dexopt*");
    mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock);
    // ART虚拟机管理服务
    mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);
    mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

    mViewCompiler = new ViewCompiler(mInstallLock, mInstaller);
    // 权限变化监听器
    mOnPermissionChangeListeners = new OnPermissionChangeListeners(
            FgThread.get().getLooper());
    mProtectedPackages = new ProtectedPackages(mContext);
    mApexManager = new ApexManager(context);
    
    // 获取默认分辨率
    getDefaultDisplayMetrics(context, mMetrics);
    //拿到SystemConfig()的对象,其中会调用SystemConfig的readPermissions()完成权限的读取,参考[5 权限管理]
    SystemConfig systemConfig = SystemConfig.getInstance();
    synchronized (mInstallLock) {
            // writer
            synchronized (mPackages) {
                // 启动"PackageManager"线程,负责apk的安装、卸载
                mHandlerThread = new ServiceThread(TAG,
                        Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
                mHandlerThread.start();
                // 应用handler
                mHandler = new PackageHandler(mHandlerThread.getLooper());
                // 进程记录handler
                mProcessLoggingHandler = new ProcessLoggingHandler();
                // Watchdog监听ServiceThread是否超时:10分钟
                Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
                // Instant应用注册
                mInstantAppRegistry = new InstantAppRegistry(this);
                 // 共享lib库配置
                ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig
                        = systemConfig.getSharedLibraries();
                final int builtInLibCount = libConfig.size();
                for (int i = 0; i < builtInLibCount; i++) {
                    String name = libConfig.keyAt(i);
                    SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
                    addBuiltInSharedLibraryLocked(entry.filename, name);
                }
                ...
                // 读取安装相关SELinux策略
                SELinuxMMAC.readInstallPolicy();

                // 返回栈加载
                FallbackCategoryProvider.loadFallbacks();
                //读取并解析/data/system下的XML文件
                mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

                // 清理代码路径不存在的孤立软件包
                final int packageSettingCount = mSettings.mPackages.size();
                for (int i = packageSettingCount - 1; i >= 0; i--) {
                    PackageSetting ps = mSettings.mPackages.valueAt(i);
                    if (!isExternal(ps) && (ps.codePath == null || !ps.codePath.exists())
                            && mSettings.getDisabledSystemPkgLPr(ps.name) != null) {
                        mSettings.mPackages.removeAt(i);
                        mSettings.enableSystemPackageLPw(ps.name);
                    }
                }

                // 如果不是首次启动,也不是CORE应用,则拷贝预编译的DEX文件
                if (!mOnlyCore && mFirstBoot) {
                    requestCopyPreoptedFiles();
                }
                ...
            } // synchronized (mPackages)
        }
}

readLPw()会扫描下面5个文件

1) "/data/system/packages.xml"

2) "/data/system/packages-backup.xml"

3) "/data/system/packages.list"

4) "/data/system/packages-stopped.xml"

5) "/data/system/packages-stopped-backup.xml"

个文件共分为三组,简单的作用描述如下:

  1. packages.xml:PKMS 扫描完目标文件夹后会创建该文件。当系统进行程序安装、卸载和更新等操作时,均会更新该文件。该文件保存了系统中与 package 相关的一些信息。

  2. packages.list:描述系统中存在的所有非系统自带的 APK 的信息。当这些程序有变动时,PKMS 就会更新该文件。

  3. packages-stopped.xml:从系统自带的设置程序中进入应用程序页面,然后在选择强制停止(ForceStop)某个应用时,系统会将该应用的相关信息记录到此文件中。也就是该文件保存系统中被用户强制停止的 Package 的信息。

这些目录的指向,都在Settings中的构造函数完成, 如下所示,得到目录后调用readLPw()进行扫描

Settings(File dataDir, PermissionSettings permission,
        Object lock) {
    mLock = lock;
    mPermissions = permission;
    mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

    mSystemDir = new File(dataDir, "system");  //mSystemDir指向目录"/data/system"
    mSystemDir.mkdirs();  //创建 "/data/system"
    //设置权限
    FileUtils.setPermissions(mSystemDir.toString(),
            FileUtils.S_IRWXU|FileUtils.S_IRWXG
            |FileUtils.S_IROTH|FileUtils.S_IXOTH,
            -1, -1);

    //(1)指向目录"/data/system/packages.xml"
    mSettingsFilename = new File(mSystemDir, "packages.xml");
    //(2)指向目录"/data/system/packages-backup.xml"
    mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
    //(3)指向目录"/data/system/packages.list"
    mPackageListFilename = new File(mSystemDir, "packages.list");
    FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
    //(4)指向目录"/data/system/packages-stopped.xml"
    mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
    //(5)指向目录"/data/system/packages-stopped-backup.xml"
    mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}

解析上面这个几个xml的内容,建立对应的数据结构

[Settings.java]
boolean readLPw(@NonNull List<UserInfo> users) {
    FileInputStream str = null;
    ...
    if (str == null) {
        str = new FileInputStream(mSettingsFilename);
    }
    //解析"/data/system/packages.xml"
    XmlPullParser parser = Xml.newPullParser();
    parser.setInput(str, StandardCharsets.UTF_8.name());

    int type;
    while ((type = parser.next()) != XmlPullParser.START_TAG
            && type != XmlPullParser.END_DOCUMENT) {
        ;
    }
    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;
        }
        //根据XML的各个节点进行各种操作,例如读取权限、shared-user等
        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);
        }...
    }
    str.close();
    ...
    return true;
}

说明:创建 SharedUserSetting 对象并添加到 Settings 的成员变量 mSharedUsers 中,在 Android 系统中,多个 package 通过设置 sharedUserId 属性可以运行在同一个进程,共享同一个 UID

源码:

mSettings.addSharedUserLPw("android.uid.system", //字符串
                           Process.SYSTEM_UID, //系统进程使用的用户id,值为1000
                           ApplicationInfo.FLAG_SYSTEM, //标志系统 Package
                           ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); //特权系统应用

SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    //mSharedUsers 是一个 HashMap,key 为字符串,值为 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;
    }
    //创建一个新的 SharedUserSetting 对象,并设置的 userId 为 uid
    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    s.userId = uid;
    if (registerExistingAppIdLPw(uid, s, name)) {
        mSharedUsers.put(name, s);//将name与s键值对添加到mSharedUsers中保存
        return s;
    }
    return null;
}

4.2.4 阶段2:BOOT_PROGRESS_PMS_SYSTEM_SCAN_START

主要工作:

(1)从init.rc中获取环境变量BOOTCLASSPATH和SYSTEMSERVERCLASSPATH;

(2)对于旧版本升级的情况,将安装时获取权限变更为运行时申请权限;

(3)扫描system/vendor/product/odm/oem等目录的priv-app、app、overlay包;

(4)清除安装时临时文件以及其他不必要的信息。

源码:

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
    synchronized (mInstallLock) {
        synchronized (mPackages) {
            // 记录扫描开始时间
            long startTime = SystemClock.uptimeMillis();
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
                    startTime);
            
            //获取环境变量,init.rc
            final String bootClassPath = System.getenv("BOOTCLASSPATH");
            final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
            ...
            // 获取system/framework目录
            File frameworkDir = new File(Environment.getRootDirectory(), "framework");
            // 获取内部版本
            final VersionInfo ver = mSettings.getInternalVersion();
            // 判断fingerprint是否有更新
            mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);
            ...
            // 对于Android M之前版本升级上来的情况,需将系统应用程序权限从安装升级到运行时
            mPromoteSystemApps =
                    mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
            // 对于Android N之前版本升级上来的情况,需像首次启动一样处理package
            mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;
            mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
            mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q;
            // 在扫描之前保存预先存在的系统package的名称,不希望自动为新系统应用授予运行时权限
            if (mPromoteSystemApps) {
                Iterator<PackageSetting> pkgSettingIter = mSettings.mPackages.values().iterator();
                while (pkgSettingIter.hasNext()) {
                    PackageSetting ps = pkgSettingIter.next();
                    if (isSystemApp(ps)) {
                        mExistingSystemPackages.add(ps.name);
                    }
                }
            }
            // 准备解析package的缓存
            mCacheDir = preparePackageParserCache();
            // 设置flag,而不在扫描安装时更改文件路径
            int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
            ...
            //扫描以下路径:
            /vendor/overlay、/product/overlay、/product_services/overlay、/odm/overlay、/oem/overlay、/system/framework
            /system/priv-app、/system/app、/vendor/priv-app、/vendor/app、/odm/priv-app、/odm/app、/oem/app、/oem/priv-app、
            /product/priv-app、/product/app、/product_services/priv-app、/product_services/app、/product_services/priv-app
      //参考[6 PMS jar包、apk的安装]
            scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),...);
            scanDirTracedLI(new File(PRODUCT_OVERLAY_DIR),...);
            scanDirTracedLI(new File(PRODUCT_SERVICES_OVERLAY_DIR),...);
            scanDirTracedLI(new File(ODM_OVERLAY_DIR),...);
            scanDirTracedLI(new File(OEM_OVERLAY_DIR),...);
            ...
            final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
            final List<String> stubSystemApps = new ArrayList<>();
            // 删掉不存在的package
            if (!mOnlyCore) {
                final Iterator<PackageParser.Package> pkgIterator = mPackages.values().iterator();
                while (pkgIterator.hasNext()) {
                    final PackageParser.Package pkg = pkgIterator.next();
                    if (pkg.isStub) {
                        stubSystemApps.add(pkg.packageName);
                    }
                }
                final Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
                while (psit.hasNext()) {
                    PackageSetting ps = psit.next();
                    // 如果不是系统应用,则不被允许disable
                    if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                        continue;
                    }
                    
                    // 如果应用被扫描,则不允许被擦除
                    final PackageParser.Package scannedPkg = mPackages.get(ps.name);
                    if (scannedPkg != null) {
                        // 如果系统应用被扫描且存在disable应用列表中,则只能通过OTA升级添加
                        if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
                            ...
                            removePackageLI(scannedPkg, true);
                            mExpectingBetter.put(ps.name, ps.codePath);
                        }
                        continue;
                    }
                    ...
                }
            }
            // 删除临时文件
            deleteTempPackageFiles();
            // 删除没有关联应用的共享UID标识
            mSettings.pruneSharedUsersLPw();
            ...
        }
        ...
    }
    ...
}

4.2.5 阶段3:BOOT_PROGRESS_PMS_DATA_SCAN_START

主要工作有:对于不仅仅解析核心应用的情况下,还处理data目录的应用信息,及时更新,祛除不必要的数据。

源码:

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
    synchronized (mInstallLock) {
        synchronized (mPackages) {
            ...
            if (!mOnlyCore) {
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
                scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
                ...
                // 移除通过OTA删除的更新系统应用程序的禁用package设置
                // 如果更新不再存在,则完全删除该应用。否则,撤消其系统权限
                for (int i = possiblyDeletedUpdatedSystemApps.size() - 1; i >= 0; --i) {
                    final String packageName = possiblyDeletedUpdatedSystemApps.get(i);
                    final PackageParser.Package pkg = mPackages.get(packageName);
                    final String msg;

                    mSettings.removeDisabledSystemPackageLPw(packageName);
                    ...
                }
                // 确保期望在userdata分区上显示的所有系统应用程序实际显示
                // 如果从未出现过,需要回滚以恢复系统版本
                for (int i = 0; i < mExpectingBetter.size(); i++) {
                    final String packageName = mExpectingBetter.keyAt(i);
                    if (!mPackages.containsKey(packageName)) {
                        final File scanFile = mExpectingBetter.valueAt(i);
                        ...
                        mSettings.enableSystemPackageLPw(packageName);
                        try {
              //扫描APK
                            scanPackageTracedLI(scanFile, reparseFlags, rescanFlags, 0, null);
                        } catch (PackageManagerException e) {
                            Slog.e(TAG, "Failed to parse original system package: "
                                    + e.getMessage());
                        }
                    }
                }
                // 解压缩并安装任何存根系统应用程序。必须最后执行此操作以确保替换或禁用所有存根
                installSystemStubPackages(stubSystemApps, scanFlags);
                ...
                // 获取storage manager包名
                mStorageManagerPackage = getStorageManagerPackageName();
                // 解决受保护的action过滤器。只允许setup wizard(开机向导)为这些action设置高优先级过滤器
                mSetupWizardPackage = getSetupWizardPackageName();
                ...
                // 更新客户端以确保持有正确的共享库路径
                updateAllSharedLibrariesLocked(null, Collections.unmodifiableMap(mPackages));
                ...
                // 读取并更新要保留的package的上次使用时间
                mPackageUsage.read(mPackages);
                mCompilerStats.read();
            }
        }
    }
}

4.2.6 阶段4:BOOT_PROGRESS_PMS_SCAN_END

主要工作:

(1)sdk版本变更,更新权限;

(2)OTA升级后首次启动,清除不必要的缓存数据;

(3)权限等默认项更新完后,清理相关数据;

(4)更新package.xml

源码:


public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
    synchronized (mInstallLock) {
        synchronized (mPackages) {
            ...
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
                    SystemClock.uptimeMillis());
            // 如果自上次启动以来,平台SDK已改变,则需要重新授予应用程序权限以捕获出现的任何新权限
            final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
            mPermissionManager.updateAllPermissions(
                    StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(),
                    mPermissionCallback);
            ...
            // 如果这是第一次启动或来自Android M之前的版本的升级,并且它是正常启动,那需要在所有已定义的用户中初始化默认的首选应用程序
            if (!onlyCore && (mPromoteSystemApps || mFirstBoot)) {
                for (UserInfo user : sUserManager.getUsers(true)) {
                    mSettings.applyDefaultPreferredAppsLPw(user.id);
                    primeDomainVerificationsLPw(user.id);
                }
            }
            // 在启动期间确实为系统用户准备存储,因为像SettingsProvider和SystemUI这样的核心系统应用程序无法等待用户启动
            final int storageFlags;
            if (StorageManager.isFileEncryptedNativeOrEmulated()) {
                storageFlags = StorageManager.FLAG_STORAGE_DE;
            } else {
                storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
            }
            ...
            // 如果是在OTA之后首次启动,并且正常启动,那需要清除代码缓存目录,但不清除应用程序配置文件
            if (mIsUpgrade && !onlyCore) {
                Slog.i(TAG, "Build fingerprint changed; clearing code caches");
                for (int i = 0; i < mSettings.mPackages.size(); i++) {
                    final PackageSetting ps = mSettings.mPackages.valueAt(i);
                    if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.volumeUuid)) {
                        // No apps are running this early, so no need to freeze
                        clearAppDataLIF(ps.pkg, UserHandle.USER_ALL,
                                FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                                        | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
                    }
                }
                ver.fingerprint = Build.FINGERPRINT;
            }
            
            //安装Android-Q前的非系统应用程序在Launcher中隐藏他们的图标
            if (!onlyCore && mIsPreQUpgrade) {
                Slog.i(TAG, "Whitelisting all existing apps to hide their icons");
                int size = mSettings.mPackages.size();
                for (int i = 0; i < size; i++) {
                    final PackageSetting ps = mSettings.mPackages.valueAt(i);
                    if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                        continue;
                    }
                    ps.disableComponentLPw(PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME,
                            UserHandle.USER_SYSTEM);
                }
            }

            // 仅在权限或其它默认配置更新后清除
            mExistingSystemPackages.clear();
            mPromoteSystemApps = false;
            ...
             // 所有变更均在扫描过程中完成
            ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION;
            //降级去读取
            mSettings.writeLPr();
        }
    }
}

4.2.7 阶段5:BOOT_PROGRESS_PMS_READY

主要工作有:

(1)创建PackageInstallerService对象

(2)GC回收内存

源码:


public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
    synchronized (mInstallLock) {
        synchronized (mPackages) {
            ...
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
                    SystemClock.uptimeMillis());
            ...
            //PermissionController 主持 缺陷许可证的授予和角色管理,所以这是核心系统的一个关键部分。
            mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr();
            ...
            updateInstantAppInstallerLocked(null);
            // 阅读并更新dex文件的用法
            // 在PM init结束时执行此操作,以便所有程序包都已协调其数据目录
            // 此时知道了包的代码路径,因此可以验证磁盘文件并构建内部缓存
            // 使用文件预计很小,因此与其他活动(例如包扫描)相比,加载和验证它应该花费相当小的时间
            final Map<Integer, List<PackageInfo>> userPackages = new HashMap<>();
            for (int userId : userIds) {
                userPackages.put(userId, getInstalledPackages(/*flags*/ 0, userId).getList());
            }
            mDexManager.load(userPackages);
            if (mIsUpgrade) {
                MetricsLogger.histogram(null, "ota_package_manager_init_time",
                        (int) (SystemClock.uptimeMillis() - startTime));
            }
        }
    }
    ...
    // 打开应用之后,及时回收处理
    Runtime.getRuntime().gc();
    // 上面的初始扫描在持有mPackage锁的同时对installd进行了多次调用
    mInstaller.setWarnIfHeld(mPackages);
    ...
}

4.3 dex 优化

    检查是否需要去更新Packages并进行dex优化,如果没有OTA升级、没有大版本升级、没有清楚过dalvik虚拟机的缓存,可以去更新packages,

最终调用的是Installer的dexopt()进行优化

调用栈如下:

4.3.1 [PackageManagerService.java] updatePackagesIfNeeded()

说明:

    检查是否需要去更新Packages并进行dex优化,如果没有OTA升级、没有大版本升级、没有清楚过dalvik虚拟机的缓存,可以去更新packages

更新packages的优先级:core app >system app > other app,调用 performDexOptUpgrade()进行更新

源码:

public void updatePackagesIfNeeded() {
     //只有system或是root用户才有权限请求 package update
    enforceSystemOrRoot("Only the system can request package update");

    //(1)判断有没有进行OTA升级,我们需要在OTA后重新提取。
    boolean causeUpgrade = isDeviceUpgrading();

    //(2)是否是第一次启动或是系统大版本升级
    boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;

    // We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
    //(3)判断是否有清除过dalvik虚拟机的缓存
    boolean causePrunedCache = VMRuntime.didPruneDalvikCache();

    //(4)如果上面的三个都没有,那么就不进行任何操作
    if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
        return;
    }

    List<PackageParser.Package> pkgs;
    synchronized (mPackages) {
        //(5)按照package的优先级进行排序,core app >system app > other app
        pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this);
    }

    final long startTime = System.nanoTime(); //记录开始时间
    
    //进行dex优化,参考[4.3.2]
    final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
                causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
                false /* bootComplete */);
    ...
}

4.3.2  [PackageManagerService.java] performDexOptUpgrade()

说明:判断是否需要对package进行更新,如果需要更新那么按照优先级完成dex优化,最终调用Install的dexopt()进行dex优化,参考上面的调用栈

源码:


private int[] performDexOptUpgrade(List<PackageParser.Package> pkgs, boolean showDialog,
        final int compilationReason, boolean bootComplete) {
    ...
    //循环取出packages,进行优化
    for (PackageParser.Package pkg : pkgs) {
        numberOfPackagesVisited++;

        boolean useProfileForDexopt = false;

        //第一次启动或是ota升级之后并且是系统应用才会进行odex
        if ((isFirstBoot() || isDeviceUpgrading()) && isSystemApp(pkg)) {
            ...
        }
        ...
        //最终是调用 mInstaller.dexopt 完成优化的,installd守护进程,installer安装器和Installd通信
        int primaryDexOptStaus = performDexOptTraced(new DexoptOptions(
                pkg.packageName,
                pkgCompilationReason,
                dexoptFlags));

        switch (primaryDexOptStaus) {
            case PackageDexOptimizer.DEX_OPT_PERFORMED:
                numberOfPackagesOptimized++; //odex优化完成的应用
                break;
            case PackageDexOptimizer.DEX_OPT_SKIPPED:
                numberOfPackagesSkipped++; //跳过的应用
                break;
            case PackageDexOptimizer.DEX_OPT_FAILED:
                numberOfPackagesFailed++; //失败的应用
                break;
            default:
                Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStaus);
                break;
        }
    }
    return new int[] { numberOfPackagesOptimized, numberOfPackagesSkipped,
            numberOfPackagesFailed };
}

4.4 磁盘维护

磁盘维护最终调用的是vold进程的 fstrim()进行清理操作

调用栈:

[PackageManagerService.java] performFstrimIfNeeded()

说明:主要是执行磁盘清理工作,释放磁盘空间

源码:

public void performFstrimIfNeeded() {
   //只有system或是root用户才有权限请求fstrim
   enforceSystemOrRoot("Only the system can request fstrim");

   //在其他事情之前,看看我们是否需要fstrim
   try {
        //获取StorageManager对象
       IStorageManager sm = PackageHelper.getStorageManager();
       if (sm != null) {
           boolean doTrim = false;
           //获取执行FTRIM间隔,默认是3天,可以通过setting provider更改这个时间
           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() - sm.lastMaintenance();
               if (timeSinceLast > interval) {
                   doTrim = true; //如果超过了三天就进行磁盘清理
                   Slog.w(TAG, "No disk maintenance in " + timeSinceLast
                           + "; running immediately");
               }
           }
           if (doTrim) {
               final boolean dexOptDialogShown;
               synchronized (mPackages) {
                   dexOptDialogShown = mDexOptDialogShown;
               }
               //如果不是第一次启动,显示一个提示框
               if (!isFirstBoot() && dexOptDialogShown) {
                   try {
                       ActivityManager.getService().showBootMessage(
                               mContext.getResources().getString(
                                       R.string.android_upgrading_fstrim), true);
                   } catch (RemoteException e) {
                   }
               }
               // 这里的sm是 StorageManagerService,发送消息H_FSTRIM给handler,然后再向vold发送fstrim命
               sm.runMaintenance();
           }
       } else {
           Slog.e(TAG, "storageManager service unavailable!");
       }
   } catch (RemoteException e) {
       // Can't happen; StorageManagerService is local
   }
}

4.5 PKMS 准备就绪

[PackageManagerService.java] systemReady()

说明:systemReady主要完成的是默认授权和更新package的信息,通知在等待pms的一些组件

源码:


public void systemReady() {
    //只有system或是root用户才有权限 声称system已经ready
    enforceSystemOrRoot("Only the system can claim the system is ready");

    ContentObserver co = new ContentObserver(mHandler) {
        ...
    };
    co.onChange(true); //注册一个监听
    ...
    synchronized (mPackages) {

        ArrayList<PreferredActivity> removed = new ArrayList<>();
        for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
            PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
            removed.clear();
            for (PreferredActivity pa : pir.filterSet()) {
                if (!mComponentResolver.isActivityDefined(pa.mPref.mComponent)) {
                    removed.add(pa);
                }
            }//移除不存在的先前用户设置保存的优先选择的activity组件
            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(); //多用户服务 systemReady
    //升级所有已获取的默认权限
    for (int userId : grantPermissionsUserIds) {
        mDefaultPermissionPolicy.grantDefaultPermissions(userId); //默认授权操作,参考[]
    }
    ...

    // Now that we've scanned all packages, and granted any default
    // permissions, ensure permissions are updated. Beware of dragons if you
    // try optimizing this.
    synchronized (mPackages) {
        mPermissionManager.updateAllPermissions(
                StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
                mPermissionCallback); //更新权限信息
        ...
        });
    }

    //注意随时间变化的外部存储设备
    final StorageManager storage = mContext.getSystemService(StorageManager.class);
    storage.registerListener(mStorageListener);

    mInstallerService.systemReady();
    mApexManager.systemReady();
    mPackageDexOptimizer.systemReady();
    ...

    //清除过期的userid和app
    sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
    reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);

    mPermissionManager.systemReady();

    if (mInstantAppResolverConnection != null) {
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                mInstantAppResolverConnection.optimisticBind();
                mContext.unregisterReceiver(this);
            }
        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
    }

    if (!isFirstBoot()) {
        Slog.i(TAG, "enablePackageStoppedPolicy");
    }

    mModuleInfoProvider.systemReady();

    // Installer service might attempt to install some packages that have been staged for
    // installation on reboot. Make sure this is the last component to be call since the
    // installation might require other components to be ready.
    mInstallerService.restoreAndApplyStagedSessionIfNeeded();

    //update partner app
    new Thread(() -> {
        updatePartnerAppInFota();
    }).start();
}

下一节将会讲解PKMS 的权限扫描、APK扫描、安装等内容,欢迎关注我

微信公众号:IngresGe

  • 46
    点赞
  • 128
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值