6.7.0 - Alpha12 - 修复 APK Parser 库可能无法正常解析部分 APK 文件类型信息的问题

This commit is contained in:
SuperMonster003
2025-12-09 12:12:26 +08:00
parent d3a6212f99
commit b4a87e6639
4 changed files with 107 additions and 34 deletions

View File

@@ -6,6 +6,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_META_DATA import android.content.pm.PackageManager.GET_META_DATA
import android.os.Build import android.os.Build
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View.MeasureSpec.UNSPECIFIED import android.view.View.MeasureSpec.UNSPECIFIED
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
@@ -37,6 +38,8 @@ import java.io.File
object ApkInfoDialogManager { object ApkInfoDialogManager {
private const val TAG = "ApkInfoDialogManager"
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@@ -179,7 +182,11 @@ object ApkInfoDialogManager {
private fun getApkInfo(apkFile: File): ApkInfo? = runCatching { private fun getApkInfo(apkFile: File): ApkInfo? = runCatching {
ApkFile(apkFile).use { parser -> ApkFile(apkFile).use { parser ->
val meta = runCatching { parser.apkMeta }.getOrNull() val meta = runCatching { parser.apkMeta }
.onFailure {
Log.d(TAG, "Failed to parse apk meta: ${apkFile.absolutePath}", it)
}
.getOrNull()
val label = meta?.label val label = meta?.label
val packageName = meta?.packageName val packageName = meta?.packageName
val minSdkVersion = meta?.minSdkVersion?.toIntOrNull() val minSdkVersion = meta?.minSdkVersion?.toIntOrNull()

View File

@@ -47,6 +47,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.IllformedLocaleException;
/** /**
* Common Apk Parser methods. * Common Apk Parser methods.
@@ -332,9 +333,17 @@ public abstract class AbstractApkFile implements Closeable {
} }
final ByteBuffer buffer = ByteBuffer.wrap(data); final ByteBuffer buffer = ByteBuffer.wrap(data);
final ResourceTableParser resourceTableParser = new ResourceTableParser(buffer); final ResourceTableParser resourceTableParser = new ResourceTableParser(buffer);
resourceTableParser.parse(); try {
this.resourceTable = resourceTableParser.getResourceTable(); resourceTableParser.parse();
this.locales = resourceTableParser.locales; this.resourceTable = resourceTableParser.getResourceTable();
this.locales = resourceTableParser.locales;
} catch (final IllformedLocaleException | IllegalArgumentException e) {
// 容错回退: 当资源表中的语言/地区字段不合法或解析实现不兼容时,
// 退化为无资源表模式,允许后续 Manifest 解析继续进行。
this.resourceTable = new ResourceTable(null);
this.locales = Collections.emptySet();
// 如需记录日志,可在此处接入项目日志系统;为保持通用性,此处不抛出异常
}
} }
/** /**

View File

@@ -2,7 +2,6 @@ package net.dongliu.apk.parser.struct.resource;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import net.dongliu.apk.parser.struct.ResourceValue; import net.dongliu.apk.parser.struct.ResourceValue;
import net.dongliu.apk.parser.struct.StringPool; import net.dongliu.apk.parser.struct.StringPool;
import net.dongliu.apk.parser.utils.Buffers; import net.dongliu.apk.parser.utils.Buffers;
@@ -15,32 +14,90 @@ import java.util.Locale;
* @author dongliu * @author dongliu
*/ */
public class Type { public class Type {
private String name;
public final short id; public final short id;
@NonNull @NonNull
public final Locale locale; public final Locale locale;
/**
* see Densities.java for values
*/
public final int density;
private String name;
private StringPool keyStringPool; private StringPool keyStringPool;
private ByteBuffer buffer; private ByteBuffer buffer;
private long[] offsets; private long[] offsets;
private StringPool stringPool; private StringPool stringPool;
/**
* see Densities.java for values
*/
public final int density;
public Type(final @NonNull TypeHeader header) { public Type(final @NonNull TypeHeader header) {
this.id = header.getId(); this.id = header.getId();
final ResTableConfig config = header.config; final ResTableConfig config = header.config;
this.locale = new Locale.Builder()
.setLanguage(config.getLanguage()) // Normalize and handle language/region safely to avoid IllformedLocaleException.
.setRegion(config.getCountry()) // zh-CN: 规范化并容错处理 language/region, 避免 IllformedLocaleException.
.build(); final String rawLang = config.getLanguage();
final String rawRegion = config.getCountry();
final String lang = sanitizeLanguage(rawLang);
final String region = sanitizeRegion(rawRegion);
Locale tmpLocale;
try {
final Locale.Builder builder = new Locale.Builder();
if (lang != null) {
builder.setLanguage(lang);
}
if (region != null) {
builder.setRegion(region);
}
tmpLocale = builder.build();
} catch (final RuntimeException e) {
// Handle all invalid data including IllformedLocaleException.
// zh-CN: 包含 IllformedLocaleException 在内的所有非法数据的兜底.
if (lang != null) {
// At least keep valid language.
// zh-CN: 至少保留合法 language.
tmpLocale = new Locale(lang);
} else {
tmpLocale = Locale.ROOT;
}
}
this.locale = tmpLocale;
this.density = config.getDensity(); this.density = config.getDensity();
} }
// Only allows 2 or 3 letter characters (BCP-47 common language subtags).
// zh-CN: 仅允许 2 或 3 位字母 (BCP-47 常见语言子标签).
private static String sanitizeLanguage(@Nullable String lang) {
if (lang == null) return null;
lang = lang.trim();
if (lang.isEmpty()) return null;
// Filter out non-letter characters.
// zh-CN: 过滤非字母字符.
final String cleaned = lang.replaceAll("[^A-Za-z]", "");
if (cleaned.length() == 2 || cleaned.length() == 3) {
return cleaned.toLowerCase(Locale.ROOT);
}
return null;
}
// Only allows 2 letter or 3 digit characters (e.g. CN/US/419).
// zh-CN: 仅允许 2 位字母或 3 位数字 (如 CN/US/419).
private static String sanitizeRegion(@Nullable String region) {
if (region == null) return null;
region = region.trim();
if (region.isEmpty()) return null;
final String letters = region.replaceAll("[^A-Za-z]", "");
if (letters.length() == 2) {
return letters.toUpperCase(Locale.ROOT);
}
final String digits = region.replaceAll("[^0-9]", "");
if (digits.length() == 3) {
return digits;
}
return null;
}
@Nullable @Nullable
public ResourceEntry getResourceEntry(final int resId) { public ResourceEntry getResourceEntry(final int resId) {
if (resId >= this.offsets.length) { if (resId >= this.offsets.length) {
@@ -49,8 +106,8 @@ public class Type {
if (this.offsets[resId] == TypeHeader.NO_ENTRY) { if (this.offsets[resId] == TypeHeader.NO_ENTRY) {
return null; return null;
} }
if( offsets[resId] >= buffer.limit() ) { if (offsets[resId] >= buffer.limit()) {
//System.out.println( "invalid offset: " + offsets[resId] ); // System.out.println( "invalid offset: " + offsets[resId] );
return null; return null;
} }
// read Resource Entries // read Resource Entries
@@ -60,7 +117,7 @@ public class Type {
private ResourceEntry readResourceEntry() { private ResourceEntry readResourceEntry() {
long beginPos = buffer.position(); long beginPos = buffer.position();
// ResourceEntry resourceEntry = new ResourceEntry(); // ResourceEntry resourceEntry = new ResourceEntry();
// size is always 8(simple), or 16(complex) // size is always 8(simple), or 16(complex)
final int size = Buffers.readUShort(buffer); final int size = Buffers.readUShort(buffer);
final int flags = Buffers.readUShort(buffer); final int flags = Buffers.readUShort(buffer);
@@ -75,22 +132,22 @@ public class Type {
Buffers.position(buffer, beginPos + size); Buffers.position(buffer, beginPos + size);
//An individual complex Resource entry comprises an entry immediately followed by one or more fields. // An individual complex Resource entry comprises an entry immediately followed by one or more fields.
ResourceTableMap[] resourceTableMaps = new ResourceTableMap[(int) count]; ResourceTableMap[] resourceTableMaps = new ResourceTableMap[(int) count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
resourceTableMaps[i] = readResourceTableMap(); resourceTableMaps[i] = readResourceTableMap();
} }
// ResourceEntry resourceEntry = new ResourceEntry(size,flags,key,resourceTableMaps); // ResourceEntry resourceEntry = new ResourceEntry(size, flags, key, resourceTableMaps);
ResourceMapEntry resourceMapEntry = new ResourceMapEntry(size,flags,key,parent,count,resourceTableMaps); ResourceMapEntry resourceMapEntry = new ResourceMapEntry(size, flags, key, parent, count, resourceTableMaps);
return resourceMapEntry; return resourceMapEntry;
} else if ((flags & ResourceEntry.FLAG_COMPACT) != 0) { } else if ((flags & ResourceEntry.FLAG_COMPACT) != 0) {
final ResourceValue value = ResourceValue.string((int) keyRef, stringPool); final ResourceValue value = ResourceValue.string((int) keyRef, stringPool);
return new ResourceEntry(size,flags,null,value); return new ResourceEntry(size, flags, null, value);
} else { } else {
String key = keyStringPool.get((int) keyRef); String key = keyStringPool.get((int) keyRef);
Buffers.position(buffer, beginPos + size); Buffers.position(buffer, beginPos + size);
final ResourceValue value = ParseUtils.readResValue(buffer, stringPool); final ResourceValue value = ParseUtils.readResValue(buffer, stringPool);
return new ResourceEntry(size,flags,key,value); return new ResourceEntry(size, flags, key, value);
} }
} }
@@ -98,10 +155,10 @@ public class Type {
final ResourceTableMap resourceTableMap = new ResourceTableMap(); final ResourceTableMap resourceTableMap = new ResourceTableMap();
resourceTableMap.setNameRef(Buffers.readUInt(this.buffer)); resourceTableMap.setNameRef(Buffers.readUInt(this.buffer));
resourceTableMap.setResValue(ParseUtils.readResValue(this.buffer, this.stringPool)); resourceTableMap.setResValue(ParseUtils.readResValue(this.buffer, this.stringPool));
//noinspection StatementWithEmptyBody // noinspection StatementWithEmptyBody
if ((resourceTableMap.getNameRef() & 0x02000000) != 0) { if ((resourceTableMap.getNameRef() & 0x02000000) != 0) {
//read arrays // read arrays
} else //noinspection StatementWithEmptyBody } else // noinspection StatementWithEmptyBody
if ((resourceTableMap.getNameRef() & 0x01000000) != 0) { if ((resourceTableMap.getNameRef() & 0x01000000) != 0) {
// read attrs // read attrs
} else { } else {
@@ -145,9 +202,9 @@ public class Type {
@Override @Override
public String toString() { public String toString() {
return "Type{" + return "Type{" +
"name='" + this.name + '\'' + "name='" + this.name + '\'' +
", id=" + this.id + ", id=" + this.id +
", locale=" + this.locale + ", locale=" + this.locale +
'}'; '}';
} }
} }

View File

@@ -1,5 +1,5 @@
#Tue Dec 09 11:33:43 CST 2025 #Tue Dec 09 12:09:37 CST 2025
BUILD_TIME=1765251223946 BUILD_TIME=1765253377144
COMPILE_SDK_VERSION=36 COMPILE_SDK_VERSION=36
IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_CMAKE_VERSION=3.22.1
IMAGE_QUANT_NDK_VERSION=26.1.10909125 IMAGE_QUANT_NDK_VERSION=26.1.10909125