@@ -1,8 +1,6 @@
package com.youlai.boot.app.packages.component ;
import cn.hutool.json.JSONArray ;
import cn.hutool.json.JSONObject ;
import cn.hutool.json.JSONUtil ;
import com.android.aapt.Resources ;
import com.android.tools.build.bundletool.commands.DumpCommand ;
import com.android.tools.build.bundletool.model.AndroidManifest ;
import com.android.tools.build.bundletool.model.AppBundle ;
@@ -11,6 +9,8 @@ import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute
import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement ;
import com.fasterxml.jackson.databind.ObjectMapper ;
import com.youlai.boot.app.packages.model.dto.ApkmJsonInfo ;
import com.youlai.boot.app.packages.model.dto.SplitApks ;
import com.youlai.boot.app.packages.model.dto.XapkJsonInfo ;
import net.dongliu.apk.parser.ApkFile ;
import net.dongliu.apk.parser.bean.ApkMeta ;
import net.dongliu.apk.parser.bean.Icon ;
@@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files ;
import java.util.* ;
import java.util.function.Predicate ;
import java.util.stream.Collectors ;
import java.util.zip.ZipEntry ;
import java.util.zip.ZipFile ;
import java.util.zip.ZipInputStream ;
@@ -44,7 +45,8 @@ import java.util.zip.ZipInputStream;
*/
public class ApkMetaParser {
private static final String ANDROID_NS = " http://schemas.android.com/apk/res/android " ;
private Logger logger = LoggerFactory . getLogger ( ApkMetaParser . class ) ;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper ( ) ;
private static final Logger LOGGER = LoggerFactory . getLogger ( ApkMetaParser . class ) ;
/**
* 统一解析入口:根据文件扩展名分发到对应解析策略。
@@ -189,6 +191,32 @@ public class ApkMetaParser {
return apkMetaResult ;
}
/**
* 在资源表中按资源 ID( packageId/typeId/entryId) 定位到目标条目。
*/
private static Resources . Entry findResourceEntry (
Resources . ResourceTable table , int resId ) {
int packageId = ( resId > > > 24 ) & 0xFF ;
int typeId = ( resId > > > 16 ) & 0xFF ;
int entryId = resId & 0xFFFF ;
for ( Resources . Package pkg : table . getPackageList ( ) ) {
if ( pkg . getPackageId ( ) . getId ( ) ! = packageId ) {
continue ;
}
for ( Resources . Type type : pkg . getTypeList ( ) ) {
if ( type . getTypeId ( ) . getId ( ) ! = typeId ) {
continue ;
}
for ( Resources . Entry entry : type . getEntryList ( ) ) {
if ( entry . getEntryId ( ) . getId ( ) = = entryId ) {
return entry ;
}
}
}
}
return null ;
}
/**
* 从 AAB base 模块的资源表中,按资源 ID 解析字符串资源(如 @string/app_name) 。
* <p>
@@ -198,34 +226,20 @@ public class ApkMetaParser {
*/
private static String resolveStringResource ( BundleModule baseModule , int resId ) {
try {
Optional < com . android . aapt . Resources. ResourceTable > tableOpt =
Optional < Resources . ResourceTable > 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 ( ) ;
}
}
}
Resources . ResourceTable table = tableOpt . get ( ) ;
Resources . Entry entry = findResourceEntry ( table , resId ) ;
if ( entry = = null ) {
return null ;
}
for ( Resources . ConfigValue cv : entry . getConfigValueList ( ) ) {
Resources . Item item = cv . getValue ( ) . getItem ( ) ;
if ( item ! = null & & item . getValueCase ( ) = = Resources . Item . ValueCase . STR ) {
return item . getStr ( ) . getValue ( ) ;
}
}
} catch ( Exception ignored ) {
@@ -243,61 +257,47 @@ public class ApkMetaParser {
*/
private static String resolveFileResource ( BundleModule baseModule , int resId ) {
try {
Optional < com . android . aapt . Resources. ResourceTable > tableOpt =
Optional < Resources . ResourceTable > 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 ) {
Resources . ResourceTable table = tableOpt . get ( ) ;
Resources . Entry entry = findResourceEntry ( table , resId ) ;
if ( entry = = null ) {
return null ;
}
String bestPath = null ;
int bestDensity = - 1 ;
boolean hasDensity = false ;
for ( Resources . ConfigValue cv : entry . getConfigValueList ( ) ) {
Resources . Item item = cv . getValue ( ) . getItem ( ) ;
if ( item = = null
| | item . getValueCase ( ) ! = Resources . Item . ValueCase . FILE ) {
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 ;
}
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 ) {
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 ;
}
}
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 ;
}
} catch ( Exception ignored ) {
// 资源解析失败不影响其它字段
@@ -355,113 +355,70 @@ public class ApkMetaParser {
//以上3个应该都存在,否则解析失败
byte [ ] infoData ;
byte [ ] iconBytes ;
ApkmContent content = readApkmContent ( apkmFile ) ;
byte [ ] infoData = content . infoJson ;
List < ContainerEntry > apks = content . apks ;
List < String > splitApkList = apks . stream ( ) . map ( apk - > apk . name ) . collect ( Collectors . toList ( ) ) ;
apkMetaResult . setSplitApkList ( splitApkList ) ;
if ( infoExists ) {
infoData = ZipUtil . unpackEntry ( apkmFile , " info.json " ) ;
ObjectMapper objectMapper = new ObjectMapper ( ) ;
A pkmJsonInfo info = objectMapper . readValue ( infoData , ApkmJsonInfo . class ) ;
apkMetaResult . setAppLabel ( info . getApp_nam e ( ) ) ;
apkMetaResult . setPackageName ( info . getPname ( ) ) ;
apkMetaResult . setVersionName ( info . getRelease_version ( ) ) ;
apkMetaResult . setVersionCode ( info . getVersioncode ( ) ) ;
apkMetaResult . setMinSdk ( info . getMin_api ( ) ) ;
apkMetaResult . setAbiList ( info . getArches ( ) ) ;
ApkmJsonInfo info = OBJECT_MAPPER . readValue ( infoData , ApkmJsonInfo . class ) ;
apkMetaResult . setAppLabel ( info . getApp_name ( ) ) ;
apkMetaResult . setPackageName ( info . getPname ( ) ) ;
a pkMetaResult . setVersionName ( info . getRelease_version ( ) ) ;
apkMetaResult . setVersionCode ( info . getVersioncod e ( ) ) ;
apkMetaResult . setMinSdk ( info . getMin_api ( ) ) ;
apkMetaResult . setAbiList ( info . getArches ( ) ) ;
//info.json 无法获取到 targetSdk,compileSdk,permissions,iconPath
//先加载icon.png兜底,后续再加载base.apk,有些base.apk无法获取到图标
iconBytes = ZipUtil . unpackEntry ( apkmFile , " icon.png " ) ;
apkMetaResult . setIconBytes ( iconBytes ) ;
apkMetaResult . setIconPath ( " icon.png " ) ;
//info.json 无法获取到 targetSdk,compileSdk,permissions,iconPath
//先加载icon.png兜底,后续再加载base.apk,有些base.apk无法获取到图标
byte [ ] iconBytes = ZipUtil . unpackEntry ( apkmFile , " icon.png " ) ;
apkMetaResult . setIconPath ( " icon.png " ) ;
apkMetaResult . setIconBytes ( iconBytes ) ;
byte [ ] baseApkbytes = ZipUtil . unpackEntry ( apkmFile , " base.apk " ) ;
ApkMetaResult result = tryParseApkBytes ( baseApkbytes ) ;
if ( result ! = null ) {
if ( ! Objects . equals ( apkMetaResult . getAppLabel ( ) , result . getAppLabel ( ) ) ) {
apkMetaResult . setAppLabel ( result . getAppLabel ( ) ) ;
}
if ( ! Objects . equals ( apkMetaResult . getPackageName ( ) , result . getPackageName ( ) ) ) {
apkMetaResult . setPackageName ( result . getPackageName ( ) ) ;
}
if ( ! Objects . equals ( apkMetaResult . getVersionName ( ) , result . getVersionName ( ) ) ) {
apkMetaResult . setVersionName ( result . getVersionName ( ) ) ;
}
if ( ! Objects . equals ( apkMetaResult . getVersionCode ( ) , result . getVersionCode ( ) ) ) {
apkMetaResult . setVersionCode ( result . getVersionCode ( ) ) ;
}
byte [ ] baseApkbytes = ZipUtil . unpackEntry ( apkmFile , " base.apk " ) ;
ApkMetaResult result = tryParseApkBytes ( baseApkbytes ) ;
//这三个属性无法从info.json中获取,只能从base.apk中获取
apkMetaResult . s etTargetSdk ( result . getTargetSdk ( ) ) ;
apkMetaResult . setCompileSdk ( result . getCompileSdk ( ) ) ;
apkMetaResult . setPermissions ( result . getPermissions ( ) ) ;
if ( result . getAbiList ( ) ! = null & & ! result . getAbiList ( ) . isEmpty ( ) ) {
apkMetaResult . setAbiList ( result . getAbiList ( ) ) ;
}
if ( r esult. g etSplitApkList ( ) ! = null & & ! result . getSplitApkList ( ) . isEmpty ( ) ) {
apkMetaResult . setSplitApkList ( result . getSplitApkList ( ) ) ;
}
if ( StringUtils . isNotBlank ( result . getIconPath ( ) ) ) {
apkMetaResult . setIconPath ( result . getIconPath ( ) ) ;
}
if ( result . getIconBytes ( ) ! = null ) {
apkMetaResult . setIconBytes ( result . getIconBytes ( ) ) ;
}
if ( result ! = null ) {
if ( ! Objects . equals ( apkMetaResult . g etAppLabel ( ) , result . getAppLabel ( ) ) ) {
apkMetaResult . setAppLabel ( result . getAppLabel ( ) ) ;
}
if ( ! Objects . equals ( apkMetaResult . getPackageName ( ) , result . getPackageName ( ) ) ) {
apkMetaResult . setPackageName ( result . getPackageName ( ) ) ;
}
if ( ! Objects . equals ( apkMetaResult . getVersionName ( ) , result . getVersionName ( ) ) ) {
apkMetaR esult. s etVersionName ( result . getVersionName ( ) ) ;
}
if ( ! Objects . equals ( apkMetaResult . getVersionCode ( ) , result . getVersionCode ( ) ) ) {
apkMetaResult . setVersionCode ( result . getVersionCode ( ) ) ;
}
//这三个属性无法从info.json中获取,只能从base.apk中获取
apkMetaResult . setTargetSdk ( result . getTargetSdk ( ) ) ;
apkMetaResult . setCompileSdk ( result . getCompileSdk ( ) ) ;
apkMetaResult . setPermissions ( result . getPermissions ( ) ) ;
return apkMetaResult ;
} else {
ApkmContent content = readApkmContent ( apkmFile ) ;
List < ContainerEntry > apks = content . apks ;
byte [ ] infoJson = content . infoJson ;
if ( apks . isEmpty ( ) ) {
throw new IOException ( " 容器内未找到任何 .apk 文件: " + apkmFile . getName ( ) ) ;
if ( isNotEmpty ( result . getAbiList ( ) ) ) {
apkMetaResult . setAbiList ( result . getAbiList ( ) ) ;
}
if ( isNotEmpty ( result . getSplitApkList ( ) ) ) {
apkMetaResult . setSplitApkList ( result . getSplitApkList ( ) ) ;
}
byte [ ] baseApkbytes = ZipUtil . unpackEntry ( apkmFile , " base.apk " ) ;
// 1) 按经验法则优先定位主 APK
ContainerEntry mainApk = locateApkmMainApk ( apks ) ;
// 2) 若经验法则未命中,兜底:逐个尝试解析,第一个拿到 packageName 的即为主包
ApkMetaResult result = tryParseApkBytes ( baseApkbytes ) ;
if ( result = = null ) {
for ( ContainerEntry entry : apks ) {
ApkMetaResult r = tryParseApkBytes ( entry . data ) ;
if ( r ! = null & & r . getPackageName ( ) ! = null & & ! r . getPackageName ( ) . isBlank ( ) ) {
result = r ;
mainApk = entry ;
break ;
}
}
if ( StringUtils . isNotBlank ( result . getIconPath ( ) ) ) {
apkMetaResult . setIconPath ( result . getIconPath ( ) ) ;
}
if ( result = = null ) {
throw new IOException ( " 容器内所有 apk 均无法解析出有效包名: " + apkmFile . getName ( ) ) ;
if ( result . getIconBytes ( ) ! = null ) {
apkMetaResult . setIconBytes ( result . getIconBytes ( ) ) ;
}
// 3) 记录 split apk 列表
for ( ContainerEntry entry : apks ) {
result . getSplitApkList ( ) . add ( entry . name ) ;
}
if ( infoJson ! = null ) {
supplementFromJson ( result , infoJson ) ;
}
return result ;
}
return apkMetaResult ;
}
/**
* 解包 ZIP 容器,收集内部所有 .apk 及描述文件( info.json) 。
* APKM固定为info.json
* <p>
* 只获取splitApkList和info.json信息
*
*/
private static ApkmContent readApkmContent ( File apkmFile ) throws IOException {
@@ -475,7 +432,7 @@ public class ApkMetaParser {
continue ;
}
String entryName = entry . getName ( ) ;
if ( entryName . toLowerCase ( Locale . ROOT ) . endsWith ( " .apk " ) ) {
if ( entryName . toLowerCase ( Locale . ROOT ) . endsWith ( " .apk " ) & & ! entryName . equalsIgnoreCase ( " base.apk " ) ) {
try ( InputStream in = zf . getInputStream ( entry ) ) {
apks . add ( new ContainerEntry ( entryName , in . readAllBytes ( ) ) ) ;
}
@@ -505,44 +462,63 @@ public class ApkMetaParser {
* 可能额外包含大型 obb 扩展文件(非 apk, 直接忽略) 。
*/
public static ApkMetaResult parseXapk ( File xapkFile ) throws IOException {
ApkMetaResult apkMetaResult = new ApkMetaResult ( ) ;
boolean manifestExists = ZipUtil . containsEntry ( xapkFile , " manifest.json " ) ;
if ( ! manifestExists ) {
throw new IOException ( " 容器内未找到 manifest.json 文件: " + xapkFile . getName ( ) ) ;
}
XapkContent content = readXapkContent ( xapkFile ) ;
List < ContainerEntry > apks = content . apks ;
byte [ ] manifestJson = content . manifestJson ;
if ( apks . isEmpty ( ) ) {
throw new IOException ( " 容器内未找到任何 .apk 文件: " + xapkFile . getName ( ) ) ;
XapkJsonInfo xapkJsonInfo = OBJECT_MAPPER . readValue ( manifestJson , XapkJsonInfo . class ) ;
apkMetaResult . setAppLabel ( xapkJsonInfo . getName ( ) ) ;
apkMetaResult . setPackageName ( xapkJsonInfo . getPackage_name ( ) ) ;
apkMetaResult . setVersionName ( xapkJsonInfo . getVersion_name ( ) ) ;
apkMetaResult . setVersionCode ( xapkJsonInfo . getVersion_code ( ) ) ;
apkMetaResult . setMinSdk ( xapkJsonInfo . getMin_sdk_version ( ) ) ;
apkMetaResult . setTargetSdk ( xapkJsonInfo . getTarget_sdk_version ( ) ) ;
apkMetaResult . setPermissions ( xapkJsonInfo . getPermissions ( ) ) ;
byte [ ] iconBytes = ZipUtil . unpackEntry ( xapkFile , xapkJsonInfo . getIcon ( ) ) ;
apkMetaResult . setIconPath ( xapkJsonInfo . getIcon ( ) ) ;
apkMetaResult . setIconBytes ( iconBytes ) ;
List < String > splitConfigs = xapkJsonInfo . getSplit_configs ( ) ;
List < SplitApks > splitApksList = xapkJsonInfo . getSplit_apks ( ) ;
Set < String > splitConfigSet = new HashSet < > ( splitConfigs ) ;
List < String > fileList = xapkJsonInfo . getSplit_apks ( ) . stream ( )
. filter ( apk - > splitConfigSet . contains ( apk . getId ( ) ) )
. map ( SplitApks : : getFile )
. collect ( Collectors . toList ( ) ) ;
apkMetaResult . setSplitApkList ( fileList ) ;
SplitApks baseApk = splitApksList . stream ( ) . filter ( splitApks - > splitApks . getId ( ) . equals ( " base " ) ) . findFirst ( ) . orElse ( null ) ;
if ( baseApk = = null ) {
throw new IOException ( " 容器内未找到 base apk 文件: " + xapkFile . getName ( ) ) ;
}
// 1) XAPK 主包优先按 manifest.json 的 split_apks(id == "base") 定位,失败再走经验法则
ContainerEntry mainApk = locateXapkMainApk ( apks , manifestJson ) ;
ContainerEntry baseApkEntry = apks . stream ( ) . filter ( containerEntry - > containerEntry . name . equals ( baseApk . getFile ( ) ) ) . findFirst ( ) . orElse ( null ) ;
if ( baseApkEntry = = null ) {
throw new IOException ( " 容器内未找到 base apk 文件: " + xapkFile . getName ( ) ) ;
}
// 2) 若经验法则未命中,兜底:逐个尝试解析,第一个拿到 packageName 的即为主包
ApkMetaResult result = mainApk ! = null ? tryParseApkBytes ( mainApk . data ) : null ;
ApkMetaResult result = tryParseApkBytes ( baseApkEntry . data ) ;
if ( result = = null ) {
for ( ContainerEntry entry : apks ) {
ApkMetaResult r = tryParseApkBytes ( entry . data ) ;
if ( r ! = null & & r . getPackageName ( ) ! = null & & ! r . getPackageName ( ) . isBlank ( ) ) {
result = r ;
mainApk = entry ;
break ;
}
}
}
if ( result = = null ) {
throw new IOException ( " 容器内所有 apk 均无法解析出有效包名: " + xapkFile . getName ( ) ) ;
throw new IOException ( " 容器内未找到 base apk 文件: " + x apkFile . getName ( ) ) ;
}
// 3) 记录 split apk 列表
for ( Contain erE ntry entry : apks ) {
r esult . g etSplitApkList ( ) . add ( entry . name ) ;
}
// 主 APK 解析出的字段优先(覆盖容器 JSON 中的值)
overrideIfDiff ere nt( apkMetaResult , result ) ;
apkMetaR esult . s etCompileSdk ( result . getCompileSdk ( ) ) ;
apkMetaResult . setAbiList ( result . getAbiList ( ) ) ;
// 4) 补充 JSON 描述信息( XAPK 通常只有 manifest.json)
if ( manifestJson ! = null ) {
supplementFromJson ( result , manifestJson ) ;
}
return result ;
return apkMetaResult ;
}
private static XapkContent readXapkContent ( File xapkFile ) throws IOException {
@@ -570,64 +546,6 @@ public class ApkMetaParser {
return new XapkContent ( apks , manifestJson ) ;
}
/**
* 经验法则定位主 APK:
* <ul>
* <li>APKM( APKMirror) : info.json 描述的 base.apk</li>
* </ul>
*/
private static ContainerEntry locateApkmMainApk ( List < ContainerEntry > apks ) {
// 优先 base-master.apk / base.apk
for ( ContainerEntry e : apks ) {
if ( e . name . toLowerCase ( Locale . ROOT ) . equalsIgnoreCase ( " base.apk " ) ) {
return e ;
}
}
// 再找不含 split / config / density 等关键字的主包
for ( ContainerEntry e : apks ) {
String lower = e . name . toLowerCase ( Locale . ROOT ) ;
if ( ! lower . contains ( " split " ) & & ! lower . contains ( " config " ) & & ! lower . contains ( " density " ) ) {
return e ;
}
}
return null ;
}
/**
* XAPK 专属定位:解析 manifest.json 的 {@code split_apks} 数组,返回
* {@code id == "base"} 项对应的 apk 条目(即主包)。
*/
private static ContainerEntry locateXapkMainApk ( List < ContainerEntry > apks , byte [ ] manifestJson ) {
if ( manifestJson = = null ) {
return null ;
}
String baseFile = null ;
try {
JSONArray splits = JSONUtil . parseObj ( new String ( manifestJson , StandardCharsets . UTF_8 ) )
. getJSONArray ( " split_apks " ) ;
if ( splits ! = null ) {
for ( int i = 0 ; i < splits . size ( ) ; i + + ) {
JSONObject split = splits . getJSONObject ( i ) ;
if ( " base " . equals ( split . getStr ( " id " ) ) ) {
baseFile = split . getStr ( " file " ) ;
break ;
}
}
}
} catch ( Exception ignored ) {
// JSON 解析失败则回退到经验法则
}
if ( baseFile = = null ) {
return null ;
}
for ( ContainerEntry e : apks ) {
if ( e . name . equalsIgnoreCase ( baseFile ) ) {
return e ;
}
}
return null ;
}
private record XapkContent ( List < ContainerEntry > apks , byte [ ] manifestJson ) {
}
@@ -870,6 +788,34 @@ public class ApkMetaParser {
}
}
/**
* 将主 APK 解析结果覆盖到容器解析结果上,仅当两者不一致时才覆盖。
*/
private static void overrideIfDifferent ( ApkMetaResult target , ApkMetaResult source ) {
if ( ! Objects . equals ( target . getAppLabel ( ) , source . getAppLabel ( ) ) ) {
target . setAppLabel ( source . getAppLabel ( ) ) ;
}
if ( ! Objects . equals ( target . getPackageName ( ) , source . getPackageName ( ) ) ) {
target . setPackageName ( source . getPackageName ( ) ) ;
}
if ( ! Objects . equals ( target . getVersionName ( ) , source . getVersionName ( ) ) ) {
target . setVersionName ( source . getVersionName ( ) ) ;
}
if ( ! Objects . equals ( target . getVersionCode ( ) , source . getVersionCode ( ) ) ) {
target . setVersionCode ( source . getVersionCode ( ) ) ;
}
if ( ! Objects . equals ( target . getMinSdk ( ) , source . getMinSdk ( ) ) ) {
target . setMinSdk ( source . getMinSdk ( ) ) ;
}
if ( ! Objects . equals ( target . getTargetSdk ( ) , source . getTargetSdk ( ) ) ) {
target . setTargetSdk ( source . getTargetSdk ( ) ) ;
}
}
private static boolean isNotEmpty ( List < ? > list ) {
return list ! = null & & ! list . isEmpty ( ) ;
}
private static void fillIcon ( ApkMetaResult result , ApkFile apkFileParser ) {
try {
List < Icon > icons = apkFileParser . getIconFiles ( ) ;