fix(app-packages): 将 versionCode 类型从 Integer 改为 Long 并增强 AAB 解析

- 扩展 ApkMetaParser 对 AAB 文件的支持:解析 ABI 列表、权限列表,并优化高分辨率图标选择逻辑
- 修改 ApkMetaResult 和 AppPackageDocument 中 versionCode 字段类型为 Long
- 更新 AppPackageServiceImpl 中对应的解析方法,适配 Long 类型
- 移除未使用的 import 语句,清理部分注释代码
This commit is contained in:
2026-08-12 22:28:43 +08:00
parent 6539878294
commit 6005c454c1
4 changed files with 107 additions and 29 deletions

View File

@@ -9,18 +9,14 @@ import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement;
import net.dongliu.apk.parser.ApkFile;
import net.dongliu.apk.parser.bean.ApkMeta;
import net.dongliu.apk.parser.bean.Icon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.function.Predicate;
import org.slf4j.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
@@ -94,6 +90,12 @@ public class ApkMetaParser {
// 1. 获取 Base Module (基础模块)
BundleModule baseModule = appBundle.getBaseModule();
if (baseModule.getNativeConfig().isPresent()) {
com.android.bundle.Files.NativeLibraries nativeLibraries = baseModule.getNativeConfig().get();
nativeLibraries.getDirectoryList().forEach(nativeDirectory -> {
apkMetaResult.getAbiList().add(nativeDirectory.getTargeting().getAbi().getAlias().name().toLowerCase(Locale.ROOT));
});
}
// 2. 获取 Manifest 信息
AndroidManifest manifest = baseModule.getAndroidManifest();
@@ -101,27 +103,37 @@ public class ApkMetaParser {
String packageName = manifest.getPackageName();
apkMetaResult.setPackageName(packageName);
int versionCode = manifest.getVersionCode().orElse(-1);
apkMetaResult.setVersionCode(versionCode);
apkMetaResult.setVersionCode(Long.valueOf(versionCode));
String versionName = manifest.getVersionName().orElse("");
apkMetaResult.setVersionName(versionName);
apkMetaResult.setMinSdk(manifest.getMinSdkVersion().orElse(-1));
apkMetaResult.setTargetSdk(manifest.getTargetSdkVersion().orElse(-1));
// 3.0 获取 ABI 列表:扫描 base 模块下 lib/<abi>/ 目录(含各拆分模块)
// apkMetaResult.setAbiList(extractAbiListFromZip(zipFile));
// 3. 获取 <manifest> 根节点
XmlProtoElement manifestElement = manifest.getManifestElement();
// 3.0 解析权限列表:遍历 <manifest> 根节点下的 <uses-permission android:name=".."/>
manifestElement.getChildrenElements("uses-permission")
.forEach(permElement -> permElement.getAttribute(ANDROID_NS, "name")
.map(XmlProtoAttribute::getValueAsString)
.filter(name -> name != null && !name.isBlank())
.ifPresent(apkMetaResult.getPermissions()::add));
// 3.1 解析 compileSdkVersion位于 <uses-sdk android:compileSdkVersion=".."/>
// XmlProtoElement usesSdkElement = manifestElement.getChildElement("uses-sdk");
// if (usesSdkElement != null) {
manifestElement.getAttribute(ANDROID_NS, "compileSdkVersion")
.map(XmlProtoAttribute::getValueAsInteger)
.ifPresent(apkMetaResult::setCompileSdk);
manifestElement.getAttribute(ANDROID_NS, "compileSdkVersion")
.map(XmlProtoAttribute::getValueAsInteger)
.ifPresent(apkMetaResult::setCompileSdk);
// }
// 3. 获取 <application> 节点
// 4. 获取 <application> 节点
XmlProtoElement appElement = manifestElement.getChildElement("application");
// 4. 解析应用名称 (android:label)
// 5. 解析应用名称 (android:label)
Optional<XmlProtoAttribute> labelOptional = appElement.getAttribute(ANDROID_NS, "label");
if (labelOptional.isPresent()) {
@@ -140,7 +152,7 @@ public class ApkMetaParser {
}
}
// 5. 解析应用图标:从 <application android:icon="@mipmap/.."> 资源引用解析
// 6. 解析应用图标:从 <application android:icon="@mipmap/.."> 资源引用解析
Optional<XmlProtoAttribute> iconOptional = appElement.getAttribute(ANDROID_NS, "icon");
if (iconOptional.isPresent()) {
XmlProtoAttribute iconAttr = iconOptional.get();
@@ -164,8 +176,6 @@ public class ApkMetaParser {
}
}
}
String appName = apkMetaResult.getAppLabel();
}
return apkMetaResult;
}
@@ -218,8 +228,9 @@ public class ApkMetaParser {
/**
* 从 AAB base 模块的资源表中,按资源 ID 解析文件资源路径(如 @mipmap/ic_launcher
* <p>
* 遍历 AAPT2 proto 资源表定位到目标条目后,取其第一个 FILE 类型的配置值,返回
* FileReference 的 path形如 res/mipmap-xxxhdpi/ic_launcher.png
* 遍历 AAPT2 proto 资源表定位到目标条目后,从所有 FILE 类型的配置值中选取 density
* 最大(即分辨率最高)的一个,返回其 FileReference 的 path形如
* res/mipmap-xxxhdpi/ic_launcher.png。若无 density 信息,则退回第一个 FILE 配置值。
*/
private static String resolveFileResource(BundleModule baseModule, int resId) {
try {
@@ -244,11 +255,37 @@ public class ApkMetaParser {
if (entry.getEntryId().getId() != entryId) {
continue;
}
String bestPath = null;
int bestDensity = -1;
boolean hasDensity = false;
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();
if (item == null
|| item.getValueCase() != com.android.aapt.Resources.Item.ValueCase.FILE) {
continue;
}
String path = item.getFile().getPath();
if (path == null || path.isBlank()) {
continue;
}
int density = cv.getConfig().getDensity();
if (density > 0) {
hasDensity = true;
if (density > bestDensity) {
bestDensity = density;
bestPath = path;
}
} else if (bestPath == null) {
// 无 density 信息的配置值,作为兜底
bestPath = path;
}
}
if (hasDensity && bestPath != null) {
return bestPath;
}
// 存在带 density 的配置时走上面的分支;否则返回无 density 的第一个兜底路径
if (bestPath != null) {
return bestPath;
}
}
}
@@ -603,7 +640,7 @@ public class ApkMetaParser {
result.setPackageName(meta.getPackageName());
result.setAppLabel(meta.getLabel());
result.setVersionName(meta.getVersionName());
result.setVersionCode(toInt(meta.getVersionCode()));
result.setVersionCode(toLong(meta.getVersionCode()));
result.setMinSdk(toInt(meta.getMinSdkVersion()));
result.setTargetSdk(toInt(meta.getTargetSdkVersion()));
result.setCompileSdk(toInt(meta.getCompileSdkVersion()));
@@ -640,7 +677,7 @@ public class ApkMetaParser {
result.setAppLabel(attr(manifestXml, "android:label"));
// 版本名 / 版本号
result.setVersionName(attr(manifestXml, "android:versionName"));
result.setVersionCode(toInt(attr(manifestXml, "android:versionCode")));
result.setVersionCode(toLong(attr(manifestXml, "android:versionCode")));
// SDK 版本:<uses-sdk android:minSdkVersion=".." android:targetSdkVersion=".." android:compileSdkVersion=".."/>
result.setMinSdk(toInt(attr(manifestXml, "android:minSdkVersion")));
result.setTargetSdk(toInt(attr(manifestXml, "android:targetSdkVersion")));
@@ -690,7 +727,7 @@ public class ApkMetaParser {
vc = jsonIntField(json, "version_code");
}
if (vc != null) {
result.setVersionCode(vc);
result.setVersionCode(vc.longValue());
}
}
}
@@ -753,6 +790,33 @@ public class ApkMetaParser {
return new ArrayList<>(abis);
}
/**
* 从 zipAAB/APKS中扫描 lib/<abi>/ 目录,提取支持的 CPU 架构列表。
* <p>
* AAB 内 native 库条目形如 base/lib/<abi>/*.so拆分模块为 feature/lib/<abi>/*.so
* 兼容 APKS 内直接位于 lib/<abi>/ 的条目。统一取每条路径中 lib/ 之后的第一个段作为 ABI。
*/
private static List<String> extractAbiListFromZip(ZipFile zipFile) {
Set<String> abis = new LinkedHashSet<>();
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
String libSegment = "/lib/";
int idx = name.indexOf(libSegment);
if (idx < 0) {
continue;
}
String afterLib = name.substring(idx + libSegment.length());
if (!afterLib.isEmpty()) {
String abi = afterLib.split("/")[0];
if (!abi.isBlank()) {
abis.add(abi);
}
}
}
return new ArrayList<>(abis);
}
private static Integer toInt(Object value) {
if (value == null) {
return null;
@@ -766,4 +830,18 @@ public class ApkMetaParser {
return null;
}
}
private static Long toLong(Object value) {
if (value == null) {
return null;
}
if (value instanceof Number) {
return ((Number) value).longValue();
}
try {
return Long.parseLong(value.toString().trim());
} catch (NumberFormatException e) {
return null;
}
}
}

View File

@@ -13,7 +13,7 @@ public class ApkMetaResult {
private String packageName;
private String appLabel;
private String versionName;
private Integer versionCode;
private Long versionCode;
private Integer minSdk;
private Integer targetSdk;
private Integer compileSdk;

View File

@@ -69,7 +69,7 @@ public class AppPackageDocument {
@Schema(description = "版本号versionCode")
@Field("version_code")
private Integer versionCode;
private Long versionCode;
@Schema(description = "最低SDK版本minSdkVersion")
@Field("min_sdk")

View File

@@ -187,7 +187,7 @@ public class AppPackageServiceImpl implements AppPackageService {
String packageName, String versionName, String versionCode) {
builder.packageName(packageName)
.versionName(versionName)
.versionCode(parseIntSafe(versionCode));
.versionCode(parseLongSafe(versionCode));
}
@Override
@@ -271,19 +271,19 @@ public class AppPackageServiceImpl implements AppPackageService {
return null;
}
private Integer orNull(Integer parsed, String fallback) {
private Long orNull(Long parsed, String fallback) {
if (parsed != null) {
return parsed;
}
return parseIntSafe(fallback);
return parseLongSafe(fallback);
}
private Integer parseIntSafe(String value) {
private Long parseLongSafe(String value) {
if (value == null || value.isBlank()) {
return null;
}
try {
return Integer.parseInt(value.trim());
return Long.parseLong(value.trim());
} catch (NumberFormatException e) {
return null;
}