iconOptional = appElement.getAttribute(ANDROID_NS, "icon");
+ if (iconOptional.isPresent()) {
+ XmlProtoAttribute iconAttr = iconOptional.get();
+ if (iconAttr.hasRefIdValue()) {
+ // 资源引用,如 @mipmap/ic_launcher,先从资源表解析出资源文件路径
+ int iconResId = iconAttr.getValueAsRefId();
+ String iconPath = resolveFileResource(baseModule, iconResId);
+ if (iconPath != null) {
+ // 资源表返回的路径以 res/ 开头,AAB 内实际位于 base/ 前缀下
+ String entryName = "base/" + iconPath;
+ ZipEntry iconEntry = zipFile.getEntry(entryName);
+ if (iconEntry != null && !iconEntry.isDirectory()) {
+ try (InputStream in = zipFile.getInputStream(iconEntry)) {
+ byte[] data = in.readAllBytes();
+ if (data != null && data.length > 0) {
+ apkMetaResult.setIconPath(iconPath);
+ apkMetaResult.setIconBytes(data);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ String appName = apkMetaResult.getAppLabel();
+ }
+ return apkMetaResult;
}
+ /**
+ * 从 AAB base 模块的资源表中,按资源 ID 解析字符串资源(如 @string/app_name)。
+ *
+ * 遍历 AAPT2 proto 资源表的 package/type/entry 三层结构,用资源 ID 的高 8 位
+ * (packageId)、次 8 位 (typeId)、低 16 位 (entryId) 定位到目标条目,取其第一个
+ * STR 类型的配置值。
+ */
+ private static String resolveStringResource(BundleModule baseModule, int resId) {
+ try {
+ Optional tableOpt =
+ baseModule.getResourceTable();
+ if (!tableOpt.isPresent()) {
+ return null;
+ }
+ com.android.aapt.Resources.ResourceTable table = tableOpt.get();
+ int packageId = (resId >>> 24) & 0xFF;
+ int typeId = (resId >>> 16) & 0xFF;
+ int entryId = resId & 0xFFFF;
+ for (com.android.aapt.Resources.Package pkg : table.getPackageList()) {
+ if (pkg.getPackageId().getId() != packageId) {
+ continue;
+ }
+ for (com.android.aapt.Resources.Type type : pkg.getTypeList()) {
+ if (type.getTypeId().getId() != typeId) {
+ continue;
+ }
+ for (com.android.aapt.Resources.Entry entry : type.getEntryList()) {
+ if (entry.getEntryId().getId() != entryId) {
+ continue;
+ }
+ for (com.android.aapt.Resources.ConfigValue cv : entry.getConfigValueList()) {
+ com.android.aapt.Resources.Item item = cv.getValue().getItem();
+ if (item != null && item.getValueCase() == com.android.aapt.Resources.Item.ValueCase.STR) {
+ return item.getStr().getValue();
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception ignored) {
+ // 资源解析失败不影响其它字段
+ }
+ return null;
+ }
+
+ /**
+ * 从 AAB base 模块的资源表中,按资源 ID 解析文件资源路径(如 @mipmap/ic_launcher)。
+ *
+ * 遍历 AAPT2 proto 资源表定位到目标条目后,取其第一个 FILE 类型的配置值,返回
+ * FileReference 的 path(形如 res/mipmap-xxxhdpi/ic_launcher.png)。
+ */
+ private static String resolveFileResource(BundleModule baseModule, int resId) {
+ try {
+ Optional tableOpt =
+ baseModule.getResourceTable();
+ if (!tableOpt.isPresent()) {
+ return null;
+ }
+ com.android.aapt.Resources.ResourceTable table = tableOpt.get();
+ int packageId = (resId >>> 24) & 0xFF;
+ int typeId = (resId >>> 16) & 0xFF;
+ int entryId = resId & 0xFFFF;
+ for (com.android.aapt.Resources.Package pkg : table.getPackageList()) {
+ if (pkg.getPackageId().getId() != packageId) {
+ continue;
+ }
+ for (com.android.aapt.Resources.Type type : pkg.getTypeList()) {
+ if (type.getTypeId().getId() != typeId) {
+ continue;
+ }
+ for (com.android.aapt.Resources.Entry entry : type.getEntryList()) {
+ if (entry.getEntryId().getId() != entryId) {
+ continue;
+ }
+ for (com.android.aapt.Resources.ConfigValue cv : entry.getConfigValueList()) {
+ com.android.aapt.Resources.Item item = cv.getValue().getItem();
+ if (item != null && item.getValueCase() == com.android.aapt.Resources.Item.ValueCase.FILE) {
+ return item.getFile().getPath();
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception ignored) {
+ // 资源解析失败不影响其它字段
+ }
+ return null;
+ }
+
+
// ============================ APKS ============================
/**
@@ -395,11 +566,13 @@ public class ApkMetaParser {
return (isMipmap ? 1000 : 0) + density;
}
- private record IconCandidate(String name, int rank, byte[] data) {}
+ private record IconCandidate(String name, int rank, byte[] data) {
+ }
// ============================ 通用工具 ============================
- private record ContainerEntry(String name, byte[] data) {}
+ private record ContainerEntry(String name, byte[] data) {
+ }
/**
* 从字节数组解析 APK(用于容器内的主 APK)。