AndroidWayAndroid进阶

Android启动时应用程序解析与安装

2018-10-11  本文已影响170人  码上就说

开始写本文的时候给大家提几个问题?

  • Android手机开机我们就能看到桌面上一个个app,这是怎么实现的?
  • 应用开发的时候,我们在AndroidManifest.xml中配置组件信息,这些信息是如何被解析并且真正应用上的?

应用开发的同学可能觉得我们不需要知道这个啊,我们会用就可以的。任何一个知识点,如果只是会用,不了解背后的实现机制,那只是一个简单的工具,没有转化成一个知识,更加不可能融化成一整套知识体系。希望大家在会用的基础上能多去探讨一下深层次的运行原理。看完本文之后,希望能解决大家的一些疑惑。
<PackageManagerService架构剖析开篇>中简单谈了PackageManagerService的启动流程和基本的解析流程,其实PackageManagerService是应用程序安装中重要的服务,它在安装一个应用程序的过程中,主要完成两件事情。接下来称PackageManagerService为PMS

  • 解析应用的配置文件AndroidManifest.xml文件
  • 为应用程序分配相应的uid和gid,保证应用在运行中获得相应的权限。

<Android应用程序启动>一文中介绍了应用程序进程启动的入口,启动应用程序的进程之后,接下来开始启动Home Activity,这需要和桌面应用Launcher交互,Launcher在启动的过程中,需要获取PMS中解析的系统中安装的应用程序信息,而PMS解析的过程又是在系统启动的时候就开始的。下面用一张图形象的表达一下:

应用解析流程.jpg
手机开机之后,系统开始运行,启动了init进程,init进程启动了Android系统的母进程-Zygote进程,Zygote进程fork systemserver进程,systemserver中启动PMS服务,PMS中扫面特定目录下的应用程序apk,然后解析apk,将信息保存起来,并启动一些系统应用。我们的桌面Launcher应用就是这时候启动的。这时候我们看到的桌面中排列了已安装应用的图标,我们点击一个icon,这时候就会触发这个应用的Launcher Activity调起,而这些信息都存在PMS中,Launcher也是从PMS中获取的。
本文我们讲解的重点就是第2部分:PMS解析过程,这也是解析apk和安装apk的重要过程。

1.PMS构造

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
//......
    synchronized (mInstallLock) {
        synchronized (mPackages) {
            mSettings = new Settings(mPermissionManager.getPermissionSettings(), mPackages);
        }
    }
//......
    synchronized (mInstallLock) {
        synchronized (mPackages) {
            mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));
        }
    }
//......
    File frameworkDir = new File(Environment.getRootDirectory(), "framework");
//......
    scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM
                    | SCAN_AS_VENDOR,
                    0);
    scanDirTracedLI(new File(PRODUCT_OVERLAY_DIR),
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM
                    | SCAN_AS_PRODUCT,
                    0);
    scanDirTracedLI(frameworkDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_NO_DEX
                    | SCAN_AS_SYSTEM
                    | SCAN_AS_PRIVILEGED,
                    0);
    final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
    scanDirTracedLI(privilegedAppDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM
                    | SCAN_AS_PRIVILEGED,
                    0);
      final File systemAppDir = new File(Environment.getRootDirectory(), "app");
      scanDirTracedLI(systemAppDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM,
                    0);
     File vendorAppDir = new File(Environment.getVendorDirectory(), "app");
     scanDirTracedLI(vendorAppDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM
                    | SCAN_AS_VENDOR,
                    0);
//......
    mPermissionManager.updateAllPermissions(
                    StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(),
                    mPermissionCallback);
//......
    mSettings.writeLPr();
//......
}

PMS的构造函数是SystemServer启动的时候就会执行的代码,具体的可以参考之前的文章<PackageManagerService架构剖析开篇>,我们这儿看到的首先执行的重要类是Settings类。

1.1 Settings类介绍

  • 这个类在com.android.server.pm中,是PackageManager中重要的管理类,它主要保存应用的动态设置。

Settings的构造函数中就表示它维护了几个系统文件:

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, "packages.xml");
        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
        mPackageListFilename = new File(mSystemDir, "packages.list");
        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, "packages-stopped.xml");
        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
    }

/data/system/目录下有下面这些文件:


/data/system/目录下文件.png

Settings中维护的是packages.xml packages.list packages-stopped.xml几个文件。

  • packages.xml维护的是应用包的基本信息,权限信息、用户权组信息等等。信息比较详细。
  • packages.list维护的是应用包列表,只是包含具体的包名以及存储的路径等等信息。
  • packages-stopped.xml 维护的是被停掉的应用。

packages-backup.xml是备份packages.xm内容的,同样的,packages-stopped-backup.xml也是备份packages-backup.xml内容的。

1.2 读取Settings信息

mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

创建了Settings对象,开始执行readLPw方法。系统每次启动的时候都会走到这里来。系统检索应用程序信息,将应用的相关信息保存到Settings中,然后启动的时候读取出来。因为在程序运行中这些设置和属性都有可能变化,当变化的时候会重新更新Settings中的文件。

1.2.1 解析packages.xml或者packages-backup.xml文件

首先读取本分的packages-backup.xml文件,如果没有的话,再去读取源文件。因为它们是xml文件,要用到XmlPullParser来解析。packages.xml是应用包的详细信息,所以包含的tag比较多。我们主要关注的是读取权限ID的那几句代码。

if (tagName.equals("package")) {
    readPackageLPw(parser);
} 

和ID相关的代码是:

name = parser.getAttributeValue(null, ATTR_NAME);
idStr = parser.getAttributeValue(null, "userId");
sharedIdStr = parser.getAttributeValue(null, "sharedUserId");

userId获取上一次安装应用时分配给它的独立ID,sharedUserId描述应用程序的共享ID。

else if (userId > 0) {
    packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                 new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
                 secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
                 pkgPrivateFlags, parentPackageName, null /*childPackageNames*/,
                 null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/);
} else if (sharedIdStr != null) {
    if (sharedUserId > 0) {
        packageSetting = new PackageSetting(name.intern(), realName, new File(
                       codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
                       primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                       versionCode, pkgFlags, pkgPrivateFlags, parentPackageName,
                       null /*childPackageNames*/, sharedUserId,
                       null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/);
        mPendingPackages.add(packageSetting);
    }
}
  1. userId > 0
    如果上次安装被分配了一个独立的ID,那么这个ID会被保存下来。
  2. sharedIdStr != null sharedUserId > 0
    else之后,说明之前应用安装的时候,并没有分配一个独立的ID,而是与其他程序共享一个ID。这种情况下不能立即保存当前的Linux ID,因为还不能确定ID是否为这个应用所有。会先创建一个PackageSetting对象放入mPendingPackages中,这表示当前应用还没有确定Linux ID,先存放起来,等到PMS解析完所有的共享ID之后,才能确定上次用用使用的共享ID是否有效。
    如果有效,那么PMS会为它保存上一次使用的Linux ID。
1.2.2 保留Linux用户ID

上面的userId > 0说明上次应用安装被分配了一个独立的Linux用户ID,会保留这个Linux ID,执行函数就是addPackageLPw(...)

PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,
            String legacyNativeLibraryPathString, String primaryCpuAbiString,
            String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc, int
            pkgFlags, int pkgPrivateFlags, String parentPackageName,
            List<String> childPackageNames, String[] usesStaticLibraries,
            long[] usesStaticLibraryNames) {
        PackageSetting p = mPackages.get(name);
        if (p != null) {
            if (p.appId == uid) {
                return p;
            }
            return null;
        }
        p = new PackageSetting(name, realName, codePath, resourcePath,
                legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
                cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags, parentPackageName,
                childPackageNames, 0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames);
        p.appId = uid;
        if (addUserIdLPw(uid, p, name)) {
            mPackages.put(name, p);
            return p;
        }
        return null;
    }

这个mPackages以name-packageName为key,packageSettings作为元素,因为这儿已经可以确定了当前应用使用上一次的Linux ID,所以开始需要校验一下,如果和mPackages中取出的PackageSettings中对比一下id一致的话,那就是合法的。
mPackages没有相应的packageSettings,需要自己创建一个对象。执行addUserIdLPw(uid, p, name)

private boolean addUserIdLPw(int uid, Object obj, Object name) {
        if (uid > Process.LAST_APPLICATION_UID) {
            return false;
        }
        if (uid >= Process.FIRST_APPLICATION_UID) {
            int N = mUserIds.size();
            final int index = uid - Process.FIRST_APPLICATION_UID;
            while (index >= N) {
                mUserIds.add(null);
                N++;
            }
            if (mUserIds.get(index) != null) {
                return false;
            }
            mUserIds.set(index, obj);
        } else {
            if (mOtherUserIds.get(uid) != null) {
                return false;
            }
            mOtherUserIds.put(uid, obj);
        }
        return true;
    }

1.应用程序使用的UID范围是 Process.FIRST_APPLICATION_UID 和 Process.LAST_APPLICATION_UID 之间(两边都包括)
public static final int FIRST_APPLICATION_UID = 10000;
public static final int LAST_APPLICATION_UID = 19999;
2.小于Process.FIRST_APPLICATION_UID存放在mOtherUserIds zhong ,他们不能作为应用程序的的Linux ID,却可以以共享的方式被使用。例如我们经常在AndroidManifest.xml中看到android:shareUserId="android.uid.system",表明此应用想和"android.uid.system" 的特权用户共享同一个Linux ID

1.2.3 解析共享ID

初始解析packages.xml的时候,解析完tag为package元素之后,开始解析tag为shared-user的元素节点。

private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException,IOException {
        String name = null;
        String idStr = null;
        int pkgFlags = 0;
        int pkgPrivateFlags = 0;
        SharedUserSetting su = null;
        try {
            name = parser.getAttributeValue(null, ATTR_NAME);
            idStr = parser.getAttributeValue(null, "userId");
            int userId = idStr != null ? Integer.parseInt(idStr) : 0;
            if ("true".equals(parser.getAttributeValue(null, "system"))) {
                pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
            }
            if (name == null) {
            } else if (userId == 0) {
            } else {
                if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags))
                        == null) {
                }
            }
        } catch (NumberFormatException e) {
        }
//......
    }

1.parser.getAttributeValue(null, "system")
如果当前是系统应用,需要标记一下,设置flag
2.当前的name != null 并且userId != 0,说明直接走到了else里面

addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags)

SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
        SharedUserSetting s = mSharedUsers.get(name);
        if (s != null) {
            if (s.userId == uid) {
                return s;
            }
            return null;
        }
        s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
        s.userId = uid;
        if (addUserIdLPw(uid, s, name)) {
            mSharedUsers.put(name, s);
            return s;
        }
        return null;
    }
    1. mSharedUsers 是一个ArrayMap结构,如果当前mSharedUsers结构中存储了对应name的SharedUserSetting对象,取出来校验一下uid,如果校验合法那就可以了。
  • 2.当前mSharedUsers结构中没有存储对应name的SharedUserSetting对象,还需要自己创建一个SharedUserSetting对象。执行addUserIdLPw(uid, s, name),这个方法上面讨论过。
1.2.4 处理PendingPackage对象

《1.2.1 解析packages.xml或者packages-backup.xml文件》中讲过之前应用安装的时候,并没有分配一个独立的ID,而是与其他程序共享一个ID,这种情况下不能立即保存当前的Linux ID,因为还不能确定ID是否为这个应用所有。会先创建一个PackageSetting对象放入mPendingPackages中,这表示当前应用还没有确定Linux ID,先存放起来,等到PMS解析完所有的共享ID之后,才能确定上次用用使用的共享ID是否有效。
我们现在看看是怎么处理mPendingPackages数据的。
解析完packages.xml文件之后,开始执行下面的一段代码:

final int N = mPendingPackages.size();
for (int i = 0; i < N; i++) {
     final PackageSetting p = mPendingPackages.get(i);
     final int sharedUserId = p.getSharedUserId();
     final Object idObj = getUserIdLPr(sharedUserId);
     if (idObj instanceof SharedUserSetting) {
          final SharedUserSetting sharedUser = (SharedUserSetting) idObj;
          p.sharedUser = sharedUser;
          p.appId = sharedUser.userId;
          addPackageSettingLPw(p, sharedUser);
      } else if (idObj != null) {
      } else {
      }
}
mPendingPackages.clear();

关键的一个函数:其中mUserIds与mOtherUserIds结构都是之前处理Linux ID与共享ID存储进去的。
这里我们早已解析完packages.xml,我们也知道了所有的shareId了,这些shareid相关的数据信息也分别存储在mUserIds与mOtherUserIds中,我只要将之前放在mPendingPackages中数据分别取出来,通过其中的共享ID去上面这两个map中查找一下,是否存在同样的共享ID,如果存在,说明mPendingPackages中包含的PackageSetting所描述的一个应用程序上一次使用的Linux ID是有效的,这时候就会保留上一次安装所使用的Linux ID,并创建PackageSettings对象保存在mPackages map中。

public Object getUserIdLPr(int uid) {
        if (uid >= Process.FIRST_APPLICATION_UID) {
            final int N = mUserIds.size();
            final int index = uid - Process.FIRST_APPLICATION_UID;
            return index < N ? mUserIds.get(index) : null;
        } else {
            return mOtherUserIds.get(uid);
        }
    }

1.3 检索系统目录

系统目录 注释
/system/framework/
/system/priv-app/ 享有系统私有权限保护的应用程序
/system/app/ 系统自带的应用程序
/vendor/priv-app/ 设备厂商
/vendor/app/
/odm/priv-app/ 原型厂商
/odm/app/
/product/priv-app/ 生产厂商
/product/app/
/data/app/ 用户自己安装的应用程序
/data/app-private/ 保存受DRM保护的私有应用程序

其实我们接下来主要关注的是/system/app/和/data/app/两个目录,关注他们是怎么解析apk文件的。因为这一块内容非常多,所以单独放在《2.检索应用目录》中分析。

1.4 更新资源访问权限

1.5 写入Settings信息

解析完成之后,会将变更的应用信息写入Settings中。

2.检索应用目录

检索系统应用

scanDirTracedLI(systemAppDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM,
                    0);

检索用户安装的应用

scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

发现其他的参数相同,第1个目录参数不同。第2个和第3个参数不同,这在下面的解析安装中会有体现。

private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = scanDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            return;
        }
        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++;
            }

            // Process results one by one
            for (; fileCount > 0; fileCount--) {
                ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
                Throwable throwable = parseResult.throwable;
                int errorCode = PackageManager.INSTALL_SUCCEEDED;

                if (throwable == null) {
                    if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
                        renameStaticSharedLibraryPackage(parseResult.pkg);
                    }
                    try {
                        if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
                            scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
                                    currentTime, null);
                        }
                    } catch (PackageManagerException e) {
                        errorCode = e.error;
                    }
                } else if (throwable instanceof PackageParser.PackageParserException) {
                    PackageParser.PackageParserException e = (PackageParser.PackageParserException)
                            throwable;
                    errorCode = e.error;
                } else {
                }

                // Delete invalid userdata apps
                if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
                        errorCode != PackageManager.INSTALL_SUCCEEDED) {
                    removeCodePathLI(parseResult.scanFile);
                }
            }
        }
    }

这儿是解析的核心函数,下面用一个流程图简单地表示一下解析的流程。

2.1 判断当前的文件类型

传入的scanDir是一个文件目录,我们取出其中的所有文件,判断当前的文件是否符合下面的条件。

final boolean isPackage = (isApkFile(file) || file.isDirectory())
                        && !PackageInstallerService.isStageName(file.getName());
  1. 当前file是apk文件或者是一个文件路径
    2.当前file不是安装的临时文件,深入到这个判断中就可以发现:这个文件一般是临时生成的文件,所以不必要解析。

2.2 文件提交解析

ParallelPackageParser是一个并行解析package的类,里面使用线程池来完成并行的工作。

parallelPackageParser.submit(file, parseFlags);
public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            ParseResult pr = new ParseResult();
            try {
                PackageParser pp = new PackageParser();
              //设置PackageParser属性
                pr.pkg = parsePackage(pp, scanFile, parseFlags);
            } catch (Throwable e) {
                pr.throwable = e;
            }
            try {
                mQueue.put(pr);
            } catch (InterruptedException e) {
            }
        });
    }

中间省略设置PackageParser属性的过程,执行parsePackage函数之后,将ParseResult,就是解析的结果,放在一个阻塞型队列中。

private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,
            "package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);

整体的解析过程放在《3.解析package过程》中讨论。

2.3 遍历取出阻塞型队列中的解析结果

ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();

3.解析package过程

核心的解析过程放在PackageParser类中,PackageParser是磁盘上的包文件(APKS)的解析器。这支持应用程序打包。支持作为单一整体的apk,或者同一个目录下的多个apk文件。

  • 打包为多个APK的应用程序总是由单一的APK组成和零个或多个“拆分”APKs(具有独特的分割)
    这些分割APKs的任何子集都是有效的安装,只要满足以下约束:
    1.所有APK必须具有完全相同的包名、版本码和签名。
    2.证书。
    3.所有APK必须具有唯一的拆分名称。
    4.所有安装必须包含一个单一的基础APK。
    这就是所谓的拆包的基础。
public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
        if (parsed != null) {
            return parsed;
        }
        if (packageFile.isDirectory()) {
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            parsed = parseMonolithicPackage(packageFile, flags);
        }
        cacheResult(packageFile, flags, parsed);
        return parsed;
    }

实际上/system/app/一个目录下文件,一般都是目录,目录里面会放一些apk文件。
我们解析整体的apk的方法已经失效了,后期会去掉这个parseMonolithicPackage方法,现在主要讨论下parseClusterPackage方法。

private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
        final PackageLite lite = parseClusterPackageLite(packageDir, 0);
        if (mOnlyCoreApps && !lite.coreApp) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                    "Not a coreApp: " + packageDir);
        }

        // Build the split dependency tree.
        SparseArray<int[]> splitDependencies = null;
        final SplitAssetLoader assetLoader;
        if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
            try {
                splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
                assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
            } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
            }
        } else {
            assetLoader = new DefaultSplitAssetLoader(lite, flags);
        }

        try {
            final AssetManager assets = assetLoader.getBaseAssetManager();
            final File baseApk = new File(lite.baseCodePath);
            final Package pkg = parseBaseApk(baseApk, assets, flags);
            if (pkg == null) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                        "Failed to parse base APK: " + baseApk);
            }

            if (!ArrayUtils.isEmpty(lite.splitNames)) {
                final int num = lite.splitNames.length;
                pkg.splitNames = lite.splitNames;
                pkg.splitCodePaths = lite.splitCodePaths;
                pkg.splitRevisionCodes = lite.splitRevisionCodes;
                pkg.splitFlags = new int[num];
                pkg.splitPrivateFlags = new int[num];
                pkg.applicationInfo.splitNames = pkg.splitNames;
                pkg.applicationInfo.splitDependencies = splitDependencies;
                pkg.applicationInfo.splitClassLoaderNames = new String[num];

                for (int i = 0; i < num; i++) {
                    final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
                    parseSplitApk(pkg, i, splitAssets, flags);
                }
            }

            pkg.setCodePath(packageDir.getCanonicalPath());
            pkg.setUse32bitAbi(lite.use32bitAbi);
            return pkg;
        } catch (IOException e) {
        } finally {
            IoUtils.closeQuietly(assetLoader);
        }
    }

1.解析文件下所有apk文件,只是简单的解析
2.组装这些分包
3.解析base apk
4.解析分包

3.1 解析目录下所有apk文件

final PackageLite lite = parseClusterPackageLite(packageDir, 0);

这个下面的代码有点多,不一一贴上了,但是会将核心的功能详细描述。

  1. 遍历传入目录下的所有文件,选出其中是apk的文件,然后轻微的解析其中的元素。
    什么叫轻微的解析?
    就是主要完成两方面的工作:
    1.1 首先解析一下AndroidManifest.xml中的基本属性信息,我们解析这些信息,是为了提前判断当前应用是否能否被安装。
    例如installLocation、versionCode、coreApp、debuggable,这些属性我们提前获取,是为了提前判断一下,如果不符合条件的话,例如versionCode没有之前的版本号高的话,那就没有必须解析其他的信息了,节省我们的时间。
    1.2 证书校验,我们在开发apk的时候,都需要签名,这是为了保护apk隐私和提升应用安全性的有效手段,我们提前校验一下签名是否合法,如果不合法,那也没有必要继续下去了。

2.取出baseApk
怎么判断是baseApk了?
步骤1中解析文件目录下所有的apk文件时,都会将当前的apk放入一个ArrayMap中,其中key是apk的splitName属性,如果当前的baseApk的话,就没有splitName属性的,且一个目录下只能有一个baseApk,就是我们常说的主Apk
取出的代码大家借鉴一下:
final ApkLite baseApk = apks.remove(null);
将ArrayMap中key为null值取出来,就是baseApk,那么apks中剩下的都是splitApk集合了。

3.处理splitApk集合
这儿的处理,就是赋值一些属性,和对集合的apk排序等等。

3.2 组装这些split apk

这儿没什么好说的,将之前得到的splitApk信息封装到新的类中,下面会用到。

3.3 解析base apk

这是解析apk的重点,我们也会重点讲解一下。

final Package pkg = parseBaseApk(baseApk, assets, flags);
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);
        }
    }

函数里面有这样一句赋值:这个mParseError就是判断当前的apk是否解析成功的标识。

mParseError = PackageManager.INSTALL_SUCCEEDED;

这儿同样解析AndroidManifest.xml文件,不是轻微的解析了,是全面的解析,所有节点信息都要解析完全了。
完整的解析方法在parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
XmlResourceParser parser, int flags, String[] outError)中,方法本身太长了,我不一一讲解了,之前我的一篇文章《PMS/AMS剖析之应用开机自启动》里面有分析android:persistent="true"属性的解析过程,大家感兴趣的可以去看一下,其他的属性解析和应用的过程都是类似的。这里大家举一反三就可以的。
这里还是有必要说一下四大组件的解析过程:这里先以Activity举例。

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

            } 
/**
放在一个解析循环中调用,解析了组件信息和组件相关的其他信息。
**/
}

parseActivity(...)是解析activity的所有属性,然后将解析的activity放入Package对象中,就是定义的owner对象。

3.4 解析split apk

解析split apk的列表,之前手机的split apk集合,都在这里一一解析。解析split apk和解析base apk有些不同,split apk中不会定义权限,只有一些组件信息和组件相关的配置信息,而且解析完之后都会加载baseApk相关的结构中去。就是说split apk没有独立的数据结构承载其中的配置。还是会依赖于baseApk的owner

3.5 处理解析完的package

回到之前处理ParsePackage之前的代码:

public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            ParseResult pr = new ParseResult();
            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 {
            }
            try {
                mQueue.put(pr);
            } catch (InterruptedException e) {
            }
        });
    }

之前解析完的Package作为PackageParser类中的一个对象,放入阻塞型队列中。用到的时候取用。这时候我们的目录下的apk文件全部解析完成放在PackageParser对象中,里面的这些信息在接下来安装和启动的过程中都有重要的作用。

3.6 写入解析的settings信息

PMS构造函数中,解析完package信息之后,开始将解析之后的信息写入本地xml文件中,写入调用的地方就是在PMS构造函数的结尾处。

mSettings.writeLPr();

具体写入的文件是/data/system/packages.xml,以写入权限信息为例:

serializer.startTag(null, "permission-trees");
mPermissions.writePermissionTrees(serializer);
serializer.endTag(null, "permission-trees");

操作packages.xml文件之后,还会将读写的其他信息写入本地,例如包列表、运行时权限等等。

writeKernelMappingLPr();
writePackageListLPr();
writeAllUsersPackageRestrictionsLPr();
writeAllRuntimePermissionsLPr();

写入这些信息,有两个方面的作用:

  • 启动的时候解析所用应用相关的信息,这些信息构成了这些应用的完整面貌,之后我们在调用这些应用的时候,例如申请权限,需要用到这些信息。
  • 重新安装或者重新启动的时候,这些之前保存过的信息起到的校验作用,保证前后的一贯性和安全性,这是Android系统安全性的一个方面。像签名校验等内容本文没有深入展开,后续会独立成章节专门讲解。

至此Android系统启动时应用程序的解析安装过程就讲解完成了,至于用户在应用市场上下载安装包自己安装的过程和系统启动时安装的路径略有不同,但是基本差别不大,下面一篇文章我们会讨论一下Android应用程序的Launcher功能。敬请期待。最近实在太忙,更新文章松懈了,要加油。

上一篇 下一篇

猜你喜欢

热点阅读