6.7.0 - Alpha12 - 修复 APK Parser 库可能无法正常解析部分 APK 文件类型信息的问题
This commit is contained in:
@@ -6,6 +6,7 @@ import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.GET_META_DATA
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View.MeasureSpec.UNSPECIFIED
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
@@ -37,6 +38,8 @@ import java.io.File
|
||||
|
||||
object ApkInfoDialogManager {
|
||||
|
||||
private const val TAG = "ApkInfoDialogManager"
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@SuppressLint("SetTextI18n")
|
||||
@@ -179,7 +182,11 @@ object ApkInfoDialogManager {
|
||||
|
||||
private fun getApkInfo(apkFile: File): ApkInfo? = runCatching {
|
||||
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 packageName = meta?.packageName
|
||||
val minSdkVersion = meta?.minSdkVersion?.toIntOrNull()
|
||||
|
||||
@@ -47,6 +47,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.IllformedLocaleException;
|
||||
|
||||
/**
|
||||
* Common Apk Parser methods.
|
||||
@@ -332,9 +333,17 @@ public abstract class AbstractApkFile implements Closeable {
|
||||
}
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
final ResourceTableParser resourceTableParser = new ResourceTableParser(buffer);
|
||||
resourceTableParser.parse();
|
||||
this.resourceTable = resourceTableParser.getResourceTable();
|
||||
this.locales = resourceTableParser.locales;
|
||||
try {
|
||||
resourceTableParser.parse();
|
||||
this.resourceTable = resourceTableParser.getResourceTable();
|
||||
this.locales = resourceTableParser.locales;
|
||||
} catch (final IllformedLocaleException | IllegalArgumentException e) {
|
||||
// 容错回退: 当资源表中的语言/地区字段不合法或解析实现不兼容时,
|
||||
// 退化为无资源表模式,允许后续 Manifest 解析继续进行。
|
||||
this.resourceTable = new ResourceTable(null);
|
||||
this.locales = Collections.emptySet();
|
||||
// 如需记录日志,可在此处接入项目日志系统;为保持通用性,此处不抛出异常
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,6 @@ package net.dongliu.apk.parser.struct.resource;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.dongliu.apk.parser.struct.ResourceValue;
|
||||
import net.dongliu.apk.parser.struct.StringPool;
|
||||
import net.dongliu.apk.parser.utils.Buffers;
|
||||
@@ -15,32 +14,90 @@ import java.util.Locale;
|
||||
* @author dongliu
|
||||
*/
|
||||
public class Type {
|
||||
private String name;
|
||||
public final short id;
|
||||
|
||||
@NonNull
|
||||
public final Locale locale;
|
||||
|
||||
/**
|
||||
* see Densities.java for values
|
||||
*/
|
||||
public final int density;
|
||||
private String name;
|
||||
private StringPool keyStringPool;
|
||||
private ByteBuffer buffer;
|
||||
private long[] offsets;
|
||||
private StringPool stringPool;
|
||||
|
||||
/**
|
||||
* see Densities.java for values
|
||||
*/
|
||||
public final int density;
|
||||
|
||||
public Type(final @NonNull TypeHeader header) {
|
||||
this.id = header.getId();
|
||||
final ResTableConfig config = header.config;
|
||||
this.locale = new Locale.Builder()
|
||||
.setLanguage(config.getLanguage())
|
||||
.setRegion(config.getCountry())
|
||||
.build();
|
||||
|
||||
// Normalize and handle language/region safely to avoid IllformedLocaleException.
|
||||
// zh-CN: 规范化并容错处理 language/region, 避免 IllformedLocaleException.
|
||||
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();
|
||||
}
|
||||
|
||||
// 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
|
||||
public ResourceEntry getResourceEntry(final int resId) {
|
||||
if (resId >= this.offsets.length) {
|
||||
@@ -49,8 +106,8 @@ public class Type {
|
||||
if (this.offsets[resId] == TypeHeader.NO_ENTRY) {
|
||||
return null;
|
||||
}
|
||||
if( offsets[resId] >= buffer.limit() ) {
|
||||
//System.out.println( "invalid offset: " + offsets[resId] );
|
||||
if (offsets[resId] >= buffer.limit()) {
|
||||
// System.out.println( "invalid offset: " + offsets[resId] );
|
||||
return null;
|
||||
}
|
||||
// read Resource Entries
|
||||
@@ -60,7 +117,7 @@ public class Type {
|
||||
|
||||
private ResourceEntry readResourceEntry() {
|
||||
long beginPos = buffer.position();
|
||||
// ResourceEntry resourceEntry = new ResourceEntry();
|
||||
// ResourceEntry resourceEntry = new ResourceEntry();
|
||||
// size is always 8(simple), or 16(complex)
|
||||
final int size = Buffers.readUShort(buffer);
|
||||
final int flags = Buffers.readUShort(buffer);
|
||||
@@ -75,22 +132,22 @@ public class Type {
|
||||
|
||||
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];
|
||||
for (int i = 0; i < count; i++) {
|
||||
resourceTableMaps[i] = readResourceTableMap();
|
||||
}
|
||||
// ResourceEntry resourceEntry = new ResourceEntry(size,flags,key,resourceTableMaps);
|
||||
ResourceMapEntry resourceMapEntry = new ResourceMapEntry(size,flags,key,parent,count,resourceTableMaps);
|
||||
// ResourceEntry resourceEntry = new ResourceEntry(size, flags, key, resourceTableMaps);
|
||||
ResourceMapEntry resourceMapEntry = new ResourceMapEntry(size, flags, key, parent, count, resourceTableMaps);
|
||||
return resourceMapEntry;
|
||||
} else if ((flags & ResourceEntry.FLAG_COMPACT) != 0) {
|
||||
final ResourceValue value = ResourceValue.string((int) keyRef, stringPool);
|
||||
return new ResourceEntry(size,flags,null,value);
|
||||
return new ResourceEntry(size, flags, null, value);
|
||||
} else {
|
||||
String key = keyStringPool.get((int) keyRef);
|
||||
Buffers.position(buffer, beginPos + size);
|
||||
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();
|
||||
resourceTableMap.setNameRef(Buffers.readUInt(this.buffer));
|
||||
resourceTableMap.setResValue(ParseUtils.readResValue(this.buffer, this.stringPool));
|
||||
//noinspection StatementWithEmptyBody
|
||||
// noinspection StatementWithEmptyBody
|
||||
if ((resourceTableMap.getNameRef() & 0x02000000) != 0) {
|
||||
//read arrays
|
||||
} else //noinspection StatementWithEmptyBody
|
||||
// read arrays
|
||||
} else // noinspection StatementWithEmptyBody
|
||||
if ((resourceTableMap.getNameRef() & 0x01000000) != 0) {
|
||||
// read attrs
|
||||
} else {
|
||||
@@ -145,9 +202,9 @@ public class Type {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Type{" +
|
||||
"name='" + this.name + '\'' +
|
||||
", id=" + this.id +
|
||||
", locale=" + this.locale +
|
||||
'}';
|
||||
"name='" + this.name + '\'' +
|
||||
", id=" + this.id +
|
||||
", locale=" + this.locale +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#Tue Dec 09 11:33:43 CST 2025
|
||||
BUILD_TIME=1765251223946
|
||||
#Tue Dec 09 12:09:37 CST 2025
|
||||
BUILD_TIME=1765253377144
|
||||
COMPILE_SDK_VERSION=36
|
||||
IMAGE_QUANT_CMAKE_VERSION=3.22.1
|
||||
IMAGE_QUANT_NDK_VERSION=26.1.10909125
|
||||
|
||||
Reference in New Issue
Block a user