【Android源码系列】如何解析APK-PackageManagerService

一、惯例BB

新的一年又到了,2018也要加油啊~距离写上一篇文章也有一个月了,今天我们就来看看PackageManagerService(以下简称PMS)。
PMS和AMS、WMS一样,也是一个系统服务,他的主要作用就是解析APK信息,并保存下来。我们平时写在Manifest里的信息是如何被解析的呢?没错都是他干的。还记得我在前面一篇文章《 VirtualAPK插件化方案原理探索》里留下了一个坑:VirtualAPK初始化时有这么一段代码:

//这里解析APK,并保存到LoadedPlugin对象中
        LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);

LoadedPlugin 这个类就是负责解析并保存APK信息的,因为主题原因当时我就一笔带过并没有深入详谈,今天就让他做一回主角。
其实这也无可厚非,插件化的基本思想就是需要利用插件APK来搞事情。所以这里究竟是如何实现的呢,让我们带着疑问往下看。

二、PackageManagerService

当一个Android手机开机时,系统会将所有已安装过的应用安装一遍,然后显示在桌面上。所以,PMS在开机时就会启动,首先我们来看看他的构造方法

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
            //省略巨量代码
            //找到framework所在
            File frameworkDir = new File(Environment.getRootDirectory(), "framework");

            alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");

            alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");

            //省略部分解析代码
            // Find base frameworks (resource packages without code):扫描framework磁盘
            scanDirLI(frameworkDir, 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");
            scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

            // Collect ordinary system packages.扫描系统app磁盘
            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
            scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

            //省略其他扫描及无关代码...

            if (!mOnlyCore) {
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
                //扫描已下载app磁盘
                scanDirLI(mAppInstallDir, 0, scanFlags, 0);

                scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                        scanFlags, 0);

        //.....
        Runtime.getRuntime().gc();
    }

代码注释已经很清楚了,首先扫描framework及核心代码,再扫描已安装应用目录,我们就来看看sacnDirLI这个方法

    private void scanDirLI(File dir, 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 {
                //扫描
                scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                        scanFlags, currentTime, null);
            } catch (PackageManagerException e) {
                Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());

               //...
            }
        }
    }

调用scanPackageLI扫描每个文件,直接看代码

    private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
            long currentTime, UserHandle user) throws PackageManagerException {
        //利用PackageParser解析APK并返回Package对象
        final PackageParser.Package pkg;
        try {
            pkg = pp.parsePackage(scanFile, parseFlags);
        } catch (PackageParserException e) {
            throw PackageManagerException.from(e);
        }

        PackageSetting ps = null;
        PackageSetting updatedPkg;

        //省略了巨量关于保存及验证签名、applicationInfo等代码

        // Note that we invoke the following method only if we are about to unpack an application
        //保存了APK信息
        PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
                | SCAN_UPDATE_SIGNATURE, currentTime, user);
        //.....

        return scannedPkg;
    }

就如同注释所说:
1、利用PackageParser解析APK并返回Package对象,这个package是PackageParser的内部类,保存了APK信息,具体如何解析,我们下文再讲。
2、将解析出来的APK信息保存起来,我们就先来看看这个scanPackageLI方法。由于这个方法代码太多了,我就放上我们关心的部分:


            //保存Service 
            for (i=0; i<N; i++) {
                PackageParser.Service s = pkg.services.get(i);
                s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        s.info.processName, pkg.applicationInfo.uid);
                mServices.addService(s);
                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(' ');
                    }
                    r.append(s.info.name);
                }
            }
            if (r != null) {
                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Services: " + r);
            }

            N = pkg.receivers.size();
            r = null;

            //保存receiver
            for (i=0; i<N; i++) {
                PackageParser.Activity a = pkg.receivers.get(i);
                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        a.info.processName, pkg.applicationInfo.uid);
                mReceivers.addActivity(a, "receiver");
                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(' ');
                    }
                    r.append(a.info.name);
                }
            }
            if (r != null) {
                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Receivers: " + r);
            }

            N = pkg.activities.size();
            r = null;
            //保存Activity 
            for (i=0; i<N; i++) {
                PackageParser.Activity a = pkg.activities.get(i);
                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        a.info.processName, pkg.applicationInfo.uid);
                mActivities.addActivity(a, "activity");
                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(' ');
                    }
                    r.append(a.info.name);
                }
            }

很简单的代码,就是把APK里的四大组件信息分别保存到mActivities、mReceivers、mServices、mProviders等,当然,实际上还保存了很多信息,这里就不管了。


    // All available activities, for your resolving pleasure.
    final ActivityIntentResolver mActivities =
            new ActivityIntentResolver();

    // All available receivers, for your resolving pleasure.
    final ActivityIntentResolver mReceivers =
            new ActivityIntentResolver();

    // All available services, for your resolving pleasure.
    final ServiceIntentResolver mServices = new ServiceIntentResolver();

    // All available providers, for your resolving pleasure.
    final ProviderIntentResolver mProviders = new ProviderIntentResolver();

都保存到IntentResolver里了。
我们拿activity来说吧,当我们启动activity时,会到mInstrumentation的execStartActivity方法跨进程调用AMS的startActivity,最后会转到startActivityMayWait方法里,这个方法会调用PMS的resolveIntent方法,这就是我们PMS发挥作用的时候了!(不了解Activity启动流程的可以参考我的这篇文章

    @Override
    public ResolveInfo resolveIntent(Intent intent, String resolvedType,
            int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "resolve intent");
        List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId);
        return chooseBestActivity(intent, resolvedType, flags, query, userId);
    }

这段代码很清晰,调用queryIntentActivities在刚刚mActivities里找到对应的activity信息,具体我就不贴了,有兴趣的童鞋可以看源码。
终于知道manifest文件里的信息是如何取得的了,我们通过intent简单设置class就能找到对应的Activity及其信息。
阿勒,不好意思,人老容易忘事,PackageParser还没说。

三、parsePackage

刚刚说到parsePackage的parsePackage方法来解析APK信息,直接上代码

    public Package parsePackage(File packageFile, int flags) throws PackageParserException {
        if (packageFile.isDirectory()) {
            return parseClusterPackage(packageFile, flags);
        } else {
            return parseMonolithicPackage(packageFile, flags);
        }
    }

如果是文件夹就执行parseClusterPackage,如果是文件就执行parseMonolithicPackage,这里我们直接看后者就好了

    @Deprecated
    public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
        if (mOnlyCoreApps) {
            final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
            if (!lite.coreApp) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                        "Not a coreApp: " + apkFile);
            }
        }

        final AssetManager assets = new AssetManager();
        try {
            final Package pkg = parseBaseApk(apkFile, assets, flags);
            pkg.codePath = apkFile.getAbsolutePath();
            return pkg;
        } finally {
            IoUtils.closeQuietly(assets);
        }
    }

很清晰,先构建了AssetManager对象,然后直接调用parseBaseApk进行解析

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
            throws PackageParserException {
        final String apkPath = apkFile.getAbsolutePath();

        mParseError = PackageManager.INSTALL_SUCCEEDED;
        mArchiveSourcePath = apkFile.getAbsolutePath();

        if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);

        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);

        Resources res = null;
        XmlResourceParser parser = null;
        try {
            res = new Resources(assets, mMetrics, null);
            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    Build.VERSION.RESOURCES_SDK_INT);
            //1
            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

            final String[] outError = new String[1];
            //2
            final Package pkg = parseBaseApk(res, parser, flags, outError);


            pkg.baseCodePath = apkPath;
            pkg.mSignatures = null;

            return pkg;

        } catch (PackageParserException e) {
            throw e;
        } catch (Exception e) {

        } finally {
            IoUtils.closeQuietly(parser);
        }
    }

1、首先使用XmlResourceParser打开manifest文件
2、调用parseBaseApk解析manifest,在这个方法里就真正开始解析application、activity等信息了,就是普通的XML解析,我只贴上application解析的代码

if (tagName.equals("application")) {
                //......

                foundApp = true;
                if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
                    return null;
                }
            }

跟进parseBaseApplication方法

private boolean parseBaseApplication(Package owner, Resources res,
            XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
        throws XmlPullParserException, IOException {
    //......
    //解析icon等信息
    ai.icon = sa.getResourceId(
                com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
        ai.logo = sa.getResourceId(
                com.android.internal.R.styleable.AndroidManifestApplication_logo, 0);
        ai.banner = sa.getResourceId(
                com.android.internal.R.styleable.AndroidManifestApplication_banner, 0);
        ai.theme = sa.getResourceId(
                com.android.internal.R.styleable.AndroidManifestApplication_theme, 0);
        ai.descriptionRes = sa.getResourceId(
                com.android.internal.R.styleable.AndroidManifestApplication_description, 0);


    //循环解析application节点
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            if (tagName.equals("activity")) {
                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
                        owner.baseHardwareAccelerated);
                if (a == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }

                owner.activities.add(a);

            } else if (tagName.equals("receiver")) {
                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
                if (a == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }

                owner.receivers.add(a);

            } else if (tagName.equals("service")) {
                Service s = parseService(owner, res, parser, attrs, flags, outError);
                if (s == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }

                owner.services.add(s);

            } else if (tagName.equals("provider")) {
                Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
                if (p == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }

                owner.providers.add(p);
                }
                //省略其他解析代码

}

一目了然了,我就不多说了。
到这本文终于快结束了,我们回头来看看开篇所说的virtualAPK的LoadedPlugin,在他的构造方法里有这么一句话

this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);

PackageParserCompat的作用就是解析APK,parsePackage方法做了适配,本文的源码基于API22,所以我们直接看相关方法

        static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws PackageParser.PackageParserException {
            PackageParser parser = new PackageParser();
            PackageParser.Package pkg = parser.parsePackage(apk, flags);
            try {
                parser.collectCertificates(pkg, flags);
            } catch (Throwable e) {
                // ignored
            }
            return pkg;
        }

果然是利用系统的PackageParser 去解析APK信息

四、总结

1、开机启动时PMS扫描framework及相关核心代码
2、浏览已安装应用目录,一一扫描并利用PackageParser解析APK信息
3、将扫描好的APK信息保存在IntentResolver里面备用
4、当四大组件启动时,去 IntentResolver找到相应信息,比如系统判定是否在manifest里注册也是通过这里保存的信息。

    原文作者:verymrq
    原文地址: https://blog.csdn.net/verymrq/article/details/78968935
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞