fix(app-packages): 将 versionCode 类型从 Integer 改为 Long 并增强 AAB 解析
- 扩展 ApkMetaParser 对 AAB 文件的支持:解析 ABI 列表、权限列表,并优化高分辨率图标选择逻辑 - 修改 ApkMetaResult 和 AppPackageDocument 中 versionCode 字段类型为 Long - 更新 AppPackageServiceImpl 中对应的解析方法,适配 Long 类型 - 移除未使用的 import 语句,清理部分注释代码
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 zip(AAB/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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user