Android 系统启动解析 Manifest 流程

转载请标明出处:一片枫叶的专栏

最近有同学问我关于Manifest何时被系统解析的问题,正好也分析到这一块了,索性这一章就讲解一下android系统何时解析Manifest吧,这里的Manifest指的是android安装文件apk中的androidManifest.xml文件是何时被解析的。
大家应该都知道,Android系统启动之后,我们就可以在一个应用中打开另一个从未打开过的应用,或者是在一个应用中发送广播,如果另外一个应用设置了这个广播的接收器,那么这个应用进程就会被启动并接收该广播并作出相应的处理,这样的例子很多,我们可以猜测到Android系统在启动的时候就会抓取到了系统中所有安装的应用信息(应该是解析apk文件的Manifest信息),即在Android系统的启动过程中就已经解析了系统中安装应用的androidManifest.xml文件并保存起来了,那么这个过程具体是如何的呢?

其实android系统启动过程中解析Manifest的流程是通过PackageManagerService服务来实现的。这里我们重点分析一下PackageManagerService服务是如何解析Manifest的。

首先看一下在SystemServer进程启动过程中是如何启动PackageManagerService服务的:

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

        ...
    }

在SystemServer进程启动过程中会调用SystemServer类的startBootstrapServices方法(主要用于启动ActivityManagerService服务和PackageManagerService服务),然后会在这个方法中会调用PackageManagerService.main静态方法,这个方法主要是用来初始化PackageManagerService服务并执行相关逻辑的。下面我来看一下main方法的具体逻辑:

public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);
        ServiceManager.addService("package", m);
        return m;
    }

可以发现main方法的实现逻辑主要是创建了一个PackageManagerService对象,并将这个对象添加到ServierManager中为其他组件提供服务。好吧,看来PackageManagerService的初始化操作主要是在PackageManagerService的构造方法中了,下面我们来看一下其构造方法的实现逻辑:

File dataDir = Environment.getDataDirectory();
            mAppDataDir = new File(dataDir, "data");
            mAppInstallDir = new File(dataDir, "app");
            mAppLib32InstallDir = new File(dataDir, "app-lib");
            mAsecInternalPath = new File(dataDir, "app-asec").getPath();
            mUserAppDataDir = new File(dataDir, "user");
            mDrmAppPrivateInstallDir = new File(dataDir, "app-private");

PackageManagerService的构造方法代码量比较大,这里就不贴出所有的代码了,我们主要和解析Manifest相关的主要代码,在构造方法中有这样几段代码。可以发现在构造方法中,解析了系统中几个apk的安装目录,这几个目录就是系统中安装apk的目录,android系统会默认解析这几个目录下apk文件,也就是说如果我们android手机在其他的目录下存在apk文件系统是不会默认解析的,反过来说,如果我们把我们的apk文件移动到这几个目录下,那么重新启动操作系统,该apk文件就会被系统解析并执行相关的逻辑操作,具体做什么操作呢?我们看下面的实现。

/ overlay packages if they reside in VENDOR_OVERLAY_DIR.
            File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
            scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

            // Find base frameworks (resource packages without code).
            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.
            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
            scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

            // Collect all vendor packages.
            File vendorAppDir = new File("/vendor/app");
            try {
                vendorAppDir = vendorAppDir.getCanonicalFile();
            } catch (IOException e) {
                // failed to look up canonical path, continue with original one
            }
            scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

            // Collect all OEM packages.
            final File oemAppDir = new File(Environment.getOemDirectory(), "app");
            scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

在我们刚刚的PackageManagerService.mani方法中,解析完刚刚的几个系统目录之后系统会调用scanDirLI方法,那么这个方法主要是做什么用的呢?看它的名字应该是遍历这个系统目录。好吧,这个方法主要就是用于解析上面几个目录下的apk文件的。不信?我们看一下scanDirLI方法的具体实现:

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

        if (DEBUG_PACKAGE_SCANNING) {
            Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
                    + " flags=0x" + Integer.toHexString(parseFlags));
        }

        for (File file : files) {
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            if (!isPackage) {
                
                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());

                
                if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                        e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
                    logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
                    if (file.isDirectory()) {
                        mInstaller.rmPackageDir(file.getAbsolutePath());
                    } else {
                        file.delete();
                    }
                }
            }
        }
    }

可以放下其首先会遍历该目录下的所有文件,并判断是否是apk文件,如果是apk文件则调用scanPackageLI方法,scanPackageLI方法的名字很明显,就是用于解析这个apk文件的。

继续看一下scanPakcageLI方法的实现:

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
            long currentTime, UserHandle user) throws PackageManagerException {
        if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
        parseFlags |= mDefParseFlags;
        PackageParser pp = new PackageParser();
        pp.setSeparateProcesses(mSeparateProcesses);
        pp.setOnlyCoreApps(mOnlyCore);
        pp.setDisplayMetrics(mMetrics);

        if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
            parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
        }

        final PackageParser.Package pkg;
        try {
            pkg = pp.parsePackage(scanFile, parseFlags);
        } catch (PackageParserException e) {
            throw PackageManagerException.from(e);
        }
        ...
}

好吧,这个方法也比较复杂,这里只是列出重点相关的代码,我们可以发现在这个方法中创建了一个PackagerParser对象,并调用了parsePackage方法,这个方法其实就是解析Manifest的主要方法,我们可以看一下其具体的实现:

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

可以发现,若我们解析的File对象是一个文件夹则执行调用parseClusterPackage方法,否则调用执行parseMonolithicPackage方法,很明显的因为我们这里解析的是apk文件(在上一方法中我们循环遍历得到了apk文件,这里的File对象就代表了一个个的apk文件信息),所以这里会执行parseMonolithicPackage方法,然后我们来看一下parseMonolithicPackage方法:

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);
        }
    }

可以看出,这里又调用了parseBaseApk方法:

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
            ...
            final Package pkg = parseBaseApk(res, parser, flags, outError);
            ...
    }

可以看出,这个parseBaseApk方法调用了其重载的parseBaseApk方法:

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

                foundApp = true;
                if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
                    return null;
                }
            } else if (tagName.equals("overlay")) {
                pkg.mTrustedOverlay = trustedOverlay;

                sa = res.obtainAttributes(attrs,
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay);
                pkg.mOverlayTarget = sa.getString(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage);
                pkg.mOverlayPriority = sa.getInt(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
                        -1);
                sa.recycle();

                if (pkg.mOverlayTarget == null) {
                    outError[0] = "
    
      does not specify a target package"
    ;
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return null;
                }
                if (pkg.mOverlayPriority < 0 || pkg.mOverlayPriority > 9999) {
                    outError[0] = "
    
      priority must be between 0 and 9999"
    ;
                    mParseError =
                        PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return null;
                }
                XmlUtils.skipCurrentTag(parser);

            } else if (tagName.equals("key-sets")) {
                if (!parseKeySets(pkg, res, parser, attrs, outError)) {
                    return null;
                }
            } else if (tagName.equals("permission-group")) {
                if (parsePermissionGroup(pkg, flags, res, parser, attrs, outError) == null) {
                    return null;
                }
            } else if (tagName.equals("permission")) {
                if (parsePermission(pkg, res, parser, attrs, outError) == null) {
                    return null;
                }
            } else if (tagName.equals("permission-tree")) {
                if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) {
                    return null;
                }
            } else if (tagName.equals("uses-permission")) {
                if (!parseUsesPermission(pkg, res, parser, attrs)) {
                    return null;
                }
            } else if (tagName.equals("uses-permission-sdk-m")
                    || tagName.equals("uses-permission-sdk-23")) {
                if (!parseUsesPermission(pkg, res, parser, attrs)) {
                    return null;
                }
            }
            ...
        }

在这个parseBaseApk方法中有一个while循环,该循环主要就是用于解析AndroidManifest.xml文件中的节点信息。在开始解析application节点的时候,同时调用了parseBaseApplication方法,该方法解析了application节点下的activity,service,broadcast,contentprovier等组件的定义信息:

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);

            }
            ...
        }

这样,经过这里循环遍历,整个androidManifest的节点信息就被解析并保存在了Package对象中。可以看到我们平时在Manifest中定义的各种节点,其实都是在这里有所体现。当androidManifest.xml文件被解析完成之后会调用我们刚刚介绍的scanPackageLI的重载方法,将解析完成的Package对象信息保存的Setting对象中,这个对象用于保存app的安装信息,具体实现是在方法:

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
            long currentTime, UserHandle user) throws PackageManagerException

当解析完成manifest文件之后会调用其重载方法:

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

这样,解析的manifest文件信息就会被保存到Settings中,并持久化,然后执行安装apk的操作,我们可以看一下该重载方法的具体实现:

private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
        boolean success = false;
        try {
            final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
                    currentTime, user);
            success = true;
            return res;
        } finally {
            if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
                removeDataDirsLI(pkg.volumeUuid, pkg.packageName);
            }
        }
    }

可以发现其内部调用了scanPackageDirtyLI方法,这个方法就是实际实现持久化manifest信息并安装APK操作的:

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
    ...
    // And now re-install the app.
    ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
                  pkg.applicationInfo.seinfo);
    ...
}

可以发现其内部调用了createDataDirLI,该方法主要实现安装apk的操作。

private int createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) {
        int[] users = sUserManager.getUserIds();
        int res = mInstaller.install(volumeUuid, packageName, uid, uid, seinfo);
        if (res < 0) {
            return res;
        }
        for (int user : users) {
            if (user != 0) {
                res = mInstaller.createUserData(volumeUuid, packageName,
                        UserHandle.getUid(user, uid), user, seinfo);
                if (res < 0) {
                    return res;
                }
            }
        }
        return res;
    }

查看该方法的实现:

public int install(String uuid, String name, int uid, int gid, String seinfo) {
        StringBuilder builder = new StringBuilder("install");
        builder.append(' ');
        builder.append(escapeNull(uuid));
        builder.append(' ');
        builder.append(name);
        builder.append(' ');
        builder.append(uid);
        builder.append(' ');
        builder.append(gid);
        builder.append(' ');
        builder.append(seinfo != null ? seinfo : "!");
        return mInstaller.execute(builder.toString());
    }

怎么样?很熟悉吧,这里的Installer其实调用的就是我们平时运行android项目很熟悉的install命令,原来android系统安装apk文件底层都是调用的adb命令。

总结:

  • android系统启动之后会解析固定目录下的apk文件,并执行解析,持久化apk信息,重新安装等操作;

  • 解析Manifest流程:Zygote进程 –> SystemServer进程 –> PackgeManagerService服务 –> scanDirLI方法 –> scanPackageLI方法 –> PackageParser.parserPackage方法;

  • 解析完成Manifest之后会将apk的Manifest信息保存在Settings对象中并持久化,然后执行重新安装的操作;

另外对android源码解析方法感兴趣的可参考我的:
android源码解析之(一)–>android项目构建过程
android源码解析之(二)–>异步消息机制
android源码解析之(三)–>异步任务AsyncTask
android源码解析之(四)–>HandlerThread
android源码解析之(五)–>IntentService
android源码解析之(六)–>Log
android源码解析之(七)–>LruCache
android源码解析之(八)–>Zygote进程启动流程
android源码解析之(九)–>SystemServer进程启动流程
android源码解析之(十)–>Launcher启动流程
android源码解析之(十一)–>应用进程启动流程

本文以同步至github中:github.com/yipianfengy…,欢迎star和follow

    原文作者:Android源码分析
    原文地址: https://juejin.im/entry/579f0b79165abd006115c5a4
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞