我有一个 .net 网络服务,它应该知道apk
其文件夹之一中文件的版本。我知道apktool
,但我需要apk
通过我的 .net 网络服务阅读清单。
我怎样才能做到这一点?你知道 C# 中用于解码和读取apk
文件清单的任何示例代码吗?
解压缩apk
并确定清单文件是我的主要问题
我有一个 .net 网络服务,它应该知道apk
其文件夹之一中文件的版本。我知道apktool
,但我需要apk
通过我的 .net 网络服务阅读清单。
我怎样才能做到这一点?你知道 C# 中用于解码和读取apk
文件清单的任何示例代码吗?
解压缩apk
并确定清单文件是我的主要问题
我使用了SharpZipLib和这个答案并创建了一个 .Net 版本。
string apkPath = "C:\\app.apk";
ICSharpCode.SharpZipLib.Zip.ZipInputStream zip = new ICSharpCode.SharpZipLib.Zip.ZipInputStream(File.OpenRead(apkPath));
var filestream = new FileStream(apkPath, FileMode.Open, FileAccess.Read);
ICSharpCode.SharpZipLib.Zip.ZipFile zipfile = new ICSharpCode.SharpZipLib.Zip.ZipFile(filestream);
ICSharpCode.SharpZipLib.Zip.ZipEntry item;
while ((item = zip.GetNextEntry()) != null)
{
if (item.Name == "AndroidManifest.xml")
{
byte[] bytes = new byte[50 * 1024];
Stream strm = zipfile.GetInputStream(item);
int size = strm.Read(bytes, 0, bytes.Length);
using (BinaryReader s = new BinaryReader(strm))
{
byte[] bytes2 = new byte[size];
Array.Copy(bytes, bytes2, size);
AndroidDecompress decompress = new AndroidDecompress();
content = decompress.decompressXML(bytes);
}
}
}
和
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
/// <summary>
/// Summary description for AndroidDecompress
/// </summary>
public class AndroidDecompress
{
private string result = "";
// 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 string 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
string sb = "";
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" + */attrResId.ToString();
sb += " " + 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 + "> \r\n"/*+"(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 '" + tag0.ToString("X")
+ "' at offset " + off);
break;
}
} // end of while loop scanning tags and attributes of XML tree
//prt(" end at offset " + off);
return result;
} // 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);
}
private void prt(string p)
{
result += p;
}
// 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 System.Text.Encoding.UTF8.GetString(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 (int)(arr[off + 3] << 24 & 0xff000000 | arr[off + 2] << 16 & 0xff0000 | arr[off + 1] << 8 & 0xff00 | arr[off] & 0xFF);
} // end of LEW
}
我创建了一个 .NET 库,它解析/读取androidManifest.xml文件数据并交叉引用Resources.arsc以获取参考 Id 值(因此您不必这样做)。
这个库的主要目标是解析 Android 应用程序包元数据。在许多情况下,您可能需要收集有关您的 android 包的信息,特别是在持续集成设置中。
解压缩文件后,只需将所需的字节数组对象传递给库的方法:
ApkReader apkReader = new ApkReader();
ApkInfo info = apkReader.extractInfo(manifestData, resourcesData);
该APKInfo
对象提供:
该答案不会向现有功能添加任何特定功能。
但是我对移植的 Java 代码的可读性和惯用性并不满意。
所以我从@Bobs 发布的代码开始,对它进行了重构,直到我对结果感到满意为止。但是,除了我的强迫症,基本上是一样的:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
// ReSharper disable CommentTypo
namespace Utils
{
/// <summary> Unpacks and reads data from a compressed apk manifest file. </summary>
/// <remarks>
/// Code coming from:
/// https://stackoverflow.com/questions/18997163/how-can-i-read-the-manifest-of-an-android-apk-file-using-c-sharp-net/22314629
/// </remarks>
public class AndroidManifestReader
{
// Parses the 'compressed' binary form of Android XML docs
// such as for AndroidManifest.binaryXml in .apk files
private const int END_DOC_TAG = 0x00100101;
private const int START_TAG = 0x00100102;
private const int END_TAG = 0x00100103;
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
private const int STRING_INDEX_TABLE_OFFSET = 0x24;
private readonly byte[] _xml;
private XDocument _manifest;
/// <summary>Reads and returns the uncompressed Xml Manifest</summary>
public XDocument Manifest => _manifest ?? (_manifest = ReadManifest());
public AndroidManifestReader(byte[] xml)
{
_xml = xml ?? throw new ArgumentNullException(nameof(xml));
}
private XDocument ReadManifest()
{
var result = new XDocument();
result.Add(new XElement("root"));
var tagStack = new Stack<XElement>();
tagStack.Push(result.Root);
var tagOffset = FindStartOfTags();
while (tagOffset < _xml.Length)
{
var tagCode = BitConverter.ToInt32(_xml, tagOffset);
switch (tagCode)
{
case START_TAG:
tagOffset += ReadStartTag(tagOffset, out var startTag);
tagStack.Peek().Add(startTag);
tagStack.Push(startTag);
break;
case END_TAG:
var expectedTagName = tagStack.Peek().Name.LocalName;
tagOffset += ReadEndTag(tagOffset, expectedTagName);
tagStack.Pop();
break;
case END_DOC_TAG:
goto manifest_read;
default:
goto manifest_read;
}
}
manifest_read:
return result;
}
/// <summary>Reads a start tag and returns the number of consumed bytes.</summary>
/// <param name="offset"></param>
/// <param name="element"></param>
/// <remarks>
/// XML tags and attributes:
/// Every XML start and end tag consists of 6 32 bit words:
/// 0th word: 02011000 for startTag, 03011000 for endTag, 01011000 for end of document
/// 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
///
/// 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??
/// </remarks>
/// <returns>The number of bytes consumed by the tag.</returns>
private int ReadStartTag(int offset, out XElement element)
{
const int startTagDataSize = 9 * 4;
element = new XElement(ReadTagName(offset));
var bytesConsumed = startTagDataSize;
var attributesCount = BitConverter.ToInt32(_xml, offset + 7 * 4);
for (var attrIdx = 0; attrIdx < attributesCount; attrIdx++)
{
bytesConsumed += ReadAttribute(offset + bytesConsumed, out var attr);
element.Add(attr);
}
return bytesConsumed;
}
/// <summary>Reads an attribute starting at the specified offset,
/// and returns the consumed bytes count.</summary>
/// <remarks>
/// 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
/// </remarks>
private int ReadAttribute(int offset, out XAttribute attribute)
{
var attributeNameStringIndex = BitConverter.ToInt32(_xml, offset + 1 * 4);
var attrName = RetrieveFromStringTable(attributeNameStringIndex);
var attributeValueStringIndex = BitConverter.ToInt32(_xml, offset + 2 * 4);
// AttrValue ResourceId or dup AttrValue StrInd
var attributeResourceId = BitConverter.ToInt32(_xml, offset + 4 * 4);
var attrValue = attributeValueStringIndex >= 0
? RetrieveFromStringTable(attributeValueStringIndex)
: attributeResourceId.ToString();
attribute = new XAttribute(attrName, attrValue);
return 20;
}
private int ReadEndTag(int tagOffset, string expectedTagName)
{
var tagName = ReadTagName(tagOffset);
// Skip over 6 words of endTag data
return tagName == expectedTagName
? 6 * 4
: throw new InvalidOperationException(
$"Malformed XML: expecting {expectedTagName} but found {tagName}");
}
private string ReadTagName(int tagOffset)
{
var nameIndexOffset = tagOffset + 5 * 4;
var nameStringIndex = BitConverter.ToInt32(_xml, nameIndexOffset);
return RetrieveFromStringTable(nameStringIndex);
}
private int GetStringTableOffset()
{
// 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
var stringsCount = BitConverter.ToInt32(_xml, 4 * 4);
return STRING_INDEX_TABLE_OFFSET + stringsCount * 4;
}
/// <summary> 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. </summary>
/// <returns></returns>
private string RetrieveFromStringTable(int strInd)
{
if (strInd < 0) return null;
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
// StringTable follows StrIndexTable
var stringOffset = GetStringTableOffset() +
BitConverter.ToInt32(_xml, STRING_INDEX_TABLE_OFFSET + strInd * 4);
var stringLength = BitConverter.ToInt16(_xml, stringOffset);
return Encoding.Unicode.GetString(_xml, stringOffset + 2, stringLength * 2);
}
/// <summary> 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. </summary>
/// <returns></returns>
internal int FindStartOfTags()
{
// 12 is the index of the word containing the base offset of the xml tree.
var xmlTagOffset = BitConverter.ToInt32(_xml, 12);
for (var offset = xmlTagOffset; offset < _xml.Length - 4; offset += 4)
{
if (BitConverter.ToInt32(_xml, offset) == START_TAG) return offset;
}
return xmlTagOffset;
}
}
}
var manifest = new AndroidManifestReader(bytes).Manifest;