将APK转换为最小APK后如何解析?
How to parse an APK after converting it to a minimal one?
背景
我希望能够解析各种来源或各种类型的 APK 文件。我只想获得有关它们的非常具体的基本信息:
- 包名
- 版本代码
- 版本名称
- 标签(应用名称)
- 图标
- minSdkVersion
- 是否是拆分APK(解析manifest时可用)
Android 框架只有一个函数来解析 APK 文件 (PackageManager.getPackageArchiveInfo),但它有 2 个缺点:
它需要一个文件路径。这并不总是可用,因为您可能需要处理 Uri,或者您的 APK 文件位于某个 ZIP 文件中。
它无法处理拆分的 APK 文件。只有其中一个基础。如果你在拆分 APK 文件上尝试它,你只会得到 null。
因此,我试图找到一个可靠的 Java&Kotlin 解决方案,可以轻松处理它们。
问题
我发现的每个解决方案都有其自身的缺点。有些库我什至找不到如何使用,有些库在没有真实文件路径的情况下无法解析 APK 文件。其中一些只是创建新文件,而不是 returning 你 Java/Kotlin 中的对象。有些甚至是其他编程语言,让我想知道是否可以使用它们。
到目前为止,我发现唯一一个足够好的是 "hsiafan" apk-parser,它只需要 APK 中的 2 个文件:清单文件和资源文件。它有一些问题,但通常它可以为您提供我提到的信息。
事情是,正如我所写,它并不总是有效,所以我想 return 了解基础知识,至少当我注意到它无法解析时,至少在它是普通 APK 的情况下拆分 APK 文件的基础。它不能很好处理的一些情况是:
- 有时无法正确获取应用名称 (here)。
- 某些资源以某种方式隐藏在某些应用程序中 (here)。即使是 Jadx 等工具也无法显示它们,但其他工具可以。
- 自适应图标难以解析,包括可绘制 XML 文件。虽然我成功解析了 VectorDrawable (here),但这是一个很好的解决方法。
还有其他一些。
我试过的
所以,我想再次尝试 Android 框架,但这次有了一个新想法:我可以根据当前的情况只提取它真正需要的内容,而不是处理整个原始 APK观察它。
例如,如果我知道我需要处理的资源,我可以只将它们(以及一些需要的关键文件)复制到一个新的 APK 中,然后丢弃其余的。这可以减少对 copy/download 大量数据的需求,只是为了解析 APK 并获取有关它的信息。例如,如果 APK 文件是 100MB,当我们只需要其中的一小部分时,就没有必要全部获取。
首先,我想看看如何创建一个可以解析的新 APK,所以现在我将原始 APK(当前应用的当前应用)的所有条目复制到一个新文件中,并选择解析它:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null)
return
thread {
val originalApplicationInfo = packageManager.getApplicationInfo(packageName, 0)
val filePath = originalApplicationInfo.publicSourceDir
val outputFile = File(cacheDir, "test.apk")
outputFile.parentFile!!.mkdirs()
outputFile.delete()
ZipFile(filePath).use { zipFile ->
ZipOutputStream(FileOutputStream(outputFile)).use { zipOutputStream ->
for (entry in zipFile.entries()) {
val name = entry.name
zipOutputStream.putNextEntry(ZipEntry(name))
zipFile.getInputStream(entry).use { it.copyTo(zipOutputStream.buffered()) }
zipOutputStream.closeEntry()
}
}
}
val originalLabel = originalApplicationInfo.loadLabel(packageManager)
val originalIcon: Drawable? = originalApplicationInfo.loadIcon(packageManager)
Log.d("AppLog", "originalPackageInfo: label:$originalLabel appIcon:${originalIcon?.javaClass?.simpleName}")
//
val packageArchiveInfo = packageManager.getPackageArchiveInfo(outputFile.absolutePath, 0)
val label = packageArchiveInfo?.applicationInfo?.loadLabel(packageManager)?.toString()
val appIcon = packageArchiveInfo?.applicationInfo?.loadIcon(packageManager)
Log.d("AppLog", "packageArchiveInfo!=null?${packageArchiveInfo != null} label:$label appIcon:${appIcon?.javaClass?.simpleName}")
}
}
}
文件确实生成了,但由于某种原因 Android 框架无法解析它,因为 packageArchiveInfo
为空。
问题
- 我做的示例怎么不能用?新APK怎么解析不了?
getPackageArchiveInfo
函数解析 APK 所需的最少文件集是什么,仅用于我上面提到的信息?
- 如果这种解决方案不起作用,是否有一个库可以处理所有类型的 APK 文件以及我提到的所有信息,无论来源如何(包括 Uri 和 zip 文件内)?
编辑:按照建议,我可以只复制文件夹 "AndroidManifest.xml"、"resources.arsc"、"res" 中的那些,但它似乎仍然无法正常工作,因为图标不不会总是一样的:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null)
return
thread {
val installedApplications = packageManager.getInstalledPackages(0)
Log.d("AppLog", "checking ${installedApplications.size} apps")
for (originalPackageInfo in installedApplications) {
val originalApplicationInfo = originalPackageInfo.applicationInfo
val filePath = originalApplicationInfo.publicSourceDir
val outputFile = File(cacheDir, "test.apk")
outputFile.parentFile!!.mkdirs()
outputFile.delete()
val toExtract = setOf<String>("AndroidManifest.xml", "resources.arsc", "res")
ZipFile(filePath).use { zipFile ->
ZipOutputStream(FileOutputStream(outputFile)).use { zipOutputStream ->
for (entry in zipFile.entries()) {
val name = entry.name
if (toExtract.contains(name.split("/")[0])) {
zipOutputStream.putNextEntry(ZipEntry(name))
zipFile.getInputStream(entry).use { inStream ->
zipOutputStream.buffered().apply {
inStream.copyTo(this)
}.flush()
}
}
}
}
}
val packageName = originalApplicationInfo.packageName
val originalLabel = originalApplicationInfo.loadLabel(packageManager)
val originalIcon: Drawable? = originalApplicationInfo.loadIcon(packageManager)
val originalIconBitmap = originalIcon?.toBitmap()
//
val packageArchiveInfo = packageManager.getPackageArchiveInfo(outputFile.absolutePath, 0)
if (packageArchiveInfo == null) {
Log.e("AppLog", "$packageName could not parse generated APK")
continue
}
val label = packageArchiveInfo.applicationInfo.loadLabel(packageManager).toString()
val appIcon = packageArchiveInfo?.applicationInfo?.loadIcon(packageManager)
val appIconBitmap = appIcon?.toBitmap()
when {
label != originalLabel ->
Log.e("AppLog", "$packageName got wrong label $label vs $originalLabel")
packageArchiveInfo.versionName != originalPackageInfo.versionName ->
Log.e("AppLog", "$packageName got wrong versionName ${packageArchiveInfo.versionName} vs ${originalPackageInfo.versionName}")
packageArchiveInfo.versionCode != originalPackageInfo.versionCode ->
Log.e("AppLog", "$packageName got wrong versionCode ${packageArchiveInfo.versionCode} vs ${originalPackageInfo.versionCode}")
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageArchiveInfo.applicationInfo.minSdkVersion != originalApplicationInfo.minSdkVersion ->
Log.e("AppLog", "$packageName got wrong minSdkVersion ${packageArchiveInfo.applicationInfo.minSdkVersion} vs ${originalApplicationInfo.minSdkVersion}")
appIcon?.javaClass?.name != originalIcon?.javaClass?.name ->
Log.e("AppLog", "$packageName got different app icon type: ${appIcon?.javaClass?.simpleName} vs ${originalIcon?.javaClass?.simpleName}")
originalIconBitmap != null && appIconBitmap != null && (originalIconBitmap.width != appIconBitmap.width || originalIconBitmap.height != appIconBitmap.height) ->
Log.e("AppLog", "$packageName got wrong app icons sizes:${appIconBitmap.width}x${appIconBitmap.height} vs ${originalIconBitmap.width}x${originalIconBitmap.height}")
originalIconBitmap != null && appIconBitmap != null && !areBitmapsSame(originalIconBitmap, appIconBitmap) ->
Log.e("AppLog", "$packageName got wrong app icons content ")
(originalIconBitmap == null && appIconBitmap != null) || (originalIconBitmap != null && appIconBitmap == null) ->
Log.e("AppLog", "$packageName null vs non-null app icon: ${appIconBitmap != null} vs ${originalIconBitmap != null}")
}
}
Log.d("AppLog", "done")
}
}
fun areBitmapsSame(bitmap: Bitmap, bitmap2: Bitmap): Boolean {
if (bitmap.width != bitmap2.width || bitmap.height != bitmap2.height)
return false
for (x in 0 until bitmap.width)
for (y in 0 until bitmap.height)
if (bitmap.getPixel(x, y) != bitmap2.getPixel(x, y))
return false
return true
}
}
我认为,由于应用程序图标非常复杂并且依赖于各种资源(甚至可能以奇怪的方式隐藏),所以除了实际将文件放在文件系统上之外别无选择。
编辑:关于获取应用程序图标,我实际上并没有很好地用于 APK 文件。我在我的 own app 上使用了一些不同的东西,它在这里似乎也工作得很好,除了某些情况下图标有点不同(例如 shape/color 可能不同),可能是由于不同配置。但至少当应用程序明显具有非默认应用程序图标时,它不会 return 您使用 Android 的默认应用程序图标。
遗憾的是,在创建最小化的 APK 时,它并不总能获得良好的应用程序图标(有时 return 告诉我 ColorStateListDrawable 或 ColorDrawable,例如),可能是因为有时原始 APK 在非-常规路径。假设您拥有整个 APK,这就是您获取应用程序图标的方法:
在获取之前,使用:
packageArchiveInfo.applicationInfo.publicSourceDir = targetFilePath
packageArchiveInfo.applicationInfo.sourceDir = targetFilePath
然后调用这个函数:
fun getAppIcon(context: Context, applicationInfo: ApplicationInfo): Drawable? {
val packageManager = context.packageManager
try {
val iconResId = applicationInfo.icon
if (iconResId != 0) {
val resources: Resources = packageManager.getResourcesForApplication(applicationInfo)
val density = context.resources.displayMetrics.densityDpi
var result = ResourcesCompat.getDrawableForDensity(resources, iconResId, density, null)
if (result != null)
return result
}
} catch (e: Exception) {
// e.printStackTrace()
}
try {
val applicationIcon = packageManager.getApplicationIcon(applicationInfo)
// Log.d("AppLog", "getApplicationIcon type:${applicationIcon.javaClass.simpleName}")
return applicationIcon
} catch (ignored: Exception) {
}
return null
}
要转换为位图,您可以使用:
val appIconBitmap = try {
appIcon?.toBitmap(appIconSize, appIconSize)
} catch (e: Exception) {
e.printStackTrace()
null
}
要获取应用程序图标大小,您可以使用:
fun getAppIconSize(context: Context): Int {
val activityManager = context.getSystemService<ActivityManager>()
val appIconSize = try {
activityManager.launcherLargeIconSize
} catch (e: Exception) {
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48f, context.resources.displayMetrics).toInt()
}
return appIconSize
}
修改复制代码自
zipFile.getInputStream(entry).use { it.copyTo(zipOutputStream.buffered()) }
至
zipFile.getInputStream(entry).use {
val bufStream = zipOutputStream.buffered()
it.copyTo(bufStream)
bufStream.flush()
}
或类似的东西以确保所有数据都被写出。
我以为 APK 没有通过证书验证,但事实并非如此。上述内容将为您提供标签和图标。
使用原始代码我看到了以下错误
chunk size is bigger than given data
Failed to load 'resources.arsc' in APK '/data/user/0/com
这向我表明输出出现了问题。通过上述更改,我看到以下内容:
originalPackageInfo: label:APK Copy and Read appIcon:AdaptiveIconDrawable
packageArchiveInfo!=null?true label:APK Copy and Read appIcon:AdaptiveIconDrawable
如果我在 Android Studio 中从设备资源管理器打开创建的 APK,它会很好地解析出来。
至于最低限度,我认为,为了您的目的,您将需要清单、resource.arsc 文件和 res 目录。以下是如何获得仅使用这些元素进行解析的简化 APK:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null)
return
thread {
val originalApplicationInfo = packageManager.getApplicationInfo(packageName, 0)
val filePath = originalApplicationInfo.publicSourceDir
val outputFile = File(cacheDir, "test.apk")
outputFile.parentFile!!.mkdirs()
outputFile.delete()
val toExtract = setOf<String>("AndroidManifest.xml", "resources.arsc","res")
ZipFile(filePath).use { zipFile ->
ZipOutputStream(FileOutputStream(outputFile)).use { zipOutputStream ->
for (entry in zipFile.entries()) {
val name = entry.name
if (toExtract.contains(name.split("/")[0])) {
zipOutputStream.putNextEntry(ZipEntry(name))
zipFile.getInputStream(entry).use { inStream ->
zipOutputStream.buffered().apply {
inStream.copyTo(this)
}.flush()
}
}
}
}
}
val originalLabel = originalApplicationInfo.loadLabel(packageManager)
val originalIcon: Drawable? = originalApplicationInfo.loadIcon(packageManager)
Log.d(
"AppLog",
"originalPackageInfo: label:$originalLabel appIcon:${originalIcon?.javaClass?.simpleName}"
)
//
val packageArchiveInfo =
packageManager.getPackageArchiveInfo(outputFile.absolutePath, 0)
val label = packageArchiveInfo?.applicationInfo?.loadLabel(packageManager)?.toString()
val appIcon = packageArchiveInfo?.applicationInfo?.loadIcon(packageManager)
Log.d(
"AppLog",
"packageArchiveInfo!=null?${packageArchiveInfo != null} label:$label appIcon:${appIcon?.javaClass?.simpleName}"
)
}
}
}
背景
我希望能够解析各种来源或各种类型的 APK 文件。我只想获得有关它们的非常具体的基本信息:
- 包名
- 版本代码
- 版本名称
- 标签(应用名称)
- 图标
- minSdkVersion
- 是否是拆分APK(解析manifest时可用)
Android 框架只有一个函数来解析 APK 文件 (PackageManager.getPackageArchiveInfo),但它有 2 个缺点:
它需要一个文件路径。这并不总是可用,因为您可能需要处理 Uri,或者您的 APK 文件位于某个 ZIP 文件中。
它无法处理拆分的 APK 文件。只有其中一个基础。如果你在拆分 APK 文件上尝试它,你只会得到 null。
因此,我试图找到一个可靠的 Java&Kotlin 解决方案,可以轻松处理它们。
问题
我发现的每个解决方案都有其自身的缺点。有些库我什至找不到如何使用,有些库在没有真实文件路径的情况下无法解析 APK 文件。其中一些只是创建新文件,而不是 returning 你 Java/Kotlin 中的对象。有些甚至是其他编程语言,让我想知道是否可以使用它们。
到目前为止,我发现唯一一个足够好的是 "hsiafan" apk-parser,它只需要 APK 中的 2 个文件:清单文件和资源文件。它有一些问题,但通常它可以为您提供我提到的信息。
事情是,正如我所写,它并不总是有效,所以我想 return 了解基础知识,至少当我注意到它无法解析时,至少在它是普通 APK 的情况下拆分 APK 文件的基础。它不能很好处理的一些情况是:
- 有时无法正确获取应用名称 (here)。
- 某些资源以某种方式隐藏在某些应用程序中 (here)。即使是 Jadx 等工具也无法显示它们,但其他工具可以。
- 自适应图标难以解析,包括可绘制 XML 文件。虽然我成功解析了 VectorDrawable (here),但这是一个很好的解决方法。
还有其他一些。
我试过的
所以,我想再次尝试 Android 框架,但这次有了一个新想法:我可以根据当前的情况只提取它真正需要的内容,而不是处理整个原始 APK观察它。
例如,如果我知道我需要处理的资源,我可以只将它们(以及一些需要的关键文件)复制到一个新的 APK 中,然后丢弃其余的。这可以减少对 copy/download 大量数据的需求,只是为了解析 APK 并获取有关它的信息。例如,如果 APK 文件是 100MB,当我们只需要其中的一小部分时,就没有必要全部获取。
首先,我想看看如何创建一个可以解析的新 APK,所以现在我将原始 APK(当前应用的当前应用)的所有条目复制到一个新文件中,并选择解析它:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null)
return
thread {
val originalApplicationInfo = packageManager.getApplicationInfo(packageName, 0)
val filePath = originalApplicationInfo.publicSourceDir
val outputFile = File(cacheDir, "test.apk")
outputFile.parentFile!!.mkdirs()
outputFile.delete()
ZipFile(filePath).use { zipFile ->
ZipOutputStream(FileOutputStream(outputFile)).use { zipOutputStream ->
for (entry in zipFile.entries()) {
val name = entry.name
zipOutputStream.putNextEntry(ZipEntry(name))
zipFile.getInputStream(entry).use { it.copyTo(zipOutputStream.buffered()) }
zipOutputStream.closeEntry()
}
}
}
val originalLabel = originalApplicationInfo.loadLabel(packageManager)
val originalIcon: Drawable? = originalApplicationInfo.loadIcon(packageManager)
Log.d("AppLog", "originalPackageInfo: label:$originalLabel appIcon:${originalIcon?.javaClass?.simpleName}")
//
val packageArchiveInfo = packageManager.getPackageArchiveInfo(outputFile.absolutePath, 0)
val label = packageArchiveInfo?.applicationInfo?.loadLabel(packageManager)?.toString()
val appIcon = packageArchiveInfo?.applicationInfo?.loadIcon(packageManager)
Log.d("AppLog", "packageArchiveInfo!=null?${packageArchiveInfo != null} label:$label appIcon:${appIcon?.javaClass?.simpleName}")
}
}
}
文件确实生成了,但由于某种原因 Android 框架无法解析它,因为 packageArchiveInfo
为空。
问题
- 我做的示例怎么不能用?新APK怎么解析不了?
getPackageArchiveInfo
函数解析 APK 所需的最少文件集是什么,仅用于我上面提到的信息?- 如果这种解决方案不起作用,是否有一个库可以处理所有类型的 APK 文件以及我提到的所有信息,无论来源如何(包括 Uri 和 zip 文件内)?
编辑:按照建议,我可以只复制文件夹 "AndroidManifest.xml"、"resources.arsc"、"res" 中的那些,但它似乎仍然无法正常工作,因为图标不不会总是一样的:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null)
return
thread {
val installedApplications = packageManager.getInstalledPackages(0)
Log.d("AppLog", "checking ${installedApplications.size} apps")
for (originalPackageInfo in installedApplications) {
val originalApplicationInfo = originalPackageInfo.applicationInfo
val filePath = originalApplicationInfo.publicSourceDir
val outputFile = File(cacheDir, "test.apk")
outputFile.parentFile!!.mkdirs()
outputFile.delete()
val toExtract = setOf<String>("AndroidManifest.xml", "resources.arsc", "res")
ZipFile(filePath).use { zipFile ->
ZipOutputStream(FileOutputStream(outputFile)).use { zipOutputStream ->
for (entry in zipFile.entries()) {
val name = entry.name
if (toExtract.contains(name.split("/")[0])) {
zipOutputStream.putNextEntry(ZipEntry(name))
zipFile.getInputStream(entry).use { inStream ->
zipOutputStream.buffered().apply {
inStream.copyTo(this)
}.flush()
}
}
}
}
}
val packageName = originalApplicationInfo.packageName
val originalLabel = originalApplicationInfo.loadLabel(packageManager)
val originalIcon: Drawable? = originalApplicationInfo.loadIcon(packageManager)
val originalIconBitmap = originalIcon?.toBitmap()
//
val packageArchiveInfo = packageManager.getPackageArchiveInfo(outputFile.absolutePath, 0)
if (packageArchiveInfo == null) {
Log.e("AppLog", "$packageName could not parse generated APK")
continue
}
val label = packageArchiveInfo.applicationInfo.loadLabel(packageManager).toString()
val appIcon = packageArchiveInfo?.applicationInfo?.loadIcon(packageManager)
val appIconBitmap = appIcon?.toBitmap()
when {
label != originalLabel ->
Log.e("AppLog", "$packageName got wrong label $label vs $originalLabel")
packageArchiveInfo.versionName != originalPackageInfo.versionName ->
Log.e("AppLog", "$packageName got wrong versionName ${packageArchiveInfo.versionName} vs ${originalPackageInfo.versionName}")
packageArchiveInfo.versionCode != originalPackageInfo.versionCode ->
Log.e("AppLog", "$packageName got wrong versionCode ${packageArchiveInfo.versionCode} vs ${originalPackageInfo.versionCode}")
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageArchiveInfo.applicationInfo.minSdkVersion != originalApplicationInfo.minSdkVersion ->
Log.e("AppLog", "$packageName got wrong minSdkVersion ${packageArchiveInfo.applicationInfo.minSdkVersion} vs ${originalApplicationInfo.minSdkVersion}")
appIcon?.javaClass?.name != originalIcon?.javaClass?.name ->
Log.e("AppLog", "$packageName got different app icon type: ${appIcon?.javaClass?.simpleName} vs ${originalIcon?.javaClass?.simpleName}")
originalIconBitmap != null && appIconBitmap != null && (originalIconBitmap.width != appIconBitmap.width || originalIconBitmap.height != appIconBitmap.height) ->
Log.e("AppLog", "$packageName got wrong app icons sizes:${appIconBitmap.width}x${appIconBitmap.height} vs ${originalIconBitmap.width}x${originalIconBitmap.height}")
originalIconBitmap != null && appIconBitmap != null && !areBitmapsSame(originalIconBitmap, appIconBitmap) ->
Log.e("AppLog", "$packageName got wrong app icons content ")
(originalIconBitmap == null && appIconBitmap != null) || (originalIconBitmap != null && appIconBitmap == null) ->
Log.e("AppLog", "$packageName null vs non-null app icon: ${appIconBitmap != null} vs ${originalIconBitmap != null}")
}
}
Log.d("AppLog", "done")
}
}
fun areBitmapsSame(bitmap: Bitmap, bitmap2: Bitmap): Boolean {
if (bitmap.width != bitmap2.width || bitmap.height != bitmap2.height)
return false
for (x in 0 until bitmap.width)
for (y in 0 until bitmap.height)
if (bitmap.getPixel(x, y) != bitmap2.getPixel(x, y))
return false
return true
}
}
我认为,由于应用程序图标非常复杂并且依赖于各种资源(甚至可能以奇怪的方式隐藏),所以除了实际将文件放在文件系统上之外别无选择。
编辑:关于获取应用程序图标,我实际上并没有很好地用于 APK 文件。我在我的 own app 上使用了一些不同的东西,它在这里似乎也工作得很好,除了某些情况下图标有点不同(例如 shape/color 可能不同),可能是由于不同配置。但至少当应用程序明显具有非默认应用程序图标时,它不会 return 您使用 Android 的默认应用程序图标。
遗憾的是,在创建最小化的 APK 时,它并不总能获得良好的应用程序图标(有时 return 告诉我 ColorStateListDrawable 或 ColorDrawable,例如),可能是因为有时原始 APK 在非-常规路径。假设您拥有整个 APK,这就是您获取应用程序图标的方法:
在获取之前,使用:
packageArchiveInfo.applicationInfo.publicSourceDir = targetFilePath
packageArchiveInfo.applicationInfo.sourceDir = targetFilePath
然后调用这个函数:
fun getAppIcon(context: Context, applicationInfo: ApplicationInfo): Drawable? {
val packageManager = context.packageManager
try {
val iconResId = applicationInfo.icon
if (iconResId != 0) {
val resources: Resources = packageManager.getResourcesForApplication(applicationInfo)
val density = context.resources.displayMetrics.densityDpi
var result = ResourcesCompat.getDrawableForDensity(resources, iconResId, density, null)
if (result != null)
return result
}
} catch (e: Exception) {
// e.printStackTrace()
}
try {
val applicationIcon = packageManager.getApplicationIcon(applicationInfo)
// Log.d("AppLog", "getApplicationIcon type:${applicationIcon.javaClass.simpleName}")
return applicationIcon
} catch (ignored: Exception) {
}
return null
}
要转换为位图,您可以使用:
val appIconBitmap = try {
appIcon?.toBitmap(appIconSize, appIconSize)
} catch (e: Exception) {
e.printStackTrace()
null
}
要获取应用程序图标大小,您可以使用:
fun getAppIconSize(context: Context): Int {
val activityManager = context.getSystemService<ActivityManager>()
val appIconSize = try {
activityManager.launcherLargeIconSize
} catch (e: Exception) {
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48f, context.resources.displayMetrics).toInt()
}
return appIconSize
}
修改复制代码自
zipFile.getInputStream(entry).use { it.copyTo(zipOutputStream.buffered()) }
至
zipFile.getInputStream(entry).use {
val bufStream = zipOutputStream.buffered()
it.copyTo(bufStream)
bufStream.flush()
}
或类似的东西以确保所有数据都被写出。
我以为 APK 没有通过证书验证,但事实并非如此。上述内容将为您提供标签和图标。
使用原始代码我看到了以下错误
chunk size is bigger than given data
Failed to load 'resources.arsc' in APK '/data/user/0/com
这向我表明输出出现了问题。通过上述更改,我看到以下内容:
originalPackageInfo: label:APK Copy and Read appIcon:AdaptiveIconDrawable
packageArchiveInfo!=null?true label:APK Copy and Read appIcon:AdaptiveIconDrawable
如果我在 Android Studio 中从设备资源管理器打开创建的 APK,它会很好地解析出来。
至于最低限度,我认为,为了您的目的,您将需要清单、resource.arsc 文件和 res 目录。以下是如何获得仅使用这些元素进行解析的简化 APK:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null)
return
thread {
val originalApplicationInfo = packageManager.getApplicationInfo(packageName, 0)
val filePath = originalApplicationInfo.publicSourceDir
val outputFile = File(cacheDir, "test.apk")
outputFile.parentFile!!.mkdirs()
outputFile.delete()
val toExtract = setOf<String>("AndroidManifest.xml", "resources.arsc","res")
ZipFile(filePath).use { zipFile ->
ZipOutputStream(FileOutputStream(outputFile)).use { zipOutputStream ->
for (entry in zipFile.entries()) {
val name = entry.name
if (toExtract.contains(name.split("/")[0])) {
zipOutputStream.putNextEntry(ZipEntry(name))
zipFile.getInputStream(entry).use { inStream ->
zipOutputStream.buffered().apply {
inStream.copyTo(this)
}.flush()
}
}
}
}
}
val originalLabel = originalApplicationInfo.loadLabel(packageManager)
val originalIcon: Drawable? = originalApplicationInfo.loadIcon(packageManager)
Log.d(
"AppLog",
"originalPackageInfo: label:$originalLabel appIcon:${originalIcon?.javaClass?.simpleName}"
)
//
val packageArchiveInfo =
packageManager.getPackageArchiveInfo(outputFile.absolutePath, 0)
val label = packageArchiveInfo?.applicationInfo?.loadLabel(packageManager)?.toString()
val appIcon = packageArchiveInfo?.applicationInfo?.loadIcon(packageManager)
Log.d(
"AppLog",
"packageArchiveInfo!=null?${packageArchiveInfo != null} label:$label appIcon:${appIcon?.javaClass?.simpleName}"
)
}
}
}