Android package管理之包解析详解

时间:2025-01-27 12:31:13

Android 中的包指的是Apk、jar和so文件等等,它们被加载到内存中,由一个包转变成可执行的代码,这就需要一个机制来进行包的加载、解析、管理等操作。管理这些包,是PackageManagerService 的核心,他负责对系统中所有的包进行管理,并与其他服务进行通信。
Android APK的安装过程,实际上就是对包进行解析,并将解析的包信息保存到settings和(内存)PMS 中各个组件列表的过程。
PMS 在启动过程中会对系统中的应用程序包进行解析并加载到内存,所以我们首先关注一下PMS的启动流程

PackageManagerService启动流程

启动PMS:
startBootstrapServices():

private void startBootstrapServices() {
        mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
}

startOtherServices()

private void startOtherServices() {
    if (!mOnlyCore) {
        try {
            //将调用performDexOpt:Performs dexopt on the set of packages
            mPackageManagerService.updatePackagesIfNeeded();
        }
        try {
            //执行Fstrim,执行磁盘维护操作,未看到详细的资料
            //可能类似于TRIM技术,将标记为删除的文件,彻底从硬盘上移除
            //而不是等到写入时再移除,目的是提高写入时效率
            mPackageManagerService.performFstrimIfNeeded();
        }
         mPackageManagerService.systemReady();
    }
}

整个system_server进程启动过程,涉及PMS服务的主要几个动作如下:

  • ()
  • ()
  • ()


里面主要是构造PMS,我们直接看PMS的构造函数,里面做了大量的工作,我们化繁为简,重点关注几个点即可:

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
            ...
	//Settings是Android的全局管理者,用于协助PMS保存所有的安装包信息
 	mSettings = new Settings(mPackages);
 	mSettings.addSharedUserLPw("", Process.SYSTEM_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
	...
	mInstaller = installer;
	//用于dex优化
	mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
        "*dexopt*")
	 // 创建各种目录
    File dataDir = Environment.getDataDirectory();
    mAppInstallDir = new File(dataDir, "app");
    mAppLib32InstallDir = new File(dataDir, "app-lib");
  	...
  	// 解析和"
    mRestoredSettings = mSettings.readLPw(sUserManager.getUsers(false));    
	//dex 优化
	mInstaller.dexopt(path, Process.SYSTEM_UID, dexCodeInstructionSet,
                        dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/);
	//收集和解析系统APKS                        
	scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
        
   //信息写回文件
	mSettings.writeLPr();
}

PMS Settings 和scanDirLI 扫描系统apks 是本文的研究重点:


Package Settings

Settings是Android的全局管理者,用于协助PMS保存所有的安装包信息
frameworks/base/services/core/java/com/android/server/pm/
先看看起构造函数

Settings(File dataDir, PermissionSettings permission, Object lock) {
        mLock = lock;
        mPermissions = permission;
        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, "");
        mBackupSettingsFilename = new File(mSystemDir, "");
        mPackageListFilename = new File(mSystemDir, "");
        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, "");
        mBackupStoppedPackagesFilename = new File(mSystemDir, "");
    }


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

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version ... />
    <version ... />
    <permissions>
        <item name="xxxS" package="xxx" protection="xx" />
        ... ...
    </permissions>
    <package xxx>
        ...
    </package>
    ...
    <shared-user xxx>
        ...
    </shared-user>
    ...
    <keyset-settings version="1">
        ...
    </keyset-settings>
</packages>

文件中主要的信息分为下面几个部分:
permission: 里面包含了系统中所有定义的权限的信息
package:里面包含了系统中所有安装的app的详细信息
shared-user:里面包含了所有系统定义的shareuser的信息
keyset-settings:里面包含了已安装app签名的public key信息


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


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


readLPw()函数,从/data/system/或文件中获得packages、permissions相关信息,添加到相关内存列表中。文件记录了系统的permisssions以及每个APK的name、codePath、flags、version等信息这些信息主要通过APK的解析获取,解析完APK后将更新信息写入这个文件,下次开机直接从里面读取相关信息添加到内存相关结构中。当有APK升级、安装或删除时会更新这个文件。


writeLPr函数,将解析出的每个APK的信息()保存到和文件。记录了如下数据:pkgName, userId, debugFlag, dataPath(包的数据路径)。

Package scanDirLI
private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
    final File[] files = dir.listFiles();
    .......
    for (File file : files) {
        final boolean isPackage = (isApkFile(file) || file.isDirectory())
                && !PackageInstallerService.isStageName(file.getName());
        if (!isPackage) {
            // Ignore entries which are not packages
            continue;
        }
        try {
            //处理目录下每一个package文件
            scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                    scanFlags, currentTime, null);
        } catch (PackageManagerException e) {
            .........
        }
    }
}

scanPackageTracedLI函数最终会调用到scanPackageLI函数:

rivate PackageParser.Package scanPackageLI(FilescanFile, int parseFlags,
                                                int scanMode, long currentTime) {
    mLastScanError = PackageManager.INSTALL_SUCCEEDED;
    StringscanPath = scanFile.getPath();
    parseFlags |= mDefParseFlags;//默认的扫描标志,正常情况下为0

    //创建一个 PackageParser 对象
    PackageParser pp = new PackageParser(scanPath);
    pp.setSeparateProcesses(mSeparateProcesses);// mSeparateProcesses 为空
    pp.setOnlyCoreApps(mOnlyCore);// mOnlyCore 为 false

    /*
           调用 PackageParser 的 parsePackage 函数解析APK文件。注意,这里把代表屏幕
           信息的 mMetrics 对象也传了进去
        */
    finalPackageParser.Package pkg = pp.parsePackage(scanFile,
                                                     scanPath, mMetrics, parseFlags);
    //...
    PackageSetting ps = null;
    PackageSetting updatedPkg;
    //...

    /*
            这里略去一大段代码,主要是关于 Package 升级方面的工作。
        */
    //收集签名信息,这部分内容涉及 signature。
    if (!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags))
        return null;

    //判断是否需要设置 PARSE_FORWARD_LOCK 标志,这个标志针对资源文件和 Class 文件
    //不在同一个目录的情况。目前只有 /vendor/app 目录下的扫描会使用该标志。这里不讨论
    //这种情况。
    if (ps != null && !ps.codePath.equals(ps.resourcePath))
        parseFlags |= PackageParser.PARSE_FORWARD_LOCK;

    String codePath = null;
    String resPath = null;
    if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) {
        //...//这里不考虑 PARSE_FORWARD_LOCK的情况。
    } else {
        resPath = pkg.mScanPath;
    }

    codePath = pkg.mScanPath;//mScanPath 指向该 APK 文件所在位置
    //设置文件路径信息,codePath 和 resPath 都指向 APK 文件所在位置
    setApplicationInfoPaths(pkg, codePath, resPath);

    //调用第二个 scanPackageLI 函数
    return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE,
                         currentTime);
}

在函数最后会调用另一个同名的scanPackageLI,我们后面再分析,先看看PackageParser的内容

PackageParser 主要负责 APK 文件的解析,即解析 APK 文件中的 代码如下:

public Package parsePackage(File sourceFile, String destCodePath,
                                DisplayMetrics metrics, int flags) {
    mParseError = PackageManager.INSTALL_SUCCEEDED;
    mArchiveSourcePath = sourceFile.getPath();
    //...//检查是否为 APK 文件
    XmlResourceParser parser = null;
    AssetManager assmgr = null;
    Resources res = null;
    boolean assetError = true;
    try {
        assmgr = new AssetManager();
        int cookie = assmgr.addAssetPath(mArchiveSourcePath);
        if (cookie != 0) {
            res = new Resources(assmgr, metrics, null);
            assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                    0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
            /*
              获得一个 XML 资源解析对象,该对象解析的是 APK 中的  文件。
              以后再讨论 AssetManager、Resource 及相关的知识
             */
            parser = assmgr.openXmlResourceParser(cookie,
                                                  ANDROID_MANIFEST_FILENAME);
            assetError = false;
        } //...//出错处理
        String[] errorText = new String[1];
        Package pkg = null;
        Exception errorException = null;
        try {
            //调用另外一个 parsePackage 函数
            pkg = parsePackage(res, parser, flags, errorText);
        }//...

        //...//错误处理
        parser.close();
        assmgr.close();
        //保存文件路径,都指向 APK 文件所在的路径
        pkg.mPath = destCodePath;
        pkg.mScanPath = mArchiveSourcePath;
        pkg.mSignatures = null;
        return pkg;
    }
}

实际上调用了另一个同名的 parsePackage 函数

private Package parsePackage(Resources res, XmlResourceParser parser, int flags, String[] outError)
        throws XmlPullParserException, IOException {
    AttributeSet attrs = parser;
    mParseInstrumentationArgs = null;
    mParseActivityArgs = null;
    mParseServiceArgs = null;
    mParseProviderArgs = null;
    //得到 Package 的名字,其实就是得到  中 package 属性的值,
    //每个 APK 都必须定义该属性
    String pkgName = parsePackageName(parser, attrs, flags, outError);
    //...
    int type;
    //...
    //以 pkgName 名字为参数,创建一个 Package 对象。后面的工作就是解析 XML 并填充
    //该 Package 信息
    final Package pkg = new Package(pkgName);
    boolean foundApp = false;
    //...//下面开始解析该文件中的标签,由于这段代码功能简单,所以这里仅列举相关函数
    while (如果解析未完成) {
        //...
        StringtagName = parser.getName(); //得到标签名
        if (tagName.equals("application")) {
            //...//解析 application 标签
            parseApplication(pkg, res, parser, attrs, flags, outError);
        } else if (tagName.equals("permission-group")) {
            //...//解析 permission-group 标签
            parsePermissionGroup(pkg, res, parser, attrs, outError);
        } else if (tagName.equals("permission")) {
            //...//解析 permission 标签
            parsePermission(pkg, res, parser, attrs, outError);
        } else if (tagName.equals("uses-permission")) {
            //从 XML 文件中获取 uses-permission 标签的属性
            sa = res.obtainAttributes(attrs,
                    com.android.internal.R.styleable.AndroidManifestUsesPermission);
            //取出属性值,也就是对应的权限使用声明
            String name = sa.getNonResourceString(com.android.internal.
                    R.styleable.AndroidManifestUsesPermission_name);
            //添加到 Package 的 requestedPermissions 数组
            if (name != null && !pkg.requestedPermissions.contains(name)) {
                pkg.requestedPermissions.add(name.intern());
            }
        } else if (tagName.equals("uses-configuration")) {
            /*
                该标签用于指明本 package 对硬件的一些设置参数,目前主要针对输入设备(触摸屏、键盘
                等)。游戏类的应用可能对此有特殊要求。
            */
            ConfigurationInfocPref = new ConfigurationInfo();
            //...//解析该标签所支持的各种属性
            pkg.configPreferences.add(cPref);//保存到 Package 的 configPreferences 数组
        }
        //...//对其他标签解析和处理
    }
}

在 PackageParser 扫描完一个 APK 后,此时系统已经根据该 APK 中 ,创建了一个完整的 Package 对象,下一步就是将该 Package 加入到系统中。此时调用的函数就是另外一个 scanPackageLI

private PackageParser.Package scanPackageLI(
    PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime) {
    FilescanFile = new File(pkg.mScanPath);
    //...
    mScanningPath = scanFile;
    //设置 package 对象中 applicationInfo 的 flags 标签,用于标示该 Package 为系统
    //Package
    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {
        pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
    }

    //下面这句 if 判断极为重要,见下面的解释
    if (pkg.packageName.equals("android")) {
        synchronized (mPackages) {
            if (mAndroidApplication != null) {
                //...

                mPlatformPackage = pkg;
                pkg.mVersionCode = mSdkVersion;
                mAndroidApplication = pkg.applicationInfo;
                mResolveActivity.applicationInfo = mAndroidApplication;
                mResolveActivity.name = ResolverActivity.class.getName();
                mResolveActivity.packageName = mAndroidApplication.packageName;
                mResolveActivity.processName = mAndroidApplication.processName;
                mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
                mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
                mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert;
                mResolveActivity.exported = true;
                mResolveActivity.enabled = true;
                //mResoveInfo 的 activityInfo 成员指向 mResolveActivity
                mResolveInfo.activityInfo = mResolveActivity;
                mResolveInfo.priority = 0;
                mResolveInfo.preferredOrder = 0;
                mResolveInfo.match = 0;
                mResolveComponentName = new ComponentName(
                        mAndroidApplication.packageName, mResolveActivity.name);
            }
        }
    }
}