Фон
Я хотел получить информацию об APK-файлах (включая разделенные APK-файлы), даже если они находятся внутри сжатых zip-файлов (без их распаковки). В моем случае это включает в себя различные вещи, такие как имя пакета, код версии, имя версии, ярлык приложения, значок приложения и, если это разделенный файл APK или нет.
Обратите внимание, что я Я хочу сделать все это внутри приложения Android, не используя P C, поэтому некоторые инструменты могут быть недоступны для использования.
Проблема
Это означает, что я могу ' • не используйте функцию getPackageArchiveInfo , так как для этой функции требуется путь к файлу APK и она работает только с файлами не-split-apk.
Короче говоря, это не фреймворковая функция для этого, поэтому мне нужно найти способ сделать это, зайдя в заархивированный файл, используя InputStream в качестве входных данных для его анализа в функции.
Существуют различные решения онлайн в том числе за пределами Android, но я не знаю ни одного, который стабилен и работает для всех случаев. Многие из них могут быть полезны даже для Android (например, здесь ), но могут не выполнить синтаксический анализ и могут потребовать путь к файлу вместо Uri / InputStream.
Что я Я нашел и попробовал
Я нашел this в StackOverflow, но, к сожалению, в соответствии с моими тестами, он всегда генерирует контент, но в некоторых редких случаях это недопустимо XML content.
До сих пор я обнаружил имена пакетов этих приложений и коды их версий, которые синтаксический анализатор не может проанализировать, так как вывод XML content недопустим:
- com.farpro c .wifi.analyzer 139
- com.teslacoilsw.launcherclientproxy 2
- com.hotornot.app 3072
- android 29 (это само системное приложение "Android System")
- com.google. android .videos 41300042
- com.facebook.katana 201518851
- com.keramidas.TitaniumBackupPr o 10
- com.google. android .apps.tachyon 2985033
- com.google. android .apps.photos 3594753
Использование средства просмотра XML и XML validator , вот проблемы с этими приложениями:
- Для # 1, # 2 я получил очень странный контент, начиная с
<mnfs
. - Для # 3, он не любит "&" в
<activity theme="resourceID 0x7f13000b" label="Features & Tests" ...
- Для # 4, он пропустил конечный тег "manifest" в конце.
- Для # 5 он пропустил несколько конечных тегов, по крайней мере, «намеренный фильтр», «получатель» и «манифест». Может быть, больше.
- Для # 6 он по какой-то причине дважды получил атрибут "allowBackup" в теге "application".
- Для # 7 он получил значение без атрибута в теге manifest :
<manifest versionCode="resourceID 0xa" ="1.3.2"
. - Для # 8, он пропустил много контента после получения некоторых тегов «особенность использования» и не имел конечного тега для «манифеста».
- Для # 9, он пропустил много контента после получения некоторых тегов «использования-разрешения» и не имел конечного тега для «манифеста»
Удивительно, но я не нашел никаких проблем с разделенными файлами APK. Только с основными файлами APK.
Вот код (также доступен здесь ):
MainActivity .kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
thread {
val problematicApkFiles = HashMap<ApplicationInfo, HashSet<String>>()
val installedApplications = packageManager.getInstalledPackages(0)
val startTime = System.currentTimeMillis()
for ((index, packageInfo) in installedApplications.withIndex()) {
val applicationInfo = packageInfo.applicationInfo
val packageName = packageInfo.packageName
// Log.d("AppLog", "$index/${installedApplications.size} parsing app $packageName ${packageInfo.versionCode}...")
val mainApkFilePath = applicationInfo.publicSourceDir
val parsedManifestOfMainApkFile =
try {
val parsedManifest = ManifestParser.parse(mainApkFilePath)
if (parsedManifest?.isSplitApk != false)
Log.e("AppLog", "$packageName - parsed normal APK, but failed to identify it as such")
parsedManifest?.manifestAttributes
} catch (e: Exception) {
Log.e("AppLog", e.toString())
null
}
if (parsedManifestOfMainApkFile == null) {
problematicApkFiles.getOrPut(applicationInfo, { HashSet() }).add(mainApkFilePath)
Log.e("AppLog", "$packageName - failed to parse main APK file $mainApkFilePath")
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
applicationInfo.splitPublicSourceDirs?.forEach {
val parsedManifestOfSplitApkFile =
try {
val parsedManifest = ManifestParser.parse(it)
if (parsedManifest?.isSplitApk != true)
Log.e("AppLog", "$packageName - parsed split APK, but failed to identify it as such")
parsedManifest?.manifestAttributes
} catch (e: Exception) {
Log.e("AppLog", e.toString())
null
}
if (parsedManifestOfSplitApkFile == null) {
Log.e("AppLog", "$packageName - failed to parse main APK file $it")
problematicApkFiles.getOrPut(applicationInfo, { HashSet() }).add(it)
}
}
}
val endTime = System.currentTimeMillis()
Log.d("AppLog", "done parsing. number of files we failed to parse:${problematicApkFiles.size} time taken:${endTime - startTime} ms")
if (problematicApkFiles.isNotEmpty()) {
Log.d("AppLog", "list of files that we failed to get their manifest:")
for (entry in problematicApkFiles) {
Log.d("AppLog", "packageName:${entry.key.packageName} , files:${entry.value}")
}
}
}
}
}
ManifestParser.kt
class ManifestParser{
var isSplitApk: Boolean? = null
var manifestAttributes: HashMap<String, String>? = null
companion object {
fun parse(file: File) = parse(java.io.FileInputStream(file))
fun parse(filePath: String) = parse(File(filePath))
fun parse(inputStream: InputStream): ManifestParser? {
val result = ManifestParser()
val manifestXmlString = ApkManifestFetcher.getManifestXmlFromInputStream(inputStream)
?: return null
val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
val builder: DocumentBuilder = factory.newDocumentBuilder()
val document: Document? = builder.parse(manifestXmlString.byteInputStream())
if (document != null) {
document.documentElement.normalize()
val manifestNode: Node? = document.getElementsByTagName("manifest")?.item(0)
if (manifestNode != null) {
val manifestAttributes = HashMap<String, String>()
for (i in 0 until manifestNode.attributes.length) {
val node = manifestNode.attributes.item(i)
manifestAttributes[node.nodeName] = node.nodeValue
}
result.manifestAttributes = manifestAttributes
}
}
result.manifestAttributes?.let {
result.isSplitApk = (it["android:isFeatureSplit"]?.toBoolean()
?: false) || (it.containsKey("split"))
}
return result
}
}
}
ApkManifestFetcher.kt
Вопросы
- Почему я получаю недопустимое содержимое XML для некоторых файлов манифеста APK (следовательно, для них не удается выполнить синтаксический анализ XML)?
- Как я могу заставить его работать, всегда?
- Есть ли лучший способ разобрать файл манифеста в действительный XML? Может быть, лучшая альтернатива, которая могла бы работать со всеми видами файлов APK, в том числе внутри заархивированных файлов, без их распаковки?