Skip to content

Split APK 解析

追踪 base 与 split APK 的轻量聚合、依赖树、资源加载、Manifest 解析和对外路径生成。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageParserSplit APK源码阅读

Split APK 解析 ​

本文承接 Manifest 解析入口、四大组件解析、meta-data 解析 和 PackageCacher 缓存。本文只讨论包解析阶段如何把一个目录中的 base APK 和 split APK 组成一个 ParsingPackage,不展开安装会话如何下载 split,也不把运行时 ClassLoader 的实现误当成解析器工作。

Android 17 的 Split 解析是两阶段流程:第一阶段只读取每个 APK 的少量 Manifest 字段,构造不可变的 PackageLite;第二阶段创建资源加载器,解析 base Manifest,再逐个解析 split Manifest。PackageLite 的数组索引是后续所有状态的坐标系,依赖、路径、revision code、是否 feature split 和 class loader 名称都必须保持同一顺序。

1. 两阶段模型 ​

1.1 主调用链 ​

PackageLite 不是最终包对象,它只保存后续解析需要的轻量元数据;真正的组件、权限和 application 字段由 base/split Manifest 阶段填入同一个 ParsingPackage。

1.2 单 APK 与目录 ​

源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java

java
public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
        File packageFile, int flags) {
    if (packageFile.isDirectory()) {
        return parseClusterPackageLite(input, packageFile, flags);
    } else {
        return parseMonolithicPackageLite(input, packageFile, flags);
    }
}

单 APK 直接包装成没有 split 数组的 PackageLite;目录才需要枚举、校验和排序。这里的目录判断发生在轻量解析入口,PackageParser2 的完整解析阶段不会自己重新猜测包组织形式。

2. Split 坐标 ​

2.1 字段 ​

源码文件:frameworks/base/core/java/android/content/pm/parsing/PackageLite.java

java
private final @NonNull String mPackageName;
private final @NonNull String mPath;
private final @NonNull String mBaseApkPath;
private final @Nullable String[] mSplitApkPaths;
private final @Nullable String[] mSplitNames;
private final @Nullable String[] mUsesSplitNames;
private final @Nullable String[] mConfigForSplit;
private final @Nullable Set<String> mBaseRequiredSplitTypes;
private final @Nullable Set<String>[] mRequiredSplitTypes;
private final @Nullable Set<String>[] mSplitTypes;
private final int mBaseRevisionCode;
private final @Nullable int[] mSplitRevisionCodes;
private final @Nullable boolean[] mIsFeatureSplits;
private final boolean mIsolatedSplits;
private final boolean mSplitRequired;

mSplitNames[i]、mSplitApkPaths[i]、mUsesSplitNames[i]、mConfigForSplit[i]、mSplitRevisionCodes[i] 和对应布尔/集合字段共同描述第 i 个 split。它们不是独立列表,任何排序或过滤都必须对所有数组同步进行。

2.2 Base 属性 ​

java
public PackageLite(String path, String baseApkPath, ApkLite baseApk,
        String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
        String[] configForSplit, String[] splitApkPaths, int[] splitRevisionCodes,
        int targetSdk, Set<String>[] requiredSplitTypes, Set<String>[] splitTypes) {
    mPath = path;
    mBaseApkPath = baseApkPath;
    mPackageName = baseApk.getPackageName();
    mVersionCode = baseApk.getVersionCode();
    mVersionCodeMajor = baseApk.getVersionCodeMajor();
    mBaseRevisionCode = baseApk.getRevisionCode();
    mIsolatedSplits = baseApk.isIsolatedSplits();
    mBaseRequiredSplitTypes = baseApk.getRequiredSplitTypes();
    mSplitRequired = (baseApk.isSplitRequired() || hasAnyRequiredSplitTypes());
    mSplitNames = splitNames;
    mSplitTypes = splitTypes;
    mIsFeatureSplits = isFeatureSplits;
    mUsesSplitNames = usesSplitNames;
    mConfigForSplit = configForSplit;
    mSplitApkPaths = splitApkPaths;
    mSplitRevisionCodes = splitRevisionCodes;
    mTargetSdk = targetSdk;
}

包名、版本、签名、target SDK、isolatedSplits 和 base 的 required split types 都从 base ApkLite 继承。split 自身不会成为另一个 package;它是同一 package 的补充输入。

2.3 统一路径 ​

java
public List<String> getAllApkPaths() {
    final ArrayList<String> paths = new ArrayList<>();
    paths.add(mBaseApkPath);
    if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
        Collections.addAll(paths, mSplitApkPaths);
    }
    return paths;
}

资源加载、native library 扫描等消费者可以通过 getAllApkPaths() 按 base-first 顺序获得完整 APK 集合。base 永远位于索引 0,split 数组从索引 0 开始但在依赖加载器内部会整体右移一位。

3. 轻量解析 ​

3.1 枚举与一致性 ​

源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java

java
public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
        File packageDir, int flags) {
    final File[] files = packageDir.listFiles();
    if (ArrayUtils.isEmpty(files)) {
        return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
                "No packages found in split");
    }
    if (files.length == 1 && files[0].isDirectory()) {
        return parseClusterPackageLite(input, files[0], flags);
    }

    String packageName = null;
    int versionCode = 0;
    final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
    for (File file : files) {
        if (!isApkFile(file)) {
            continue;
        }
        final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
        if (result.isError()) {
            return input.error(result);
        }
        final ApkLite lite = result.getResult();
        if (packageName == null) {
            packageName = lite.getPackageName();
            versionCode = lite.getVersionCode();
        } else if (!packageName.equals(lite.getPackageName())
                || versionCode != lite.getVersionCode()) {
            return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                    "Inconsistent package or version in " + file);
        }
        ApkLite previous = apks.put(lite.getSplitName(), lite);
        if (previous != null) {
            return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                    "Split name " + lite.getSplitName() + " defined more than once");
        }
    }
    final ApkLite baseApk = apks.remove(null);
    return composePackageLiteFromApks(input, packageDir, baseApk, apks);
}

轻量阶段先过滤非 APK 文件,再要求所有 APK 的 package name 和 version code 一致,并以 split name 为 key 拒绝重复定义。null split name 代表 base;最后必须存在 base,否则组合阶段失败。

3.2 排序与数组 ​

java
final int size = ArrayUtils.size(splitApks);
String[] splitNames = null;
String[] splitCodePaths = null;
int[] splitRevisionCodes = null;
if (size > 0) {
    splitNames = new String[size];
    splitCodePaths = new String[size];
    splitRevisionCodes = new int[size];
    splitNames = splitApks.keySet().toArray(splitNames);
    Arrays.sort(splitNames, sSplitNameComparator);

    for (int i = 0; i < size; i++) {
        final ApkLite apk = splitApks.get(splitNames[i]);
        splitCodePaths[i] = apk.getPath();
        splitRevisionCodes[i] = apk.getRevisionCode();
    }
}

排序依据是 split name,而不是目录枚举顺序。所有属性数组都在同一 i 下写入,后续 parseSplitApk(..., splitIndex) 才能用一个整数定位正确 APK 和依赖信息。

3.3 Split 名称 ​

java
String splitName = parser.getAttributeValue(null, "split");
if (splitName != null) {
    if (splitName.length() == 0) {
        splitName = null;
    } else {
        final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(
                input, splitName, false, false);
        if (nameResult.isError()) {
            return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
                    "Invalid manifest split: " + nameResult.getErrorMessage());
        }
    }
}
return input.success(Pair.create(packageName.intern(),
        splitName != null ? splitName.intern() : splitName));

空字符串 split 被当作 base;非空 split name 必须通过名称校验。这个规则发生在每个 ApkLite 的 Manifest 轻量解析中,因此组合阶段拿到的 key 已经是规范化结果。

4. 资源加载器 ​

4.1 普通模式 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/split/DefaultSplitAssetLoader.java

java
public AssetManager getBaseAssetManager() throws IllegalArgumentException {
    if (mCachedAssetManager != null) {
        return mCachedAssetManager;
    }

    ApkAssets[] apkAssets = new ApkAssets[(mSplitApkPaths != null
            ? mSplitApkPaths.length : 0) + 1];
    int splitIdx = 0;
    apkAssets[splitIdx++] = mBaseApkAssets = loadApkAssets(mBaseApkPath, mFlags);
    if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
        for (String apkPath : mSplitApkPaths) {
            apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
        }
    }

    AssetManager assets = new AssetManager();
    assets.setConfiguration(/* ... */, Build.VERSION.RESOURCES_SDK_INT_FULL);
    assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
    mCachedAssetManager = assets;
    return mCachedAssetManager;
}

public AssetManager getSplitAssetManager(int splitIdx) throws IllegalArgumentException {
    return getBaseAssetManager();
}

非 isolated 模式把 base 和全部 split 放进同一个 AssetManager;解析任意 split Manifest 时返回同一个资源管理器。getSplitAssetManager 的参数因此不会改变资源可见范围。

4.2 isolated 模式 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/split/SplitAssetDependencyLoader.java

java
public SplitAssetDependencyLoader(PackageLite pkg,
        SparseArray<int[]> dependencies, @ParseFlags int flags) {
    super(dependencies);
    // Base is inserted at index 0; split indices are shifted by one.
    mSplitPaths = new String[pkg.getSplitApkPaths().length + 1];
    mSplitPaths[0] = pkg.getBaseApkPath();
    System.arraycopy(pkg.getSplitApkPaths(), 0, mSplitPaths, 1,
            pkg.getSplitApkPaths().length);
    mFlags = flags;
    mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
    mCachedAssetManagers = new AssetManager[mSplitPaths.length];
}

protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
        int parentSplitIdx) throws IllegalArgumentException {
    final ArrayList<ApkAssets> assets = new ArrayList<>();
    if (parentSplitIdx >= 0) {
        Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
    }
    assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
    for (int configSplitIdx : configSplitIndices) {
        assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
    }
    mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
    mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(
            mCachedSplitApks[splitIdx]);
}

isolated 模式把父 split、当前 feature split 和它的 config split 组合成独立 AssetManager,并缓存构造结果。依赖数组由 SplitDependencyLoader 驱动;如果依赖图非法,ParsingPackageUtils 在创建 loader 时直接返回 bad manifest。

5. 完整解析 ​

5.1 Loader 与 Base ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java

java
final PackageLite lite = liteResult.getResult();
SparseArray<int[]> splitDependencies = null;
final SplitAssetLoader assetLoader;
if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {
    try {
        splitDependencies = SplitAssetDependencyLoader
                .createDependenciesFromPackage(lite);
        assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
    } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
        return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
    }
} else {
    assetLoader = new DefaultSplitAssetLoader(lite, flags);
}

final File baseApk = new File(lite.getBaseApkPath());
final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
        lite.getPath(), assetLoader, flags, shouldSkipComponents);
if (result.isError()) {
    return input.error(result);
}

loader 选择必须发生在 base Manifest 完整解析前,因为 base 的资源引用和 split 的 Manifest 都依赖 AssetManager。isolatedSplits=true 且确实存在 split 时才建立依赖树;没有 split 时仍使用普通 loader。

5.2 写入并解析 ​

java
ParsingPackage pkg = result.getResult();
if (!ArrayUtils.isEmpty(lite.getSplitNames())) {
    pkg.asSplit(lite.getSplitNames(), lite.getSplitApkPaths(),
            lite.getSplitRevisionCodes(), splitDependencies);
    final int num = lite.getSplitNames().length;
    for (int i = 0; i < num; i++) {
        final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
        final ParseResult<ParsingPackage> split =
                parseSplitApk(input, pkg, i, splitAssets, flags);
        if (split.isError()) {
            return input.error(split);
        }
    }
}
pkg.set32BitAbiPreferred(lite.isUse32bitAbi());
return input.success(pkg);

asSplit() 先把 split 名称、路径、revision code 和依赖树写入 package,并初始化每个 split 的 flags/class-loader 数组。随后循环索引与 PackageLite 相同的 split;任何一个 split 失败都会使整个 package 解析失败,不会返回“只有 base 的半成品”。

5.3 Split Manifest ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java

java
private ParseResult<ParsingPackage> parseSplitApk(ParseInput input,
        ParsingPackage pkg, int splitIndex, AssetManager assets, int flags) {
    final String apkPath = pkg.getSplitCodePaths()[splitIndex];
    final int cookie = assets.findCookieForPath(apkPath);
    if (cookie == 0) {
        return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                "Failed adding asset path: " + apkPath);
    }
    try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
            ANDROID_MANIFEST_FILENAME, false)) {
        Resources res = new Resources(assets, mDisplayMetrics, null);
        ParseResult<ParsingPackage> parseResult = parseSplitApk(
                input, pkg, res, parser, flags, splitIndex);
        if (parseResult.isError()) {
            return input.error(parseResult.getErrorCode(),
                    apkPath + " (at " + parser.getPositionDescription() + "): "
                            + parseResult.getErrorMessage());
        }
        return parseResult;
    } catch (Exception e) {
        return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to read manifest from " + apkPath, e);
    }
}

split 解析器先通过 AssetManager cookie 找到已经加载的 APK,再打开其 Manifest。错误信息会追加 split 的路径和 XML 位置,便于区分 base 失败还是某一个 split 失败。

6. Split 应用 ​

6.1 顶层 Application ​

java
boolean foundApp = false;
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
    if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) {
        continue;
    }
    final String tagName = parser.getName();
    if (TAG_APPLICATION.equals(tagName)) {
        if (foundApp) {
            if (RIGID_PARSER) {
                result = input.error("<manifest> has more than one <application>");
            } else {
                Slog.w(TAG, "<manifest> has more than one <application>");
                result = input.success(null);
            }
        } else {
            foundApp = true;
            result = parseSplitApplication(input, pkg, res, parser, flags, splitIndex);
        }
    } else {
        result = ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
    }
    if (result.isError()) {
        return input.error(result);
    }
}

split Manifest 的顶层只接受一个 <application>;其它顶层标签进入 unknownTag。没有 application 时通过 deferred error 处理,保持与完整 Manifest 解析的兼容错误模型。

6.2 hasCode 与类加载 ​

java
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
try {
    pkg.setSplitHasCode(splitIndex, sa.getBoolean(
            R.styleable.AndroidManifestApplication_hasCode, true));

    final String classLoaderName = sa.getString(
            R.styleable.AndroidManifestApplication_classLoader);
    if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
        pkg.setSplitClassLoaderName(splitIndex, classLoaderName);
    } else {
        return input.error("Invalid class loader name: " + classLoaderName);
    }
} finally {
    sa.recycle();
}

hasCode 和 classLoader 写入 ParsingPackage 的 split 数组,而不是覆盖 package 级字段。class loader 名称非法会立即失败;合法的 null 表示未显式指定。

6.3 组件默认 split ​

java
// If a component did not specify a split, inherit the split it is defined in.
String defaultSplitName = pkg.getSplitNames()[splitIndex];
...
if (isMainComponentTag(tagName)) {
    result = parseMainComponent(input, pkg, res, parser, tagName,
            defaultSplitName, flags);
} else {
    result = parseSplitBaseAppChildTags(input, tagName, pkg, res, parser);
}

split 中声明的 Activity、Service、Provider 等主组件,如果没有显式 split 属性,会继承当前 split name。这个默认值用于后续启动组件时选择正确的代码/资源 split;base 解析则传入 null。

7. 依赖与输出 ​

7.1 PackageImpl 状态 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/pkg/PackageImpl.java

java
private String[] splitClassLoaderNames;
private SparseArray<int[]> splitDependencies;
private int[] splitFlags;
private String[] splitNames;
private int[] splitRevisionCodes;

public PackageImpl asSplit(String[] splitNames, String[] splitCodePaths,
        int[] splitRevisionCodes, SparseArray<int[]> splitDependencies) {
    this.splitNames = splitNames;
    this.splitCodePaths = splitCodePaths;
    this.splitRevisionCodes = splitRevisionCodes;
    this.splitDependencies = splitDependencies;

    int count = splitNames.length;
    this.splitFlags = new int[count];
    this.splitClassLoaderNames = new String[count];
    return this;
}

asSplit() 是从轻量对象到完整对象的状态切换点:数组从 PackageLite 转移到 PackageImpl,并为每个 split 创建待填充的 flags/class-loader 槽位。它不解析 Manifest,也不构造 AssetManager。

7.2 PackageInfo 路径 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java

java
info.sourceDir = pkg.getBaseApkPath();
info.publicSourceDir = pkg.getBaseApkPath();
info.splitNames = pkg.getSplitNames();
info.splitSourceDirs = pkg.getSplitCodePaths().length == 0
        ? null : pkg.getSplitCodePaths();
info.splitPublicSourceDirs = pkg.getSplitCodePaths().length == 0
        ? null : pkg.getSplitCodePaths();
info.splitDependencies = pkg.getSplitDependencies().size() == 0
        ? null : pkg.getSplitDependencies();

对外 ApplicationInfo/相关信息使用 base path、split names、split code paths 和依赖树。没有 split 时这些数组字段以 null 形式暴露;内部 getter 则可以返回空数组或空 SparseArray。不要把对外 null 当成内部从未初始化。

7.3 AppInfo 字段 ​

java
appInfo.splitClassLoaderNames = splitClassLoaderNames;
appInfo.splitDependencies = (splitDependencies == null || splitDependencies.size() == 0)
        ? null : splitDependencies;
appInfo.splitNames = splitNames;
appInfo.setBaseCodePath(mBaseApkPath);
appInfo.setBaseResourcePath(mBaseApkPath);
appInfo.setCodePath(mPath);
appInfo.setResourcePath(mPath);
appInfo.setSplitCodePaths(ArrayUtils.size(splitCodePaths) == 0
        ? null : splitCodePaths);
appInfo.setSplitResourcePaths(ArrayUtils.size(splitCodePaths) == 0
        ? null : splitCodePaths);

这些字段由 package 对象生成 ApplicationInfo 时一次性复制。解析阶段已经确定 split 的路径和依赖;对外生成阶段只负责按 flags/state 组合信息,不会再次读取 split Manifest。

8. 状态、失败与清理 ​

8.1 状态机 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java、frameworks/base/core/java/com/android/internal/pm/split/DefaultSplitAssetLoader.java、frameworks/base/core/java/com/android/internal/pm/split/SplitAssetDependencyLoader.java

8.2 失败传播 ​

阶段典型条件结果
轻量枚举目录为空、缺 APKINSTALL_PARSE_FAILED_NOT_APK
一致性校验package/version 不一致bad manifest
split 唯一性同名 split 两次出现bad manifest
依赖树isolated split 依赖非法bad manifest,不创建完整包
资源加载APK 不是有效路径或无法打开IllegalArgumentException 转解析错误
split Manifestcookie 为 0、XML 错误、顶层标签非法带 split 路径的位置错误
class loader名称非法当前 split 立即失败

任一 split 失败都会使整个 parseClusterPackage() 返回 error。源码没有把成功的 base 和前序 split 作为可安装结果返回。

8.3 资源释放 ​

java
try {
    // parse base and all splits
    return input.success(pkg);
} catch (IllegalArgumentException e) {
    return input.error(e.getCause() instanceof IOException
            ? INSTALL_FAILED_INVALID_APK : INSTALL_PARSE_FAILED_NOT_APK,
            e.getMessage(), e);
} finally {
    IoUtils.closeQuietly(assetLoader);
}

无论成功还是失败,SplitAssetLoader 都在 finally 中关闭;isolated 模式还会逐个关闭缓存的 AssetManager。解析失败不会泄漏已经加载的 ApkAssets。

9. 测试与诊断 ​

9.1 输入与断言 ​

场景断言证明范围
单 base APKsplit names/paths 为空或 nullmonolithic 包形状
base + 一个 splitmSplitNames[0] 与 path/revision 对齐并行数组
目录中混入非 APK非 APK 被忽略枚举过滤
两个 package name返回 bad manifest一致性校验
两个相同 split name返回 bad manifest唯一性校验
缺少 base组合失败base 必需
isolated split 合法依赖split AssetManager 包含父/当前/config依赖加载
isolated split 非法依赖返回 bad manifest依赖错误
split classLoader 非法解析错误包含 class loadersplit application
任一 split XML 错误整个 package error原子失败
PackageInfoUtils 输出split paths/names/dependencies 对齐对外复制

测试不能只检查“目录解析成功”。至少还要断言索引对齐、依赖树、split flags/class loader 和失败时资源关闭,否则无法发现“数组错位”或“base 成功但 split 失败仍返回”的错误。

9.2 现场排查顺序 ​

  1. 先确认输入是单 APK 还是 cluster 目录,并列出目录中的 .apk 文件。
  2. 对每个 APK 检查 Manifest package、version 和 split name。
  3. 查看 PackageLite 的 split names 与 paths 是否按 split name 排序。
  4. 判断 isolatedSplits,再检查依赖树是否创建成功。
  5. 检查 base 是否先完成解析,之后失败的是哪个 splitIndex。
  6. 对 split 组件确认 defaultSplitName、hasCode 和 class loader 是否写入对应索引。
  7. 最后检查 ApplicationInfo 的 split paths/dependencies 是否只是 flags 过滤,而不是解析阶段丢失。

10. 源码路线 ​

建议按以下顺序阅读:

  1. ApkLiteParseUtils.parsePackageLite():单 APK 与 cluster 分流。
  2. parseClusterPackageLite():目录枚举、一致性和 split 唯一性。
  3. composePackageLiteFromApks():排序和并行数组构造。
  4. PackageLite:确认 base 继承字段和 split 坐标系。
  5. ParsingPackageUtils.parseClusterPackage():loader 选择、base/split 主循环。
  6. DefaultSplitAssetLoader:普通模式的单 AssetManager。
  7. SplitAssetDependencyLoader:isolated 模式的依赖 AssetManager。
  8. parseSplitApk() / parseSplitApplication():split Manifest 的限制和默认 split。
  9. PackageImpl.asSplit() 与 PackageInfoUtils:内部状态转移和对外路径生成。

11. 设计收束 ​

Split APK 解析的核心不是“把多个 APK 拼起来”,而是维护一套稳定坐标:

text
目录 APK
  -> ApkLite(每个文件的轻量 Manifest)
  -> PackageLite(按 splitName 排序的并行数组)
  -> AssetLoader(普通合并或 isolated 依赖树)
  -> base ParsingPackage
  -> asSplit(路径、revision、依赖、待填充 flags/classLoader)
  -> 逐 split Manifest
  -> ParsedPackage / ApplicationInfo

base 决定 package 身份和全局属性,split 提供按索引附加的代码、资源和组件。轻量阶段负责“能否组成一个合法 cluster”,完整阶段负责“每个 split 的资源和 Manifest 是否能加入同一个 package”,对外生成阶段才把这些数组暴露成 paths、names 和 dependencies。沿着这三个边界读源码,才能准确解释 split 缺失、依赖错误、资源不可见和组件启动时选错代码路径等问题。