如何解析 .apk 包中的 AndroidManifest.xml 文件
Posted
技术标签:
【中文标题】如何解析 .apk 包中的 AndroidManifest.xml 文件【英文标题】:How to parse the AndroidManifest.xml file inside an .apk package 【发布时间】:2011-01-07 01:20:54 【问题描述】:此文件似乎是二进制 XML 格式。这种格式是什么以及如何以编程方式对其进行解析(而不是使用 SDK 中的 aapt 转储工具)?
文档here 中没有讨论这种二进制格式。
注意:我想从 android 环境之外访问这些信息,最好是从 Java 中访问。
【问题讨论】:
您追求的具体用例是什么?可以使用android.content.pm.PackageManager.queryXX
方法(文档:developer.android.com/reference/android/content/pm/…)查询您自己的应用程序中的许多清单信息。
我不在安卓环境中。我想读取一个 .apk 文件,提取 AndroidManifest.xml 并将其解析为 XML。
我开发了一个不依赖 AAPT 的 APK 提取器。它包括可以解析任何 Android 二进制 XML 内容的解析器 - code.google.com/p/apk-extractor
【参考方案1】:
使用 android-apktool
有一个应用程序可以读取 apk 文件并将 XML 解码为几乎原始的形式。
用法:
apktool d Gmail.apk && cat Gmail/AndroidManifest.xml
查看android-apktool了解更多信息
【讨论】:
展示其实力的 sn-p 可能会很好:apktool d Gmail.apk && cat Gmail/AndroidManifest.xml
minSdkVersion
等版本参数也可以在Gmail/apktool.yml
中找到
如何在Android应用程序中使用它?是否可以用于从 InputStream 获取清单数据(例如:APK 文件存在于 zip 文件中)?【参考方案2】:
这个在 Android 上运行的 Java 方法记录了(我已经能够解释的).apk 包中 AndroidManifest.xml 文件的二进制格式。第二个代码框显示了如何调用 decompressXML 以及如何从设备上的应用程序包文件中加载 byte[]。 (有些字段的用途我不明白,如果您知道它们的含义,请告诉我,我会更新信息。)
// decompressXML -- Parse the 'compressed' binary form of Android XML docs
// such as for AndroidManifest.xml in .apk files
public static int endDocTag = 0x00100101;
public static int startTag = 0x00100102;
public static int endTag = 0x00100103;
public void decompressXML(byte[] xml)
// Compressed XML file/bytes starts with 24x bytes of data,
// 9 32 bit words in little endian order (LSB first):
// 0th word is 03 00 08 00
// 3rd word SEEMS TO BE: Offset at then of StringTable
// 4th word is: Number of strings in string table
// WARNING: Sometime I indiscriminently display or refer to word in
// little endian storage format, or in integer format (ie MSB first).
int numbStrings = LEW(xml, 4*4);
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
int sitOff = 0x24; // Offset of start of StringIndexTable
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
int stOff = sitOff + numbStrings*4; // StringTable follows StrIndexTable
// XMLTags, The XML tag tree starts after some unknown content after the
// StringTable. There is some unknown data after the StringTable, scan
// forward from this point to the flag for the start of an XML start tag.
int xmlTagOff = LEW(xml, 3*4); // Start from the offset in the 3rd word.
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
for (int ii=xmlTagOff; ii<xml.length-4; ii+=4)
if (LEW(xml, ii) == startTag)
xmlTagOff = ii; break;
// end of hack, scanning for start of first start tag
// XML tags and attributes:
// Every XML start and end tag consists of 6 32 bit words:
// 0th word: 02011000 for startTag and 03011000 for endTag
// 1st word: a flag?, like 38000000
// 2nd word: Line of where this tag appeared in the original source file
// 3rd word: FFFFFFFF ??
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
// 5th word: StringIndex of Element Name
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
// Start tags (not end tags) contain 3 more words:
// 6th word: 14001400 meaning??
// 7th word: Number of Attributes that follow this tag(follow word 8th)
// 8th word: 00000000 meaning??
// Attributes consist of 5 words:
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
// 1st word: StringIndex of Attribute Name
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
// 3rd word: Flags?
// 4th word: str ind of attr value again, or ResourceId of value
// TMP, dump string table to tr for debugging
//tr.addSelect("strings", null);
//for (int ii=0; ii<numbStrings; ii++)
// // Length of string starts at StringTable plus offset in StrIndTable
// String str = compXmlString(xml, sitOff, stOff, ii);
// tr.add(String.valueOf(ii), str);
//
//tr.parent();
// Step through the XML tree element tags and attributes
int off = xmlTagOff;
int indent = 0;
int startTagLineNo = -2;
while (off < xml.length)
int tag0 = LEW(xml, off);
//int tag1 = LEW(xml, off+1*4);
int lineNo = LEW(xml, off+2*4);
//int tag3 = LEW(xml, off+3*4);
int nameNsSi = LEW(xml, off+4*4);
int nameSi = LEW(xml, off+5*4);
if (tag0 == startTag) // XML START TAG
int tag6 = LEW(xml, off+6*4); // Expected to be 14001400
int numbAttrs = LEW(xml, off+7*4); // Number of Attributes to follow
//int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
off += 9*4; // Skip over 6+3 words of startTag data
String name = compXmlString(xml, sitOff, stOff, nameSi);
//tr.addSelect(name, null);
startTagLineNo = lineNo;
// Look for the Attributes
StringBuffer sb = new StringBuffer();
for (int ii=0; ii<numbAttrs; ii++)
int attrNameNsSi = LEW(xml, off); // AttrName Namespace Str Ind, or FFFFFFFF
int attrNameSi = LEW(xml, off+1*4); // AttrName String Index
int attrValueSi = LEW(xml, off+2*4); // AttrValue Str Ind, or FFFFFFFF
int attrFlags = LEW(xml, off+3*4);
int attrResId = LEW(xml, off+4*4); // AttrValue ResourceId or dup AttrValue StrInd
off += 5*4; // Skip over the 5 words of an attribute
String attrName = compXmlString(xml, sitOff, stOff, attrNameSi);
String attrValue = attrValueSi!=-1
? compXmlString(xml, sitOff, stOff, attrValueSi)
: "resourceID 0x"+Integer.toHexString(attrResId);
sb.append(" "+attrName+"=\""+attrValue+"\"");
//tr.add(attrName, attrValue);
prtIndent(indent, "<"+name+sb+">");
indent++;
else if (tag0 == endTag) // XML END TAG
indent--;
off += 6*4; // Skip over 6 words of endTag data
String name = compXmlString(xml, sitOff, stOff, nameSi);
prtIndent(indent, "</"+name+"> (line "+startTagLineNo+"-"+lineNo+")");
//tr.parent(); // Step back up the NobTree
else if (tag0 == endDocTag) // END OF XML DOC TAG
break;
else
prt(" Unrecognized tag code '"+Integer.toHexString(tag0)
+"' at offset "+off);
break;
// end of while loop scanning tags and attributes of XML tree
prt(" end at offset "+off);
// end of decompressXML
public String compXmlString(byte[] xml, int sitOff, int stOff, int strInd)
if (strInd < 0) return null;
int strOff = stOff + LEW(xml, sitOff+strInd*4);
return compXmlStringAt(xml, strOff);
public static String spaces = " ";
public void prtIndent(int indent, String str)
prt(spaces.substring(0, Math.min(indent*2, spaces.length()))+str);
// compXmlStringAt -- Return the string stored in StringTable format at
// offset strOff. This offset points to the 16 bit string length, which
// is followed by that number of 16 bit (Unicode) chars.
public String compXmlStringAt(byte[] arr, int strOff)
int strLen = arr[strOff+1]<<8&0xff00 | arr[strOff]&0xff;
byte[] chars = new byte[strLen];
for (int ii=0; ii<strLen; ii++)
chars[ii] = arr[strOff+2+ii*2];
return new String(chars); // Hack, just use 8 byte chars
// end of compXmlStringAt
// LEW -- Return value of a Little Endian 32 bit word from the byte array
// at offset off.
public int LEW(byte[] arr, int off)
return arr[off+3]<<24&0xff000000 | arr[off+2]<<16&0xff0000
| arr[off+1]<<8&0xff00 | arr[off]&0xFF;
// end of LEW
该方法将AndroidManifest读入一个byte[]进行处理:
public void getIntents(String path)
try
JarFile jf = new JarFile(path);
InputStream is = jf.getInputStream(jf.getEntry("AndroidManifest.xml"));
byte[] xml = new byte[is.available()];
int br = is.read(xml);
//Tree tr = TrunkFactory.newTree();
decompressXML(xml);
//prt("XML\n"+tr.list());
catch (Exception ex)
console.log("getIntents, ex: "+ex); ex.printStackTrace();
// end of getIntents
大多数应用程序存储在 /system/app 中,无需 root 我的 Evo 即可读取,其他应用程序位于 /data/app 中,我需要 root 才能查看。上面的“路径”参数类似于:“/system/app/Weather.apk”
【讨论】:
+1 表示可以在 Android 之外使用的工具。我把它包装成一个工作的命令行 Java 工具;见pastebin.com/c53DuqMt。 你好 Ribo,我正在使用上面的代码来读取 xml 文件。现在我想做的是在我的 xml 文件中,我有一个属性名称,其值由“@string/abc”指定,我想将它硬编码为某个字符串。 IE;删除字符串引用。但问题是我将 attrValueSi 的值设为-1。我在地图中添加键并且我在地图中有键条目,我想将值放在 attrValueSi 中。我该如何进行?请帮忙。 在这篇博文中有一个非常详细的格式描述:justanapplication.wordpress.com/category/android/…。我是通过浏览github.com/xiaxiaocao/apk-parser的源码找到的。 @corey-ogburn,更改 compXmlStringAt 的实现:` char[] chars = new char[strLen]; for (int ii = 0; ii 最近有人试过吗?我们正在使用 Android Studio 3.0.1,最近切换到 cmake,这不再有效。需要确定是 AS 还是我们的构建过程发生了变化。【参考方案3】:如何将 Android SDK 中的 Android 资产打包工具 (aapt) 用于 Python(或其他)脚本?
实际上,通过 aapt (http://elinux.org/Android_aapt),您可以检索有关 .apk 包及其 AndroidManifest.xml 文件的信息。特别是,您可以通过 'dump' 子命令提取 .apk 包的各个元素的值。例如,您可以通过以下方式在 .apk 包内的 AndroidManifest.xml 文件中提取 user-permissions:
$ aapt dump permissions package.apk
package.apk 是你的 .apk 包。
此外,您可以使用 Unix 管道命令清除输出。例如:
$ aapt dump permissions package.apk | sed 1d | awk ' print $NF '
这是一个以编程方式实现的 Python 脚本:
import os
import subprocess
#Current directory and file name:
curpath = os.path.dirname( os.path.realpath(__file__) )
filepath = os.path.join(curpath, "package.apk")
#Extract the AndroidManifest.xml permissions:
command = "aapt dump permissions " + filepath + " | sed 1d | awk ' print $NF '"
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None, shell=True)
permissions = process.communicate()[0]
print permissions
以类似的方式,您可以提取 AndroidManifest.xml 的其他信息(例如 package、应用名称 等...) :
#Extract the APK package info:
shellcommand = "aapt dump badging " + filepath
process = subprocess.Popen(shellcommand, stdout=subprocess.PIPE, stderr=None, shell=True)
apkInfo = process.communicate()[0].splitlines()
for info in apkInfo:
#Package info:
if string.find(info, "package:", 0) != -1:
print "App Package: " + findBetween(info, "name='", "'")
print "App Version: " + findBetween(info, "versionName='", "'")
continue
#App name:
if string.find(info, "application:", 0) != -1:
print "App Name: " + findBetween(info, "label='", "'")
continue
def findBetween(s, prefix, suffix):
try:
start = s.index(prefix) + len(prefix)
end = s.index(suffix, start)
return s[start:end]
except ValueError:
return ""
如果您想要解析整个 AndroidManifest XML 树,您可以使用 xmltree 命令以类似的方式进行:
aapt dump xmltree package.apk AndroidManifest.xml
像以前一样使用 Python:
#Extract the AndroidManifest XML tree:
shellcommand = "aapt dump xmltree " + filepath + " AndroidManifest.xml"
process = subprocess.Popen(shellcommand, stdout=subprocess.PIPE, stderr=None, shell=True)
xmlTree = process.communicate()[0]
print "Number of Activities: " + str(xmlTree.count("activity"))
print "Number of Services: " + str(xmlTree.count("service"))
print "Number of BroadcastReceivers: " + str(xmlTree.count("receiver"))
【讨论】:
Android Rom 上是否一直存在这个工具?它是始终内置的吗? 如果你问我会更好。 :) 我在使用 apktool 和 AXMLPrinter2 时遇到了问题:有时它们会抛出异常等。aapt 每次都有效,而且用途更广泛。更不用说它是官方工具。【参考方案4】:借助最新的 SDK-Tools,您现在可以使用名为 apkanalyzer 的工具打印出 APK 的 AndroidManifest.xml(以及其他部分,例如资源)。
[android sdk]/tools/bin/apkanalyzer manifest print [app.apk]
apkanalyzer
【讨论】:
非常感谢!我已经在谷歌上搜索了好几天,并且不想要一些依赖于 python 或 perl 或 Java JAR 或你有什么的第三方解决方案。 考虑到当前的工具环境,这是最好的答案。【参考方案5】:您可以在android-random项目中使用不久前开发的axml2xml.pl工具。它将从二进制文件生成文本清单文件 (AndroidManifest.xml)。
我说的是“textual”而不是“original”,因为像许多逆向工程工具一样,这个工具并不完美,结果不会完整。我认为要么它从来没有功能完整,要么根本不向前兼容(使用更新的二进制编码方案)。无论是什么原因,axml2xml.pl 工具都无法正确提取所有属性值。这些属性是 minSdkVersion、targetSdkVersion 和基本上所有引用资源的属性(如字符串、图标等),即只有类名(活动、服务等)被正确提取。
但是,您仍然可以通过在原始 Android 应用文件 (.apk) 上运行 aapt 工具来找到这些缺失的信息:
aapt l -a
【讨论】:
谢谢@Shonzilla。我需要包名称和版本代码信息,aapt 可以完成这项工作。在使用 LAMP 时,我在 php 中运行 aapt 命令并使用 PHP 处理输出。 任何适用于 Android 的 Java/Kotlin 解决方案?【参考方案6】:apk-parser,https://github.com/caoqianli/apk-parser,一个轻量级的 java impl,不依赖 aapt 或其他二进制文件,适用于解析二进制 xml 文件和其他 apk 信息。
ApkParser apkParser = new ApkParser(new File(filePath));
// set a locale to translate resource tag into specific strings in language the locale specified, you set locale to Locale.ENGLISH then get apk title 'WeChat' instead of '@string/app_name' for example
apkParser.setPreferredLocale(locale);
String xml = apkParser.getManifestXml();
System.out.println(xml);
String xml2 = apkParser.transBinaryXml(xmlPathInApk);
System.out.println(xml2);
ApkMeta apkMeta = apkParser.getApkMeta();
System.out.println(apkMeta);
Set<Locale> locales = apkParser.getLocales();
for (Locale l : locales)
System.out.println(l);
apkParser.close();
【讨论】:
未测试。它应该可以工作,但确实有人报告了 android L 的问题。 我明白了。你能通过这种方式获得意图过滤器吗? Intent-filters可以通过解析manifest xml文件得到,现在没有直接的方法了。 “纯 java”,一个非常不幸的短语【参考方案7】:在正确解码属性的WPF Project 之后检查此内容。
【讨论】:
为此+1,谢谢!!!对于 C# 开发人员,我绝对推荐这个。节省了我很多时间 =) 它让我耽搁了一段时间,因为我必须运行“aapt”来检索版本号和包名称(这是不可能的,因为我的场景是在 Web 环境中并且用户在检索两者后需要反馈包名和版本号)。 您实际上可以轻松删除 PresentationCore 依赖项,它仅用于其 Color 类。您可以创建自己的,也可以使用 System.Drawing。 有这样的解决方案,但可以在 Android 应用程序中运行吗?【参考方案8】:如果您使用 Python 或使用 Androguard,Androguard Androaxml 功能将为您完成此转换。该功能在this blog post 中有详细说明,另外还有documentation here 和source here。
用法:
$ ./androaxml.py -h
Usage: androaxml.py [options]
Options:
-h, --help show this help message and exit
-i INPUT, --input=INPUT
filename input (APK or android's binary xml)
-o OUTPUT, --output=OUTPUT
filename output of the xml
-v, --version version of the API
$ ./androaxml.py -i yourfile.apk -o output.xml
$ ./androaxml.py -i AndroidManifest.xml -o output.xml
【讨论】:
【参考方案9】:如果有用,这里是 Ribo 发布的 Java sn-p 的 C++ 版本:
struct decompressXML
// decompressXML -- Parse the 'compressed' binary form of Android XML docs
// such as for AndroidManifest.xml in .apk files
enum
endDocTag = 0x00100101,
startTag = 0x00100102,
endTag = 0x00100103
;
decompressXML(const BYTE* xml, int cb)
// Compressed XML file/bytes starts with 24x bytes of data,
// 9 32 bit words in little endian order (LSB first):
// 0th word is 03 00 08 00
// 3rd word SEEMS TO BE: Offset at then of StringTable
// 4th word is: Number of strings in string table
// WARNING: Sometime I indiscriminently display or refer to word in
// little endian storage format, or in integer format (ie MSB first).
int numbStrings = LEW(xml, cb, 4*4);
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
int sitOff = 0x24; // Offset of start of StringIndexTable
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
int stOff = sitOff + numbStrings*4; // StringTable follows StrIndexTable
// XMLTags, The XML tag tree starts after some unknown content after the
// StringTable. There is some unknown data after the StringTable, scan
// forward from this point to the flag for the start of an XML start tag.
int xmlTagOff = LEW(xml, cb, 3*4); // Start from the offset in the 3rd word.
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
for (int ii=xmlTagOff; ii<cb-4; ii+=4)
if (LEW(xml, cb, ii) == startTag)
xmlTagOff = ii; break;
// end of hack, scanning for start of first start tag
// XML tags and attributes:
// Every XML start and end tag consists of 6 32 bit words:
// 0th word: 02011000 for startTag and 03011000 for endTag
// 1st word: a flag?, like 38000000
// 2nd word: Line of where this tag appeared in the original source file
// 3rd word: FFFFFFFF ??
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
// 5th word: StringIndex of Element Name
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
// Start tags (not end tags) contain 3 more words:
// 6th word: 14001400 meaning??
// 7th word: Number of Attributes that follow this tag(follow word 8th)
// 8th word: 00000000 meaning??
// Attributes consist of 5 words:
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
// 1st word: StringIndex of Attribute Name
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
// 3rd word: Flags?
// 4th word: str ind of attr value again, or ResourceId of value
// TMP, dump string table to tr for debugging
//tr.addSelect("strings", null);
//for (int ii=0; ii<numbStrings; ii++)
// // Length of string starts at StringTable plus offset in StrIndTable
// String str = compXmlString(xml, sitOff, stOff, ii);
// tr.add(String.valueOf(ii), str);
//
//tr.parent();
// Step through the XML tree element tags and attributes
int off = xmlTagOff;
int indent = 0;
int startTagLineNo = -2;
while (off < cb)
int tag0 = LEW(xml, cb, off);
//int tag1 = LEW(xml, off+1*4);
int lineNo = LEW(xml, cb, off+2*4);
//int tag3 = LEW(xml, off+3*4);
int nameNsSi = LEW(xml, cb, off+4*4);
int nameSi = LEW(xml, cb, off+5*4);
if (tag0 == startTag) // XML START TAG
int tag6 = LEW(xml, cb, off+6*4); // Expected to be 14001400
int numbAttrs = LEW(xml, cb, off+7*4); // Number of Attributes to follow
//int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
off += 9*4; // Skip over 6+3 words of startTag data
std::string name = compXmlString(xml, cb, sitOff, stOff, nameSi);
//tr.addSelect(name, null);
startTagLineNo = lineNo;
// Look for the Attributes
std::string sb;
for (int ii=0; ii<numbAttrs; ii++)
int attrNameNsSi = LEW(xml, cb, off); // AttrName Namespace Str Ind, or FFFFFFFF
int attrNameSi = LEW(xml, cb, off+1*4); // AttrName String Index
int attrValueSi = LEW(xml, cb, off+2*4); // AttrValue Str Ind, or FFFFFFFF
int attrFlags = LEW(xml, cb, off+3*4);
int attrResId = LEW(xml, cb, off+4*4); // AttrValue ResourceId or dup AttrValue StrInd
off += 5*4; // Skip over the 5 words of an attribute
std::string attrName = compXmlString(xml, cb, sitOff, stOff, attrNameSi);
std::string attrValue = attrValueSi!=-1
? compXmlString(xml, cb, sitOff, stOff, attrValueSi)
: "resourceID 0x"+toHexString(attrResId);
sb.append(" "+attrName+"=\""+attrValue+"\"");
//tr.add(attrName, attrValue);
prtIndent(indent, "<"+name+sb+">");
indent++;
else if (tag0 == endTag) // XML END TAG
indent--;
off += 6*4; // Skip over 6 words of endTag data
std::string name = compXmlString(xml, cb, sitOff, stOff, nameSi);
prtIndent(indent, "</"+name+"> (line "+toIntString(startTagLineNo)+"-"+toIntString(lineNo)+")");
//tr.parent(); // Step back up the NobTree
else if (tag0 == endDocTag) // END OF XML DOC TAG
break;
else
prt(" Unrecognized tag code '"+toHexString(tag0)
+"' at offset "+toIntString(off));
break;
// end of while loop scanning tags and attributes of XML tree
prt(" end at offset "+off);
// end of decompressXML
std::string compXmlString(const BYTE* xml, int cb, int sitOff, int stOff, int strInd)
if (strInd < 0) return std::string("");
int strOff = stOff + LEW(xml, cb, sitOff+strInd*4);
return compXmlStringAt(xml, cb, strOff);
void prt(std::string str)
printf("%s", str.c_str());
void prtIndent(int indent, std::string str)
char spaces[46];
memset(spaces, ' ', sizeof(spaces));
spaces[min(indent*2, sizeof(spaces) - 1)] = 0;
prt(spaces);
prt(str);
prt("\n");
// compXmlStringAt -- Return the string stored in StringTable format at
// offset strOff. This offset points to the 16 bit string length, which
// is followed by that number of 16 bit (Unicode) chars.
std::string compXmlStringAt(const BYTE* arr, int cb, int strOff)
if (cb < strOff + 2) return std::string("");
int strLen = arr[strOff+1]<<8&0xff00 | arr[strOff]&0xff;
char* chars = new char[strLen + 1];
chars[strLen] = 0;
for (int ii=0; ii<strLen; ii++)
if (cb < strOff + 2 + ii * 2)
chars[ii] = 0;
break;
chars[ii] = arr[strOff+2+ii*2];
std::string str(chars);
free(chars);
return str;
// end of compXmlStringAt
// LEW -- Return value of a Little Endian 32 bit word from the byte array
// at offset off.
int LEW(const BYTE* arr, int cb, int off)
return (cb > off + 3) ? ( arr[off+3]<<24&0xff000000 | arr[off+2]<<16&0xff0000
| arr[off+1]<<8&0xff00 | arr[off]&0xFF ) : 0;
// end of LEW
std::string toHexString(DWORD attrResId)
char ch[20];
sprintf_s(ch, 20, "%lx", attrResId);
return std::string(ch);
std::string toIntString(int i)
char ch[20];
sprintf_s(ch, 20, "%ld", i);
return std::string(ch);
;
【讨论】:
两个错误:在 compXMLStringAt 中:chars
由 new char[] 分配,但随后被释放,而不是正确的 delete[] chars;
。在 decompressXML ctor 的末尾必须是prt(" end at offset "+toIntString(off));
,否则使用指针算法...【参考方案10】:
@Mathieu Kotlin 版本如下:
fun main(args : Array<String>)
val fileName = "app.apk"
ZipFile(fileName).use zip ->
zip.entries().asSequence().forEach entry ->
if(entry.name == "AndroidManifest.xml")
zip.getInputStream(entry).use input ->
val xml = decompressXML(input.readBytes())
//TODO: parse the XML
println(xml)
/**
* Binary XML doc ending Tag
*/
var endDocTag = 0x00100101
/**
* Binary XML start Tag
*/
var startTag = 0x00100102
/**
* Binary XML end Tag
*/
var endTag = 0x00100103
/**
* Reference var for spacing
* Used in prtIndent()
*/
var spaces = " "
/**
* Parse the 'compressed' binary form of Android XML docs
* such as for AndroidManifest.xml in .apk files
* Source: http://***.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689
*
* @param xml Encoded XML content to decompress
*/
fun decompressXML(xml: ByteArray): String
val resultXml = StringBuilder()
// Compressed XML file/bytes starts with 24x bytes of data,
// 9 32 bit words in little endian order (LSB first):
// 0th word is 03 00 08 00
// 3rd word SEEMS TO BE: Offset at then of StringTable
// 4th word is: Number of strings in string table
// WARNING: Sometime I indiscriminently display or refer to word in
// little endian storage format, or in integer format (ie MSB first).
val numbStrings = LEW(xml, 4 * 4)
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
val sitOff = 0x24 // Offset of start of StringIndexTable
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
val stOff = sitOff + numbStrings * 4 // StringTable follows StrIndexTable
// XMLTags, The XML tag tree starts after some unknown content after the
// StringTable. There is some unknown data after the StringTable, scan
// forward from this point to the flag for the start of an XML start tag.
var xmlTagOff = LEW(xml, 3 * 4) // Start from the offset in the 3rd word.
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
run
var ii = xmlTagOff
while (ii < xml.size - 4)
if (LEW(xml, ii) == startTag)
xmlTagOff = ii
break
ii += 4
// end of hack, scanning for start of first start tag
// XML tags and attributes:
// Every XML start and end tag consists of 6 32 bit words:
// 0th word: 02011000 for startTag and 03011000 for endTag
// 1st word: a flag?, like 38000000
// 2nd word: Line of where this tag appeared in the original source file
// 3rd word: FFFFFFFF ??
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
// 5th word: StringIndex of Element Name
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
// Start tags (not end tags) contain 3 more words:
// 6th word: 14001400 meaning??
// 7th word: Number of Attributes that follow this tag(follow word 8th)
// 8th word: 00000000 meaning??
// Attributes consist of 5 words:
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
// 1st word: StringIndex of Attribute Name
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
// 3rd word: Flags?
// 4th word: str ind of attr value again, or ResourceId of value
// TMP, dump string table to tr for debugging
//tr.addSelect("strings", null);
//for (int ii=0; ii<numbStrings; ii++)
// // Length of string starts at StringTable plus offset in StrIndTable
// String str = compXmlString(xml, sitOff, stOff, ii);
// tr.add(String.valueOf(ii), str);
//
//tr.parent();
// Step through the XML tree element tags and attributes
var off = xmlTagOff
var indent = 0
var startTagLineNo = -2
while (off < xml.size)
val tag0 = LEW(xml, off)
//int tag1 = LEW(xml, off+1*4);
val lineNo = LEW(xml, off + 2 * 4)
//int tag3 = LEW(xml, off+3*4);
val nameNsSi = LEW(xml, off + 4 * 4)
val nameSi = LEW(xml, off + 5 * 4)
if (tag0 == startTag) // XML START TAG
val tag6 = LEW(xml, off + 6 * 4) // Expected to be 14001400
val numbAttrs = LEW(xml, off + 7 * 4) // Number of Attributes to follow
//int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
off += 9 * 4 // Skip over 6+3 words of startTag data
val name = compXmlString(xml, sitOff, stOff, nameSi)
//tr.addSelect(name, null);
startTagLineNo = lineNo
// Look for the Attributes
val sb = StringBuffer()
for (ii in 0 until numbAttrs)
val attrNameNsSi = LEW(xml, off) // AttrName Namespace Str Ind, or FFFFFFFF
val attrNameSi = LEW(xml, off + 1 * 4) // AttrName String Index
val attrValueSi = LEW(xml, off + 2 * 4) // AttrValue Str Ind, or FFFFFFFF
val attrFlags = LEW(xml, off + 3 * 4)
val attrResId = LEW(xml, off + 4 * 4) // AttrValue ResourceId or dup AttrValue StrInd
off += 5 * 4 // Skip over the 5 words of an attribute
val attrName = compXmlString(xml, sitOff, stOff, attrNameSi)
val attrValue = if (attrValueSi != -1)
compXmlString(xml, sitOff, stOff, attrValueSi)
else
"resourceID 0x" + Integer.toHexString(attrResId)
sb.append(" $attrName=\"$attrValue\"")
//tr.add(attrName, attrValue);
resultXml.append(prtIndent(indent, "<$name$sb>"))
indent++
else if (tag0 == endTag) // XML END TAG
indent--
off += 6 * 4 // Skip over 6 words of endTag data
val name = compXmlString(xml, sitOff, stOff, nameSi)
resultXml.append(prtIndent(indent, "</$name> (line $startTagLineNo-$lineNo)"))
//tr.parent(); // Step back up the NobTree
else if (tag0 == endDocTag) // END OF XML DOC TAG
break
else
println(" Unrecognized tag code '" + Integer.toHexString(tag0)
+ "' at offset " + off
)
break
// end of while loop scanning tags and attributes of XML tree
println(" end at offset $off")
return resultXml.toString()
// end of decompressXML
/**
* Tool Method for decompressXML();
* Compute binary XML to its string format
* Source: Source: http://***.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689
*
* @param xml Binary-formatted XML
* @param sitOff
* @param stOff
* @param strInd
* @return String-formatted XML
*/
fun compXmlString(xml: ByteArray, sitOff: Int, stOff: Int, strInd: Int): String?
if (strInd < 0) return null
val strOff = stOff + LEW(xml, sitOff + strInd * 4)
return compXmlStringAt(xml, strOff)
/**
* Tool Method for decompressXML();
* Apply indentation
*
* @param indent Indentation level
* @param str String to indent
* @return Indented string
*/
fun prtIndent(indent: Int, str: String): String
return spaces.substring(0, Math.min(indent * 2, spaces.length)) + str
/**
* Tool method for decompressXML()
* Return the string stored in StringTable format at
* offset strOff. This offset points to the 16 bit string length, which
* is followed by that number of 16 bit (Unicode) chars.
*
* @param arr StringTable array
* @param strOff Offset to get string from
* @return String from StringTable at offset strOff
*/
fun compXmlStringAt(arr: ByteArray, strOff: Int): String
val strLen = (arr[strOff + 1] shl (8 and 0xff00)) or (arr[strOff].toInt() and 0xff)
val chars = ByteArray(strLen)
for (ii in 0 until strLen)
chars[ii] = arr[strOff + 2 + ii * 2]
return String(chars) // Hack, just use 8 byte chars
// end of compXmlStringAt
/**
* Return value of a Little Endian 32 bit word from the byte array
* at offset off.
*
* @param arr Byte array with 32 bit word
* @param off Offset to get word from
* @return Value of Little Endian 32 bit word specified
*/
fun LEW(arr: ByteArray, off: Int): Int
return (arr[off + 3] shl 24 and -0x1000000 or ((arr[off + 2] shl 16) and 0xff0000)
or (arr[off + 1] shl 8 and 0xff00) or (arr[off].toInt() and 0xFF))
// end of LEW
private infix fun Byte.shl(i: Int): Int = (this.toInt() shl i)
private infix fun Int.shl(i: Int): Int = (this shl i)
这是上述答案的 kotlin 版本。
【讨论】:
遗憾的是,这似乎在一些罕见的情况下存在问题。见这里:***.com/q/60565299/878126【参考方案11】:在 Android Studio 2.2 中,您可以直接分析 apk。转到构建 - 分析 apk。选择 apk,导航到 androidmanifest.xml。可以查看androidmanifest的详细信息。
【讨论】:
你也可以直接通过File->Open...打开APK 【参考方案12】:这里是我的 Ribo 代码版本供参考。主要区别在于 decompressXML() 直接返回一个字符串,这对我来说是更合适的用法。
注意:我使用 Ribo 解决方案的唯一目的是从 Manifest XML 文件中获取 .APK 文件的已发布版本,我确认为此目的它运行良好。
编辑 [2013-03-16]:效果很好 如果版本设置为纯文本,但如果设置为引用资源 XML,它将显示为“资源”例如 0x1'。在这种特殊情况下,您可能必须将此解决方案与另一个将获取正确字符串资源引用的解决方案结合起来。
/**
* Binary XML doc ending Tag
*/
public static int endDocTag = 0x00100101;
/**
* Binary XML start Tag
*/
public static int startTag = 0x00100102;
/**
* Binary XML end Tag
*/
public static int endTag = 0x00100103;
/**
* Reference var for spacing
* Used in prtIndent()
*/
public static String spaces = " ";
/**
* Parse the 'compressed' binary form of Android XML docs
* such as for AndroidManifest.xml in .apk files
* Source: http://***.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689
*
* @param xml Encoded XML content to decompress
*/
public static String decompressXML(byte[] xml)
StringBuilder resultXml = new StringBuilder();
// Compressed XML file/bytes starts with 24x bytes of data,
// 9 32 bit words in little endian order (LSB first):
// 0th word is 03 00 08 00
// 3rd word SEEMS TO BE: Offset at then of StringTable
// 4th word is: Number of strings in string table
// WARNING: Sometime I indiscriminently display or refer to word in
// little endian storage format, or in integer format (ie MSB first).
int numbStrings = LEW(xml, 4*4);
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
int sitOff = 0x24; // Offset of start of StringIndexTable
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
int stOff = sitOff + numbStrings*4; // StringTable follows StrIndexTable
// XMLTags, The XML tag tree starts after some unknown content after the
// StringTable. There is some unknown data after the StringTable, scan
// forward from this point to the flag for the start of an XML start tag.
int xmlTagOff = LEW(xml, 3*4); // Start from the offset in the 3rd word.
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
for (int ii=xmlTagOff; ii<xml.length-4; ii+=4)
if (LEW(xml, ii) == startTag)
xmlTagOff = ii; break;
// end of hack, scanning for start of first start tag
// XML tags and attributes:
// Every XML start and end tag consists of 6 32 bit words:
// 0th word: 02011000 for startTag and 03011000 for endTag
// 1st word: a flag?, like 38000000
// 2nd word: Line of where this tag appeared in the original source file
// 3rd word: FFFFFFFF ??
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
// 5th word: StringIndex of Element Name
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
// Start tags (not end tags) contain 3 more words:
// 6th word: 14001400 meaning??
// 7th word: Number of Attributes that follow this tag(follow word 8th)
// 8th word: 00000000 meaning??
// Attributes consist of 5 words:
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
// 1st word: StringIndex of Attribute Name
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
// 3rd word: Flags?
// 4th word: str ind of attr value again, or ResourceId of value
// TMP, dump string table to tr for debugging
//tr.addSelect("strings", null);
//for (int ii=0; ii<numbStrings; ii++)
// // Length of string starts at StringTable plus offset in StrIndTable
// String str = compXmlString(xml, sitOff, stOff, ii);
// tr.add(String.valueOf(ii), str);
//
//tr.parent();
// Step through the XML tree element tags and attributes
int off = xmlTagOff;
int indent = 0;
int startTagLineNo = -2;
while (off < xml.length)
int tag0 = LEW(xml, off);
//int tag1 = LEW(xml, off+1*4);
int lineNo = LEW(xml, off+2*4);
//int tag3 = LEW(xml, off+3*4);
int nameNsSi = LEW(xml, off+4*4);
int nameSi = LEW(xml, off+5*4);
if (tag0 == startTag) // XML START TAG
int tag6 = LEW(xml, off+6*4); // Expected to be 14001400
int numbAttrs = LEW(xml, off+7*4); // Number of Attributes to follow
//int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
off += 9*4; // Skip over 6+3 words of startTag data
String name = compXmlString(xml, sitOff, stOff, nameSi);
//tr.addSelect(name, null);
startTagLineNo = lineNo;
// Look for the Attributes
StringBuffer sb = new StringBuffer();
for (int ii=0; ii<numbAttrs; ii++)
int attrNameNsSi = LEW(xml, off); // AttrName Namespace Str Ind, or FFFFFFFF
int attrNameSi = LEW(xml, off+1*4); // AttrName String Index
int attrValueSi = LEW(xml, off+2*4); // AttrValue Str Ind, or FFFFFFFF
int attrFlags = LEW(xml, off+3*4);
int attrResId = LEW(xml, off+4*4); // AttrValue ResourceId or dup AttrValue StrInd
off += 5*4; // Skip over the 5 words of an attribute
String attrName = compXmlString(xml, sitOff, stOff, attrNameSi);
String attrValue = attrValueSi!=-1
? compXmlString(xml, sitOff, stOff, attrValueSi)
: "resourceID 0x"+Integer.toHexString(attrResId);
sb.append(" "+attrName+"=\""+attrValue+"\"");
//tr.add(attrName, attrValue);
resultXml.append(prtIndent(indent, "<"+name+sb+">"));
indent++;
else if (tag0 == endTag) // XML END TAG
indent--;
off += 6*4; // Skip over 6 words of endTag data
String name = compXmlString(xml, sitOff, stOff, nameSi);
resultXml.append(prtIndent(indent, "</"+name+"> (line "+startTagLineNo+"-"+lineNo+")"));
//tr.parent(); // Step back up the NobTree
else if (tag0 == endDocTag) // END OF XML DOC TAG
break;
else
Log.e(TAG, " Unrecognized tag code '"+Integer.toHexString(tag0)
+"' at offset "+off);
break;
// end of while loop scanning tags and attributes of XML tree
Log.i(TAG, " end at offset "+off);
return resultXml.toString();
// end of decompressXML
/**
* Tool Method for decompressXML();
* Compute binary XML to its string format
* Source: Source: http://***.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689
*
* @param xml Binary-formatted XML
* @param sitOff
* @param stOff
* @param strInd
* @return String-formatted XML
*/
public static String compXmlString(byte[] xml, int sitOff, int stOff, int strInd)
if (strInd < 0) return null;
int strOff = stOff + LEW(xml, sitOff+strInd*4);
return compXmlStringAt(xml, strOff);
/**
* Tool Method for decompressXML();
* Apply indentation
*
* @param indent Indentation level
* @param str String to indent
* @return Indented string
*/
public static String prtIndent(int indent, String str)
return (spaces.substring(0, Math.min(indent*2, spaces.length()))+str);
/**
* Tool method for decompressXML()
* Return the string stored in StringTable format at
* offset strOff. This offset points to the 16 bit string length, which
* is followed by that number of 16 bit (Unicode) chars.
*
* @param arr StringTable array
* @param strOff Offset to get string from
* @return String from StringTable at offset strOff
*
*/
public static String compXmlStringAt(byte[] arr, int strOff)
int strLen = arr[strOff+1]<<8&0xff00 | arr[strOff]&0xff;
byte[] chars = new byte[strLen];
for (int ii=0; ii<strLen; ii++)
chars[ii] = arr[strOff+2+ii*2];
return new String(chars); // Hack, just use 8 byte chars
// end of compXmlStringAt
/**
* Return value of a Little Endian 32 bit word from the byte array
* at offset off.
*
* @param arr Byte array with 32 bit word
* @param off Offset to get word from
* @return Value of Little Endian 32 bit word specified
*/
public static int LEW(byte[] arr, int off)
return arr[off+3]<<24&0xff000000 | arr[off+2]<<16&0xff0000
| arr[off+1]<<8&0xff00 | arr[off]&0xFF;
// end of LEW
希望它也可以帮助其他人。
【讨论】:
如果 APK 的清单文件引用 String 资源 xml 作为版本,您的此代码将失败。就我而言,我从github.com/stephanenicolas/RoboDemo/robodemo-sample-1.0.1.apk/… 下载了一个 APK 并在上面运行了您的代码。它不打印版本,而是打印资源 ID。即“resourceID 0x1”,这是无用的,要找出该资源 id,我们需要另一个可以找出该资源文件并反编译它的程序。 这完全有道理。老实说,我没有想到可以在 Resource XML 而不是纯文本中引用该版本。我将编辑我的帖子以涵盖该特殊性。 你能告诉我如何解码 String.xml 并找出那个特定的资源 ID。我想在我的大学项目中使用它。创建构建管理系统 @Cheeta 老实说我不知道比你多。我只是拿了 Ribo 的代码并根据我的特定需求对其进行了修改,然后分享它以防其他人受益。我建议寻找一种专门用于从 .APK 中检索字符串资源的解决方案,并将其与我在此处发布的解决方案结合使用。祝你好运!【参考方案13】:我在 Android4Me 项目中发现了 AXMLPrinter2,这是一个 Java 应用程序,可以在我拥有的 AndroidManifest.xml 上正常工作(并以格式良好的方式打印 XML)。 http://code.google.com/p/android4me/downloads/detail?name=AXMLPrinter2.jar
注意.. 它(以及来自 Ribo 的此答案的代码)似乎无法处理我遇到的每个已编译的 XML 文件。我发现一个字符串以每个字符一个字节的形式存储,而不是它假定的双字节格式。
【讨论】:
我无法访问此链接。有什么选择吗?【参考方案14】:我已经使用上面发布的 Ribo 代码一年多了,它对我们很有帮助。但是,随着最近的更新(Gradle 3.x),我不再能够解析 AndroidManifest.xml,出现索引越界错误,并且通常它不再能够解析文件。
更新:我现在认为我们的问题在于升级到 Gradle 3.x。本文介绍 AirWatch 如何出现问题,可以通过使用 Gradle 设置来解决问题,以使用 aapt 而不是 aapt2 AirWatch seems to be incompatible with Android Plugin for Gradle 3.0.0-beta1
在四处搜索时,我发现了这个开源项目,它正在被维护,我能够直截了当地阅读我以前可以解析的旧 APK,以及来自 Ribo 的逻辑引发异常的新 APK
https://github.com/xgouchet/AXML
从他的例子来看,这就是我正在做的事情
zf = new ZipFile(apkFile);
//Getting the manifest
ZipEntry entry = zf.getEntry("AndroidManifest.xml");
InputStream is = zf.getInputStream(entry);
// Read our manifest Document
Document manifestDoc = new CompressedXmlParser().parseDOM(is);
// Make sure we got a doc, and that it has children
if (null != manifestDoc && manifestDoc.getChildNodes().getLength() > 0)
//
Node firstNode = manifestDoc.getFirstChild();
// Now get the attributes out of the node
NamedNodeMap nodeMap = firstNode.getAttributes();
// Finally to a point where we can read out our values
versionName = nodeMap.getNamedItem("android:versionName").getNodeValue();
versionCode = nodeMap.getNamedItem("android:versionCode").getNodeValue();
【讨论】:
【参考方案15】:apkanalyzer 会很有帮助
@echo off
::##############################################################################
::##
::## apkanalyzer start up script for Windows
::##
::## converted by ewwink
::##
::##############################################################################
::Attempt to set APP_HOME
SET SAVED=%cd%
SET APP_HOME=C:\android\sdk\tools
SET APP_NAME="apkanalyzer"
::Add default JVM options here. You can also use JAVA_OPTS and APKANALYZER_OPTS to pass JVM options to this script.
SET DEFAULT_JVM_OPTS=-Dcom.android.sdklib.toolsdir=%APP_HOME%
SET CLASSPATH=%APP_HOME%\lib\dvlib-26.0.0-dev.jar;%APP_HOME%\lib\util-2.2.1.jar;%APP_HOME%\lib\jimfs-1.1.jar;%APP_HOME%\lib\annotations-13.0.jar;%APP_HOME%\lib\ddmlib-26.0.0-dev.jar;%APP_HOME%\lib\repository-26.0.0-dev.jar;%APP_HOME%\lib\sdk-common-26.0.0-dev.jar;%APP_HOME%\lib\kotlin-stdlib-1.1.3-2.jar;%APP_HOME%\lib\protobuf-java-3.0.0.jar;%APP_HOME%\lib\apkanalyzer-cli.jar;%APP_HOME%\lib\gson-2.3.jar;%APP_HOME%\lib\httpcore-4.2.5.jar;%APP_HOME%\lib\dexlib2-2.2.1.jar;%APP_HOME%\lib\commons-compress-1.12.jar;%APP_HOME%\lib\generator.jar;%APP_HOME%\lib\error_prone_annotations-2.0.18.jar;%APP_HOME%\lib\commons-codec-1.6.jar;%APP_HOME%\lib\kxml2-2.3.0.jar;%APP_HOME%\lib\httpmime-4.1.jar;%APP_HOME%\lib\annotations-12.0.jar;%APP_HOME%\lib\bcpkix-jdk15on-1.56.jar;%APP_HOME%\lib\jsr305-3.0.0.jar;%APP_HOME%\lib\explainer.jar;%APP_HOME%\lib\builder-model-3.0.0-dev.jar;%APP_HOME%\lib\baksmali-2.2.1.jar;%APP_HOME%\lib\j2objc-annotations-1.1.jar;%APP_HOME%\lib\layoutlib-api-26.0.0-dev.jar;%APP_HOME%\lib\jcommander-1.64.jar;%APP_HOME%\lib\commons-logging-1.1.1.jar;%APP_HOME%\lib\annotations-26.0.0-dev.jar;%APP_HOME%\lib\builder-test-api-3.0.0-dev.jar;%APP_HOME%\lib\animal-sniffer-annotations-1.14.jar;%APP_HOME%\lib\bcprov-jdk15on-1.56.jar;%APP_HOME%\lib\httpclient-4.2.6.jar;%APP_HOME%\lib\common-26.0.0-dev.jar;%APP_HOME%\lib\jopt-simple-4.9.jar;%APP_HOME%\lib\sdklib-26.0.0-dev.jar;%APP_HOME%\lib\apkanalyzer.jar;%APP_HOME%\lib\shared.jar;%APP_HOME%\lib\binary-resources.jar;%APP_HOME%\lib\guava-22.0.jar
SET APP_ARGS=%*
::Collect all arguments for the java command, following the shell quoting and substitution rules
SET APKANALYZER_OPTS=%DEFAULT_JVM_OPTS% -classpath %CLASSPATH% com.android.tools.apk.analyzer.ApkAnalyzerCli %APP_ARGS%
::Determine the Java command to use to start the JVM.
SET JAVACMD="java"
where %JAVACMD% >nul 2>nul
if %errorlevel%==1 (
echo ERROR: 'java' command could be found in your PATH.
echo Please set the 'java' variable in your environment to match the
echo location of your Java installation.
echo.
exit /b 0
)
:: execute apkanalyzer
%JAVACMD% %APKANALYZER_OPTS%
原帖https://***.com/a/51905063/1383521
【讨论】:
参考@Alther 回答【参考方案16】:很有帮助
public static int vCodeApk(String path)
PackageManager pm = G.context.getPackageManager();
PackageInfo info = pm.getPackageArchiveInfo(path, 0);
return info.versionCode;
// Toast.makeText(this, "VersionCode : " + info.versionCode + ", VersionName : " + info.versionName, Toast.LENGTH_LONG).show();
G 是我的应用程序类:
public class G extends Application
【讨论】:
以上是关于如何解析 .apk 包中的 AndroidManifest.xml 文件的主要内容,如果未能解决你的问题,请参考以下文章
如何扫描Android APK依赖的Jar包中哪些文件包含指定字符
调试 APK 工作正常,包中的 APKS 工作正常,但发布包不能正常工作