深度揭秘,Android应用是如何安装到手机上的

b35be0b7423b7b5074409e6d25481844.jpeg

/   今日科技快讯   /

ChatGPT 的横空出世,在业界掀起了惊涛骇浪。专家表示,ChatGPT 和相关人工智能技术可能会威胁到一些工作岗位,尤其是白领工作。

自去年11月发布以来,新型聊天机器人模型 ChatGPT 已经被用于各种各样的工作:撰写求职信、编写儿童读物,甚至帮助学生在论文中作弊。谷歌公司发现,从理论上来讲,如果机器人参加谷歌的面试,该公司会雇佣它成为一名入门级程序员。

/   作者简介   /

本篇文章来自Pingred_hjh的投稿,文章主要对PackageManagerService的相关分析,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

Pingred_hjh的博客地址:

https://blog.csdn.net/qq_39867049?type=blog

/   开始   /

把一个安卓应用的apk文件下载下来之后,点击它进行安装,然后安装完成后,在桌面上点击它的图标进入app到使用,这个过程相信大家都很熟悉了,那么当点击这个apk进行安装的时候,PMS就开始进行工作,下面将详细讲解它是怎样工作的。

/   PackageManagerService   /

简称PMS,当安卓系统要安装某个应用时,apk文件会被加载到/data/app目录下,然后PMS就对该目录下的apk缓存文件进行扫描:

7131a958994b3ad18d0f1d319f284779.png

也就是说PMS就会在/data/app目录里对这个apk文件进行解析,apk文件里是有很多文件的,因此要让系统迅速定位到这么多类(四大组件等),就应该有个服务事先把它们的包名路径等信息加载好,这样就可以快速地定位到这些类(比如入口Activity等),而这个服务就是PMS,然后AMS即ActivityManagerService就能根据PMS里面的这些类信息来进行创建入口Activity等四大组件类并启动。

另外,在安卓手机开机时,PMS也会去扫描加载/data/app目录和system/app里每个apk里的所有类信息,然后缓存到PMS它自己里面定义好的集合里,供AMS使用:

a67bd5c543b0e5c439321b0be72cd696.png

PMS加载每个apk(包括系统apk)里的清单文件,然后把里面相应的类(节点)信息进行分类存储:

bb0c1204b90357ed8b58a86ffd4f6175.png

像上图AndroidManifest.xml清单文件里的节点信息,它是一个xml文件,PMS将这些节点信息缓存起来不用占太多容量,而且节点信息又清晰明了,有了这些信息足以让AMS快速去反射创建类对象,然后进行启动。

PMS使用的是一个工具类PackageParse去解析清单文件的,将里面的节点信息(Activity、Service、provider等)解析出来缓存到PMS里面的一个Package里的不同集合里:

074a21e0fcecec6fe18e8b6fba98dded.png

但这里要注意的是,收集Activity信息的集合里的这个Activity类它不是我们平常见到的活动类Activity类,它是Package类里的自己定义的特殊Activity,里面存储的只是清单文件里关于Activity类的一些路径、是否是启动活动等的一些配置信息,因此它不是正常的完整活动类Activity:

c52fc16f7498d0c6bd66dd7e439e6bac.png

上面这些集合就是Package类里对于清单文件里配置的四大组件而定义的缓存集合,这些集合就是用来缓存清单文件里配置的四大组件,但并不是真正的四大组件的类。

每个apk对应一个缓存对象Package,而PMS是一个单独进程,来管理这些Package对象。

之后当用户点击app图标的那一刻,AMS就根据PMS里Package里的这些集合里的类信息以及intent里设置的意图信息,然后定位到要显示的类,创建然后启动它:

a9a402a1ceb9ff18ff74ae90ee6000db.png

AMS从PMS里拿到这个启动类的信息数据之后,就缓存到它自己的ActivityThread里的ActivityClientRecord集合里,也就是跳转记录集合里:

d15634dffa669dc4f4f49cc034415fa1.png

/   SystemServer   /

SystemServer是管理和启动所有服务的类,PMS和AMS就是它负责管理和启动的。而它自己的启动方法run方法则是在手机开启时就会被调用:

/**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }

可以看到,它是在main里调用的,而main方法又是被zygote进程调用的,也就是说SystemServer进程是被zygote进程启动的。重新看回SystemServer的run方法,往下看:

// Start services.
        try {
            traceBeginAndSlog("StartServices");
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        } finally {
            traceEnd();
        }

这里面启动了很多服务,看startBootstrapServices()方法:

// Activity manager runs the show.
        traceBeginAndSlog("StartActivityManager");
        // TODO: Might need to move after migration to WM.
        ActivityTaskManagerService atm = mSystemServiceManager.startService(
                ActivityTaskManagerService.Lifecycle.class).getService();
        mActivityManagerService = ActivityManagerService.Lifecycle.startService(
                mSystemServiceManager, atm);
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        mActivityManagerService.setInstaller(installer);
        mWindowManagerGlobalLock = atm.getGlobalLock();
        traceEnd();
        ...

        traceBeginAndSlog("StartPackageManagerService");
        try {
            Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
            mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
        } finally {
            Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
        }
        mFirstBoot = mPackageManagerService.isFirstBoot();
        mPackageManager = mSystemContext.getPackageManager();
        traceEnd();

可以看到,构建了AMS对象也就是mActivityManagerService ,然后启动了AMS,继续往下看就是调用了PackageManagerService的main方法,即启动了PMS,所以我们也可以知道,当开机时,SystemServer会去启动AMS和PMS等这些服务:

5b5dbe56384d34dc244baa5eaf6cc2e8.png

而且从run方法来看,这个方法里这么多代码,相信里面也是不少耗时功能的,所以这也是手机开机时比较耗时的原因之一。

如果我们想拿到PMS对象,则可以通过context的getPackageManager()方法,但它返回的是PackageManager抽象对象,因此要调用它的实现对象ContextImpl的实现方法:

@Override
    public PackageManager getPackageManager() {
        if (mPackageManager != null) {
            return mPackageManager;
        }

        IPackageManager pm = ActivityThread.getPackageManager();
        if (pm != null) {
            // Doesn't matter if we make more than one instance.
            return (mPackageManager = new ApplicationPackageManager(this, pm));
        }

        return null;
    }

可以看到,通过ActivityThread的getPackageManager方法去获取,然后返回的是IPackageManager抽象对象,它是个Binder对象,因为此时我们是在app里使用context去获取PMS,这相当于app进程和SystemServer进程的通信(SystemServer拥有PMS),也就是跨进程间通信,因此要使用Binder机制来进行获取进程对象的:

@UnsupportedAppUsage
    public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
            return sPackageManager;
        }
        IBinder b = ServiceManager.getService("package");
        //Slog.v("PackageManager", "default service binder = " + b);
        sPackageManager = IPackageManager.Stub.asInterface(b);
        //Slog.v("PackageManager", "default service = " + sPackageManager);
        return sPackageManager;
    }

既然PMS也是服务,那么我们来看它的main方法:

public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        // Self-check for initial settings.
        PackageManagerServiceCompilerMapping.checkProperties();

        PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);
        m.enableSystemUserPackages();
        ServiceManager.addService("package", m);
        final PackageManagerNative pmn = m.new PackageManagerNative();
        ServiceManager.addService("package_native", pmn);
        return m;
    }

先是构造了自己PackageManagerService对象m,然后按照key-value方式存放到ServiceManager里,其实就是缓存,下次再来取PMS对象时,就可以直接按照key方法来取:

06d02b2949e8a3bf73aec3088648fb5e.png

所以可以知道ServiceManager存储了很多服务的对象:

f7c01f3f3f99d299e8fb705cbc701b92.png

App应用就可以去调用ServiceManager去取这些服务对象了,当然取出来的是Binder对象,存的时候则是真实的服务对象。

我们继续看回PMS的main方法里,它构造了自己,因此来看它的构造方法:

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager");
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
                SystemClock.uptimeMillis());

        if (mSdkVersion <= 0) {
            Slog.w(TAG, "**** ro.build.version.sdk not set!");
        }

        mContext = context;

        mFactoryTest = factoryTest;
        mOnlyCore = onlyCore;
        mMetrics = new DisplayMetrics();
        mInstaller = installer;

        ...

代码很长,所以这也解释了为什么手机开机时要花费这么长时间了,我们往下看这段代码:

public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {

            ...
            if (!mOnlyCore) {
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
                scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

                // Remove disable package settings for updated system apps that were
                // removed via an OTA. If the update is no longer present, remove the
                // app completely. Otherwise, revoke their system privileges.
            ...
            }
            ...
 }

这里调用了一个scanDirTracedLI()方法,里面有个变量sAppInstallDir,可以看看它是什么:

//Directory where installed applications are stored
private static final File sAppInstallDir =
            new File(Environment.getDataDirectory(), "app");

通过注释可以知道,这个sAppInstallDir就是缓存的要安装的应用的路径,也就是安装app时它的apk文件缓存的目录:

dc93721b8edfa1792bd4ed799cd9937f.png

DIR_ANDROID_ADTA的值就是data/app目录,刚刚可以看到sAppInstallDir被传到这个scanDirTracedLI方法里,这个方法就是去扫描data/app目录下每个apk方法,而系统app的apk缓存文件(system/app)也是在PMS构造方法里去扫描的:

// Collect ordinary system packages.
            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
            scanDirTracedLI(systemAppDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM,
                    0);

而这里的systemAppDir跟踪一下可以知道就是system/app目录:

4e18d3184210a64c721dd36b487ff07b.png

接下来我们来看看scanDirTracedLI()这个扫描方法的详情:

private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
        try {
            scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

它里面调用了scanDirLI(),那么继续跟踪此方法:

private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = scanDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            Log.d(TAG, "No files in app dir " + scanDir);
            return;
        }

        if (DEBUG_PACKAGE_SCANNING) {
            Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
                    + " flags=0x" + Integer.toHexString(parseFlags));
        }
        try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
                mParallelPackageParserCallback)) {
            // Submit files for parsing in parallel
            int fileCount = 0;
            for (File file : files) {
                final boolean isPackage = (isApkFile(file) || file.isDirectory())
                        && !PackageInstallerService.isStageName(file.getName());
                if (!isPackage) {
                    // Ignore entries which are not packages
                    continue;
                }
                parallelPackageParser.submit(file, parseFlags);
                fileCount++;
            }

            ...
            }
        }
    }

可以看到关键代码for循环里遍历每个apk文件,然后判断是否是apk文件才去解析,然后把apk文件传到了parallelPackageParser的submit方法里,来看看这个方法的详情:

public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            ParseResult pr = new ParseResult();
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
            try {
                PackageParser pp = new PackageParser();
                pp.setSeparateProcesses(mSeparateProcesses);
                pp.setOnlyCoreApps(mOnlyCore);
                pp.setDisplayMetrics(mMetrics);
                pp.setCacheDir(mCacheDir);
                pp.setCallback(mPackageParserCallback);
                pr.scanFile = scanFile;
                pr.pkg = parsePackage(pp, scanFile, parseFlags);
            } catch (Throwable e) {
                pr.throwable = e;
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
            try {
                mQueue.put(pr);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                // Propagate result to callers of take().
                // This is helpful to prevent main thread from getting stuck waiting on
                // ParallelPackageParser to finish in case of interruption
                mInterruptedInThread = Thread.currentThread().getName();
            }
        });
    }

实质上是调用了mService的submit方法,这个mService是一个线程池:

private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,
            "package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);

1f662e804c13d129af6a3eeb2526be53.png

线程核心数为4,也就是说,解析apk其实是启动了一个线程池去解析的:

be8b2ea9369c42752070507cb699cc22.png

这是安卓29(10.0)的版本下的解析代码,而以前版本比如6.0-8.0的版本下是没有使用线程池去解析的,直接在扫描方法里解析的:

889b6a5e2cdb78c410905d092f8b05c2.png

可以看到,6.0版本的解析过程中就没有启动线程池,而是直接就使用PackageParse去解析了,所以对比10.0系统,谷歌很明显优化了PMS在解析apk文件时的操作,启动了线程池去解析,因此现在新版的安卓手机开机时间会比以前快很多。而9.0和11.0也是启动了线程池去解析的,这里就不展示源码,感兴趣可自行搜索查看。

/   解析   /

上文parallelPackageParser的submit()里已经知道,里面会使用线程池,然后调用parsePackage()方法,该方法就是进行解析,把清单文件里的类信息都解析成Package对象返回给PMS:

40d47b79d3becbce839958f7907a8a26.png

现在来看看parsePackage()方法里的详情:

@VisibleForTesting
    protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
            int parseFlags) throws PackageParser.PackageParserException {
        return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
    }

接着跟踪packageParser的parsePackge()方法:

/**
     * Equivalent to {@link #parsePackage(File, int, boolean)} with {@code useCaches == false}.
     */
    @UnsupportedAppUsage
    public Package parsePackage(File packageFile, int flags) throws PackageParserException {
        return parsePackage(packageFile, flags, false /* useCaches */);
    }

返回的是Package的parsePackage()方法,所以继续跟踪:

public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
        if (parsed != null) {
            return parsed;
        }

        long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
        if (packageFile.isDirectory()) {
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            parsed = parseMonolithicPackage(packageFile, flags);
        }

        long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
        cacheResult(packageFile, flags, parsed);
        if (LOG_PARSE_TIMINGS) {
            parseTime = cacheTime - parseTime;
            cacheTime = SystemClock.uptimeMillis() - cacheTime;
            if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
                Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
                        + "ms, update_cache=" + cacheTime + " ms");
            }
        }
        return parsed;
    }

首先就是判断有无缓存useCaches,有则从缓存里直接调用getCachedResult()获取这个解析对象Package,没有缓存则重新解析,这种缓存机制也是9.0之后才有,9.0之前的版本是没有缓存,每次都需重新解析(所以这也是优化解析性能的一个点,感兴趣可自行查看源码对比):

继续往下看解析方法可以看到,里面有个判断packageFile.isDirectory(),如果不是目录,也就是有apk文件的,则调用parseMonolithicPackage(),因此继续看该方法的详情:

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

        final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
        try {
            final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
            pkg.setCodePath(apkFile.getCanonicalPath());
            pkg.setUse32bitAbi(lite.use32bitAbi);
            return pkg;
        } catch (IOException e) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed to get path: " + apkFile, e);
        } finally {
            IoUtils.closeQuietly(assetLoader);
        }
    }

代码虽然有点长,但还是可以看到中间调用了parseBaseApk()方法:

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

        String volumeUuid = null;
        if (apkPath.startsWith(MNT_EXPAND)) {
            final int end = apkPath.indexOf('/', MNT_EXPAND.length());
            volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
        }

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

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

        XmlResourceParser parser = null;
        try {
            final int cookie = assets.findCookieForPath(apkPath);
            if (cookie == 0) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                        "Failed adding asset path: " + apkPath);
            }
            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
            final Resources res = new Resources(assets, mMetrics, null);

            final String[] outError = new String[1];
            final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
            if (pkg == null) {
                throw new PackageParserException(mParseError,
                        apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
            }

            pkg.setVolumeUuid(volumeUuid);
            pkg.setApplicationVolumeUuid(volumeUuid);
            pkg.setBaseCodePath(apkPath);
            pkg.setSigningDetails(SigningDetails.UNKNOWN);

            return pkg;

        } catch (PackageParserException e) {
            throw e;
        } catch (Exception e) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed to read manifest from " + apkPath, e);
        } finally {
            IoUtils.closeQuietly(parser);
        }
    }

看到这里相信大家都能知道,parseBaseApk这个方法就是去解析清单文件AndroidManifest.xml,最终得把解析的节点信息都封装在Package对象,一个apk对应一个Package对象,然后返回给PMS,看看这个Package类:

public final static class Package implements Parcelable {
         @UnsupportedAppUsage
        public String packageName;

        // The package name declared in the manifest as the package can be
        // renamed, for example static shared libs use synthetic package names.
        public String manifestPackageName;
        ...
        // For now we only support one application per package.
        @UnsupportedAppUsage
        public ApplicationInfo applicationInfo = new ApplicationInfo();

        @UnsupportedAppUsage
        public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
        @UnsupportedAppUsage
        public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
        @UnsupportedAppUsage
        public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
        @UnsupportedAppUsage
        public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
        @UnsupportedAppUsage
        public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
        @UnsupportedAppUsage
        public final ArrayList<Service> services = new ArrayList<Service>(0);
        @UnsupportedAppUsage
        public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);

        @UnsupportedAppUsage
        public final ArrayList<String> requestedPermissions = new ArrayList<String>();
        ...
}

可以看到,有包名信息,还有权限集合,以及四大组件对应的存储集合,每个集合存放对应的类型(Activity、Service、Provider和PermissionGroup等)。

我们继续看回parseBaseApk()方法:

f59d8ed9bf3e94d3a91e47c0f6bb78d9.png

openXmlResourceParser()方法打开的就是我们的清单文件,也就是解析xml文件的节点信息,最后返回解析对象,然后把解析对象又传到了下面调用的parseBaseApk()方法里:

7b1ac958b4ba46a35fe45e494adfe2b9.png

来看parseBaseApk方法的详情:

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
            String[] outError) throws XmlPullParserException, IOException {
        final String splitName;
        final String pkgName;

        ...

        final Package pkg = new Package(pkgName);

        TypedArray sa = res.obtainAttributes(parser,
                com.android.internal.R.styleable.AndroidManifest);

        pkg.mVersionCode = sa.getInteger(
                com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
        pkg.mVersionCodeMajor = sa.getInteger(
                com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0);
        pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode());
        pkg.baseRevisionCode = sa.getInteger(
                com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);
        pkg.mVersionName = sa.getNonConfigurationString(
                com.android.internal.R.styleable.AndroidManifest_versionName, 0);
        if (pkg.mVersionName != null) {
            pkg.mVersionName = pkg.mVersionName.intern();
        }

        pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);

        pkg.mCompileSdkVersion = sa.getInteger(
                com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
        pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
        pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
                com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
        if (pkg.mCompileSdkVersionCodename != null) {
            pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
        }
        pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;

        sa.recycle();

        return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
    }

该方法是为了获取到app的版本号等信息(清单文件里定义的sdkVersion等),最后返回parseBaseApkCommon()方法:

private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
            XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
            IOException {
            ...
        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 (acceptedTags != null && !acceptedTags.contains(tagName)) {
                Slog.w(TAG, "Skipping unsupported element under <manifest>: "
                        + tagName + " at " + mArchiveSourcePath + " "
                        + parser.getPositionDescription());
                XmlUtils.skipCurrentTag(parser);
                continue;
            }

            if (tagName.equals(TAG_APPLICATION)) {
                if (foundApp) {
                    if (RIGID_PARSER) {
                        outError[0] = "<manifest> has more than one <application>";
                        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                        return null;
                    } else {
                        Slog.w(TAG, "<manifest> has more than one <application>");
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                }

                foundApp = true;
                if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
                    return null;
                }
            }
            ...
            else if (tagName.equals(TAG_PERMISSION_GROUP)) {
                if (!parsePermissionGroup(pkg, flags, res, parser, outError)) {
                    return null;
                }
            } else if (tagName.equals(TAG_PERMISSION)) {
                if (!parsePermission(pkg, res, parser, outError)) {
                    return null;
                }
            }
            ...
}

可以看到关键的代码,while循环里获取清单文件AndroidManifest.xml里每个节点,对不同的节点进行相应的解析方法,我们可以来看看其中一个方法,比如parseBaseApplication():

private boolean parseBaseApplication(Package owner, Resources res,
            XmlResourceParser parser, int flags, String[] outError)
        throws XmlPullParserException, IOException {

        final ApplicationInfo ai = owner.applicationInfo;
        final String pkgName = owner.applicationInfo.packageName;

        TypedArray sa = res.obtainAttributes(parser,
                com.android.internal.R.styleable.AndroidManifestApplication);
        ...
        String tagName = parser.getName();
        if (tagName.equals("activity")) {
                Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
                        owner.baseHardwareAccelerated);
                if (a == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }

                hasActivityOrder |= (a.order != 0);
                owner.activities.add(a);

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

                hasReceiverOrder |= (a.order != 0);
                owner.receivers.add(a);

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

                hasServiceOrder |= (s.order != 0);
                owner.services.add(s);

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

                owner.providers.add(p);

          }
...
}

可以看到,分别对清单文件里application节点里的元素activity节点和service节点等进行解析与创建,创建的是Package它自己的Activity类和Service类,这些类跟我们平时见到的Activity类和Service类是不同的,是PMS的自定义的内部类,用来存储清单文件里我们设置的类的信息,用相应的集合来存储,最后供AMS创建真正的类时用的:

public final static class Activity extends Component<ActivityIntentInfo> implements Parcelable {
        @UnsupportedAppUsage
        public final ActivityInfo info;
        private boolean mHasMaxAspectRatio;
        private boolean mHasMinAspectRatio;

        private boolean hasMaxAspectRatio() {
            return mHasMaxAspectRatio;
        }

        private boolean hasMinAspectRatio() {
            return mHasMinAspectRatio;
        }

        // To construct custom activity which does not exist in manifest
        Activity(final Package owner, final String className, final ActivityInfo info) {
            super(owner, new ArrayList<>(0), className);
            this.info = info;
            this.info.applicationInfo = owner.applicationInfo;
        }
        ...
}

不过这里要注意的是,解析广播组件的时候,用的也是Activity来构造,那是因为广播和Activity在清单文件里设置的时候很像,因此这里也是处于方便的考虑,把广播也认作是Activity。

最后解析出来的Activity对象都被添加到Package里对应的集合里:

db8fe416380cbcf4a06a6fa8eabe2cec.png

也就是这些集合:

@UnsupportedAppUsage
public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
@UnsupportedAppUsage
public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
@UnsupportedAppUsage
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
@UnsupportedAppUsage
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
@UnsupportedAppUsage
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
@UnsupportedAppUsage
public final ArrayList<Service> services = new ArrayList<Service>(0);
@UnsupportedAppUsage
public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);

我们再来看看parseActivity()方法:

private Activity parseActivity(Package owner, Resources res,
            XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,
            boolean receiver, boolean hardwareAccelerated)
            throws XmlPullParserException, IOException {

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

            if (parser.getName().equals("intent-filter")) {
                ActivityIntentInfo intent = new ActivityIntentInfo(a);
                if (!parseIntent(res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/,
                        intent, outError)) {
                    return null;
                }
                if (intent.countActions() == 0) {
                    Slog.w(TAG, "No actions in intent filter at "
                            + mArchiveSourcePath + " "
                            + parser.getPositionDescription());
                } else {
                    a.order = Math.max(intent.getOrder(), a.order);
                    a.intents.add(intent);
                }
                ...
            }
           ...    
}

可以该方法里解析activity节点时,也会遍历activity节点里的子节点,即解析清单文件里面的比如activity节点里的intent-filter信息,把ActivityIntentInfo对象intent解析出来然后放进Activity的intents集合里,这个intents集合是Component类里的II集合(Activity继承Component):

public static abstract class Component<II extends IntentInfo> {
        @UnsupportedAppUsage
        public final ArrayList<II> intents;
        @UnsupportedAppUsage
        public final String className;

        @UnsupportedAppUsage
        public Bundle metaData;
        @UnsupportedAppUsage
        public Package owner;
        /** The order of this component in relation to its peers */
        public int order;

        ComponentName componentName;
        String componentShortName;
       ...
}

/   总结   /

相信经过上面的讲解之后,大家都会对PMS有了更深的理解了,而且在开发过程中,我们自己也可以通过PMS去做一些事情,既然通过源码知道了PMS是通过scanDirTracedLI()方法去扫描apk文件里的清单文件,然后把里面的类等信息解析成一个Package对象(通过PackageParser的parsePackage()方法所得),那我们也可以通过反射对应的方法去对一些特定的apk文件进行扫描和解析,然后根据Package对象获取到里面的类信息,去创建我们需要的四大组件类,从而达到我们的app和其他apk进行通信的目的了。还有很多这些类似的应用场景也是可以利用PMS去完成的。

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

LeakCanary是怎么检测到内存泄露的,看完这篇你就懂了

Kotlin Flow响应式编程,StateFlow和SharedFlow

欢迎关注我的公众号

学习技术或投稿

222032073d82647723ac0cdba46032f2.png

3679f3785ce21b7e80ce569674e371b9.jpeg

长按上图,识别图中二维码即可关注

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值