Android 解析AndroidManifest.xml文件

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 1. 格式解析1). AndroidManifest.xml文件格式看雪大神--原图地址图1.png图中可以清晰的看出AndroidManifest.

1. 格式解析

1). AndroidManifest.xml文件格式

看雪大神--原图地址

img_b31af6afc5a11b2606154c9eb9f682d8.png
图1.png

图中可以清晰的看出AndroidManifest.xml文件的格式,几个大的模块:

  • Magic Number 魔数
  • File Size 文件大小
  • String Chunk 字符串模块
  • ResourceId Chunk 资源模块
  • XmlContent Chunk XML模块
2). 解析--主流程
public class ParseMain {
    /**
     * 应用主入口
     */
    public static void main(String[] args) {
        // 读取AndroidManifest.xml文件为二进制数据
        byte[] byteSrc = Util.readXML("xml/AndroidManifest.xml");
        // 解析XML 头
        System.out.println("Parse XML Header -----------");
        ParseChunkUtil.parseXmlHeader(byteSrc);
        System.out.println();
        // 解析String Chunk
        System.out.println("Parse String Chunk -----------");
        ParseChunkUtil.parseStringChunk(byteSrc);
        System.out.println();
        // 解析Resource Chunk
        System.out.println("Parse Resource Chunk -----------");
        ParseChunkUtil.parseResourceChunk(byteSrc);
        System.out.println();
        // 解析XML内容
        System.out.println("Parse XML Content -----------");
        ParseChunkUtil.parseXmlContent(byteSrc);
        System.out.println();
        // 输出XML文件
        ParseChunkUtil.writeFormatXmlToFile();
    }

}
3). 头部
  • Magic Number 魔数:4个字节
  • File Size 文件大小:4个字节
    /** XML文件内容 */
    private static StringBuilder builder = new StringBuilder();
    /**
     * 解析xml的头部信息 
     *  1. Magic Number: 文件魔数,4个字节 
     *  2. File Size: 文件大小,4个字节
     * 
     * @param byteSrc
     */
    public static void parseXmlHeader(byte[] byteSrc) {
        // 1. Magic Number: 文件魔数,4个字节
        byte[] magic = Util.copyByte(byteSrc, 0, 4);
        System.out.println("magic number:" + Util.byteToHexString(magic));
        // 2. File Size: 文件大小,4个字节
        byte[] size = Util.copyByte(byteSrc, 4, 4);
        System.out.println("xml size:" + Util.byteToHexString(size));

        // 拼接文件内容
        builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
        builder.append("\n");
    }
4). String Chunk 解析
    /** String Chunk 偏移量 */
    private static int STRING_CHUNK_OFFSET = 8;
    /** 字符串列表 */
    private static ArrayList<String> stringContentList;
    /** Resource Chunk 偏移量,动态计算 */
    private static int resourceChunkOffset;
    /**
     * 解析String Chunk 
     *  1. ChunkType:StringChunk的类型,固定四个字节:0x001C0001 
     *  2. ChunkSize:StringChunk的大小,四个字节 
     *  3. StringCount:StringChunk中字符串的个数,四个字节 
     *  4. StyleCount:StringChunk中样式的个数,四个字节,但是在实际解析过程中,这个值一直是0x00000000 
     *  5. Unknown:位置区域,四个字节,在解析的过程中,这里需要略过四个字节 
     *  6. StringPoolOffset:字符串池的偏移值,四个字节,这个偏移值是相对于StringChunk的头部位置 
     *  7. StylePoolOffset:样式池的偏移值,四个字节,这里没有Style,所以这个字段可忽略 
     *  8. StringOffsets:每个字符串的偏移值,所以他的大小应该是:StringCount*4个字节 
     *  9. SytleOffsets:每个样式的偏移值,所以他的大小应该是SytleCount*4个字节 
     *  10. String Pool 
     *  11. Style Pool
     * 
     * @param byteSrc 二进制数据
     */
    public static void parseStringChunk(byte[] byteSrc) {
        // 1. Chunk Type:String Chunk的类型,固定四个字节:0x001C0001
        byte[] type = Util.copyByte(byteSrc, STRING_CHUNK_OFFSET, 4);
        System.out.println("string chunktag:" + Util.byteToHexString(type));

        // 2. Chunk Size:String Chunk的大小,四个字节
        byte[] sizeByte = Util.copyByte(byteSrc, STRING_CHUNK_OFFSET + 4, 4);
        // 获取String Chunk大小
        int chunkSize = Util.byte2Int(sizeByte);
        System.out.println("chunk size:" + chunkSize);

        // 3. String Count:StringChunk中字符串的个数,四个字节
        byte[] stringCountByte = Util.copyByte(byteSrc, STRING_CHUNK_OFFSET + 8, 4);
        // 获取字符串个数
        int chunkStringCount = Util.byte2Int(stringCountByte);
        System.out.println("string count:" + chunkStringCount);

        // 4. Style Count:StringChunk中样式的个数,四个字节,但是在实际解析过程中,这个值一直是0x00000000
        // 5. Unknown:位置区域,四个字节,在解析的过程中,这里需要略过四个字节
        // 6. String Pool Offset:字符串池的偏移值,四个字节,这个偏移值是相对于StringChunk的头部位置
        // 创建指定字符串个数的列表
        stringContentList = new ArrayList<String>(chunkStringCount);
        // 7. Style Pool Offset:样式池的偏移值,四个字节,这里没有Style,所以这个字段可忽略
        // 8. String Offsets:每个字符串的偏移值,所以他的大小应该是:StringCount*4个字节
        byte[] stringOffsetByte = Util.copyByte(byteSrc, 28, 4);
        // 获取字符串内容开始位置
        int stringContentStart = 8 + Util.byte2Int(stringOffsetByte);
        System.out.println("start:" + stringContentStart);
        // 9. Sytle Offsets:每个样式的偏移值,所以他的大小应该是SytleCount*4个字节
        // 10. String Pool
        // 获取String Content
        byte[] stringContentByte = Util.copyByte(byteSrc, stringContentStart, chunkSize);
        // 获取内容开始位置, 字符串大小
        int start = 0, stringSize;
        // 字符串
        String text = "";
        // 循环结束条件,字符串列表中的数据个数等于字符串个数
        while (stringContentList.size() < chunkStringCount) {
            // 解析字符串时问题,编码:UTF-8和UTF-16,如果是UTF-8的话是以00结尾的,如果是UTF-16的话以00 00结尾的
            // 格式是:偏移值开始的两个字节是字符串的长度,接着是字符串的内容,后面跟着两个字符串的结束符00
            // 一个字符对应两个字节,所以要乘以2
            stringSize = Util.byte2Short(Util.copyByte(stringContentByte, start, 2)) * 2;
            System.out.println("string size : " + stringSize);
            // 获取字符串文本
            text = new String(Util.copyByte(stringContentByte, start + 2, stringSize + 2));
            System.out.println("text : " + Util.filterStringNull(text));
            // 字符串文本
            stringContentList.add(Util.filterStringNull(text));
            start += (2 + stringSize + 2);
        }
        // 11. Style Pool

        // 此处的代码是用来解析资源文件xml的
        /*int index = 0; 
        while(index < chunkStringCount){
            byte[] stringSizeByte = Util.copyByte(stringContentByte, start, 2);
            stringSize = (stringSizeByte[1] & 0x7F);
            System.out.println("string size:"+Util.byteToHexString(Util.int2Byte(stringSize)));
            if(stringSize != 0){ //这里注意是UTF-8编码的 String
                text = ""; 
                try{ 
                    text = new String(Util.copyByte(stringContentByte, start + 2,stringSize), "utf-8"); 
                }catch(Exception e){
                    System.out.println("string encode error:"+e.toString());
                }
                stringContentList.add(text); 
            } else { 
                stringContentList.add("");
            }
            start += (stringSize+3);
            index++;
        }
         */
        // 拼接Resource Chunk 偏移位置
        resourceChunkOffset = STRING_CHUNK_OFFSET + Util.byte2Int(sizeByte);
    }
5). Resource Chunk 解析
    /** XMLContent Chunk 偏移量,动态计算 */
    private static int nextChunkOffset;
    /**
     * 解析Resource Chunk 1. Chunk Type: Resource Chunk 类型,4个字节,0x00080108 2.
     * Chunk Size: Resource Chunk 大小,4个字节 3. ResourceIds: 资源ID,
     * 大小是ResourceChunk大小除以4,减去头部的大小8个字节(ChunkType和ChunkSize)==(Chunk Size / 4 -
     * 2) * 4个字节
     * 
     * @param byteSrc
     *            二进制数据
     */
    public static void parseResourceChunk(byte[] byteSrc) {
        // 1. Chunk Type: Resource Chunk 类型,4个字节,0x00080108
        byte[] typeByte = Util.copyByte(byteSrc, resourceChunkOffset, 4);
        System.out.println("type: " + Util.byteToHexString(typeByte));
        // 2. Chunk Size: Resource Chunk 大小,4个字节
        byte[] sizeByte = Util.copyByte(byteSrc, resourceChunkOffset + 4, 4);
        // 文件大小
        int size = Util.byte2Int(sizeByte);
        System.out.println("size: " + size);
        // 3. ResourceIds: 资源ID,
        // 大小是ResourceChunk大小除以4,减去头部的大小8个字节(ChunkType和ChunkSize)==(Chunk Size /
        // 4 - 2) * 4个字节
        // chunk size包含chunk type 与 chunk size两个数组的字节,所以要剔除
        byte[] resourceIdByte = Util.copyByte(byteSrc, resourceChunkOffset + 8, size - 8);
        // 创建资源列表
        ArrayList<Integer> resourceIdList = new ArrayList<>(resourceIdByte.length / 4);
        // 遍历获取资源ID
        for (int i = 0; i < resourceIdByte.length; i += 4) {
            // 获取资源ID
            int resId = Util.byte2Int(Util.copyByte(resourceIdByte, i, 4));
            System.out.println("id: " + resId + ", hex: " + Util.byteToHexString(Util.copyByte(resourceIdByte, i, 4)));
            // 将资源ID添加到列表
            resourceIdList.add(resId);
        }
        // 计算XMLContent Chunk 偏移量
        nextChunkOffset = resourceChunkOffset + size;
    }
6). XmlContent Chunk解析
    /**KEY为uri,VALUE为prefix的map*/
    private static Map<String,String> uriPrefixMap = new HashMap<>();
    /**KEY为prefix,VALUE为uri的map*/
    private static Map<String, String> prefixUriMap = new HashMap<>();
    /**
     * 开始解析XML的正文内容 
     *  1. type: 类型,4个字节 
     *  2. size: 文件大小,4个字节 
     *  3. start namaspace 
     *  4. start tag 
     *  5. end tag 
     *  6. end namespace
     * 
     * @param byteSrc 二进制数据
     */
    public static void parseXmlContent(byte[] byteSrc) {
        // 判断是否到结尾处了
        while (!isEnd(byteSrc.length)) {
            // 获取类型
            byte[] typeByte = Util.copyByte(byteSrc, nextChunkOffset, 4);
            // 获取节点类型
            int type = Util.byte2Int(typeByte);
            System.out.println("chunk type: " + Util.byteToHexString(typeByte));
            // 文件大小
            byte[] sizeByte = Util.copyByte(byteSrc, nextChunkOffset + 4, 4);
            // 获取文件大小
            int size = Util.byte2Int(sizeByte);
            System.out.println("size: " + size);
            switch (type) {
                case ChunkMagicNumber.CHUNK_START_NS:
                    System.out.println("parse start namespace");
                    parseStartNameSpaceChunk(Util.copyByte(byteSrc, nextChunkOffset, size));
                    break;
                case ChunkMagicNumber.CHUNK_START_TAG:
                    System.out.println("parse start tag");
                    parseStartTagChunk(Util.copyByte(byteSrc, nextChunkOffset, size));
                    break;
                case ChunkMagicNumber.CHUNK_END_TAG:
                    System.out.println("parse end tag");
                    parseEndTagChunk(Util.copyByte(byteSrc, nextChunkOffset, size));
                    break;
                case ChunkMagicNumber.CHUNK_END_NS:
                    System.out.println("parse end namespace");
                    parseEndNameSpaceChunk(Util.copyByte(byteSrc, nextChunkOffset, size));
                    break;
                default:
                    break;
            }
            // 赋值
            nextChunkOffset += size;
        }
        System.out.println("parse xml: " + builder.toString());
    }
7). Start Namespace Chunk解析
    /**
     * 解析命名空间 开始
     *  1. Chunk Type:类型,4个字节
     *  2. Chunk Size: 大小,4个字节
     *  3. Line Number: 在AndroidManifest.xml文件中的行号,4个字节
     *  4. Unkonwn(0xFFFFFFFF): 未知区域,4个字节
     *  5. Prefix: 命名空间前缀,4个字节。如:android
     *  6. Uri: 命令空间的Uri。如:http://schemas.android.com/apk/res/android
     * @param byteSrc 字节数组
     */
    private static void parseStartNameSpaceChunk(byte[] byteSrc) {
        // 1. Chunk Type:类型,4个字节
        byte[] typeByte = Util.copyByte(byteSrc, 0, 4);
        System.out.println("type: " + Util.byteToHexString(typeByte));
        // 2. Chunk Size: 大小,4个字节
        byte[] sizeByte = Util.copyByte(byteSrc, 4, 4);
        // 获取大小
        int size = Util.byte2Int(sizeByte);
        System.out.println("size: " + size);
        // 3. Line Number: 在AndroidManifest.xml文件中的行号,4个字节
        byte[] lineNumberByte = Util.copyByte(byteSrc, 8, 4);
        // 获取行号
        int lineNumber = Util.byte2Int(lineNumberByte);
        System.out.println("line number: " + lineNumber);
        // 4. Unkonwn(0xFFFFFFFF): 未知区域,4个字节
        // 5. Prefix: 命名空间前缀,4个字节。如:android
        byte[] prefixByte = Util.copyByte(byteSrc, 16, 4);
        // 获取前缀标识
        int prefixIndex = Util.byte2Int(prefixByte);
        // 获取前缀
        String prefix = stringContentList.get(prefixIndex);
        System.out.println("prefix: " + prefixIndex + ", prefix str: " + prefix);
        // 6. Uri: 命令空间的Uri。如:http://schemas.android.com/apk/res/android
        byte[] uriByte = Util.copyByte(byteSrc, 20, 4);
        // 获取uri标识
        int uriIndex = Util.byte2Int(uriByte);
        // 获取uri
        String uri = stringContentList.get(uriIndex);
        System.out.println("uri: " + uriIndex + ", uri str: " + uri);
        
        // 存入Map
        uriPrefixMap.put(uri, prefix);
        prefixUriMap.put(prefix, uri);      
    }
8). Start Tag Chunk 解析
    /**
     * 解析TAG 开始
     *  1. Chunk Type: 类型,4个字节:0x00100102
     *  2. Chunk Size: 大小,4个字节
     *  3. Line Number: AndroidManifesta.xml文件中的行号,4个字节
     *  4. Unknown: 未知区域,4个字节
     *  5. NamespaceUri: 标签用到的命名空间的Uri,比如用到了android这个前缀,那么就需要用http://schemas.android.com/apk/res/android这个Uri去获取,四个字节
     *  6. Name: 标签名称(字符串中的索引值),4个字节
     *  7. Flags: 标签的类型,4个字节,开始/结束
     *  8. AttributeCount: 标签包含的属性个数,4个字节
     *  9. ClassAttribute: 标签包含的属性,4个字节
     *  10. Attributes: 属性内容,每个属性算是一个Entry,固定大小为5的字节数组
     *  [Namespace,Uri,Name,ValueString,Data],我们在解析的时候需要注意第四个值,要做一次处理:需要右移24位。所以这个字段的大小是:属性个数*5*4个字节
     * @param byteSrc 字节数组
     */
    private static void parseStartTagChunk(byte[] byteSrc) {
        // 1. Chunk Type: 类型,4个字节:0x00100102
        byte[] typeByte = Util.copyByte(byteSrc, 0, 4);
        System.out.println("type: " + Util.byteToHexString(typeByte));
        // 2. Chunk Size: 大小,4个字节
        byte[] sizeByte = Util.copyByte(byteSrc, 4, 4);
        // 获取大小
        int size = Util.byte2Int(sizeByte);
        System.out.println("size: " + size);
        // 3. Line Number: AndroidManifesta.xml文件中的行号,4个字节
        byte[] lineNumberByte = Util.copyByte(byteSrc, 8, 4);
        // 获取行号
        int lineNumber = Util.byte2Int(lineNumberByte);
        System.out.println("line number: " + lineNumber);
        // 4. Unknown: 未知区域,4个字节
        byte[] prefixByte = Util.copyByte(byteSrc, 8, 4);
        // 获取前缀标识
        int prefixIndex = Util.byte2Int(prefixByte);
        // 这里可能会返回-1, 如果返回-1的话,那就说明没有前缀
        if (-1 != prefixIndex && prefixIndex < stringContentList.size()) {
            System.out.println("prefix: " + prefixIndex);
            System.out.println("prefix str: " + stringContentList.get(prefixIndex));
        } else {
            System.out.println("prefix null");
        }
        // 5. NamespaceUri: 标签用到的命名空间的Uri,比如用到了android这个前缀,那么就需要用http://schemas.android.com/apk/res/android这个Uri去获取,四个字节
        byte[] uriByte = Util.copyByte(byteSrc, 16, 4);
        // 获取uri标识
        int uriIndex = Util.byte2Int(uriByte);
        // 如果前缀大的话,说明uri不存在
        if (-1 != uriIndex && prefixIndex < stringContentList.size()) {
            System.out.println("uri: " + uriIndex);
            System.out.println("uri str: " + stringContentList.get(uriIndex));
        } else {
            System.out.println("uri null");
        }
        // 6. Name: 标签名称(字符串中的索引值),4个字节
        byte[] tagNameByte = Util.copyByte(byteSrc, 20, 4);
        System.out.println(Util.byteToHexString(tagNameByte));
        // 获取标签名称标识
        int nameIndex = Util.byte2Int(tagNameByte);
        // 获取标签名称
        String name = stringContentList.get(nameIndex);
        if (-1 != nameIndex) {
            System.out.println("tag name index: " + nameIndex);
            System.out.println("tag name str: " + name);
        } else {
            System.out.println("tag name null");
        }
        // 7. Flags: 标签的类型,4个字节,开始/结束
        // 8. AttributeCount: 标签包含的属性个数,4个字节
        byte[] attrCountByte = Util.copyByte(byteSrc, 28, 4);
        // 获取属性个数
        int attrCount = Util.byte2Int(attrCountByte);
        System.out.println("attr count:" + attrCount);
        // 9. ClassAttribute: 标签包含的属性,4个字节
        // 10. Attributes: 属性内容,每个属性算是一个Entry,固定大小为5的字节数组
        // 创建指定长度的属性列表
        ArrayList<AttributeData> attrList = new ArrayList<>(attrCount);
        // 遍历
        for (int i = 0; i < attrCount; i++) {
            // 五个属性
            Integer[] values = new Integer[5];
            // 创建属性对象
            AttributeData attData = new AttributeData();
            // 遍历赋值
            for (int j = 0; j < 5; j++) {
                // 5个属性,每个属性占4个字节,所以是i*5*4
                int value = Util.byte2Int(Util.copyByte(byteSrc, 36 + i * 5 * 4 + j * 4, 4));
                switch (j) {
                case 0:
                    attData.nameSpaceUri = value;
                    break;
                case 1:
                    attData.name = value;
                    break;
                case 2:
                    attData.valueString = value;
                    break;
                case 3:
                    // 获取到的type要右移24位
                    attData.type = (value >> 24);                   
                    break;
                case 4:
                    attData.data = value;
                    break;
                default:
                    break;
                }
                values[j] = value;
            }
            // 添加到属性列表
            attrList.add(attData);
        }
        // 构造XML结构
        builder.append(createStartTagXml(name, attrList));
    }
9). End Tag Chunk解析
    /**
     * 解析TAG 结束
     *  1. Chunk Type(0x00100101): 4个字节
     *  2. Chunk Size: 4个字节
     *  3. Line Number: 4个字节
     *  4. Unknown(0xFFFFFFFF):4个字节
     *  5. Name: 4个字节
     *  6. Unknown: 4个字节
     *  7. Unknown: 4个字节
     * @param byteSrc 字节数组
     */
    private static void parseEndTagChunk(byte[] byteSrc) {
        // 1. Chunk Type(0x00100101): 4个字节
        byte[] tyepByte = Util.copyByte(byteSrc, 0, 4);
        System.out.println("type: " + Util.byteToHexString(tyepByte));
        // 2. Chunk Size: 4个字节
        byte[] sizeByte = Util.copyByte(byteSrc, 4, 4);
        // 获取长度
        int size = Util.byte2Int(sizeByte);
        System.out.println("size: " + size);
        // 3. Line Number: 4个字节
        byte[] lineNumberByte = Util.copyByte(byteSrc, 8, 4);
        // 获取行号
        int lineNumber = Util.byte2Int(lineNumberByte);
        System.out.println("line number:" + lineNumber);
        
        // 解析prefix
        byte[] prefixByte = Util.copyByte(byteSrc, 8, 4);
        // 获取前缀标识
        int prefixIndex = Util.byte2Int(prefixByte);
        // 可能返回-1,如果返回-1,则说明没有prefix
        if (-1 != prefixIndex && prefixIndex < stringContentList.size()) {
            System.out.println("prefix: " + prefixIndex);
            System.out.println("prefix str: " + stringContentList.get(prefixIndex));
        } else {
            System.out.println("prefix null.");
        }
        // 解析Uri
        byte[] uriByte = Util.copyByte(byteSrc, 16, 4);
        // 获取uri标识
        int uriIndex = Util.byte2Int(uriByte);
        // 如果前缀大的话,说明uri不存在
        if (-1 != uriIndex && prefixIndex < stringContentList.size()) {
            System.out.println("uri: " + uriIndex);
            System.out.println("uri str: " + stringContentList.get(uriIndex));
        } else {
            System.out.println("uri null");
        }
        
        // 4. Unknown(0xFFFFFFFF):4个字节
        // 5. Name: 4个字节
        byte[] tagNameByte = Util.copyByte(byteSrc, 20, 4);
        System.out.println("name: " + Util.byteToHexString(tagNameByte));
        // 获取tag名标识
        int tagNameIndex = Util.byte2Int(tagNameByte);
        // 获取tag名
        String tagName = stringContentList.get(tagNameIndex);
        if (-1 != tagNameIndex) {
            System.out.println("tag name index: " + tagNameIndex);
            System.out.println("tag name str: " + tagName);
        } else {
            System.out.println("tag name null");
        }
            
        builder.append(createEndTagXml(tagName));
    }

2. xml-apk-parser解析

1). 引包
img_f125a6671a2b88811caa15e91ae3f8a8.png
图2.png
2). 使用
public class APKParserDemo {
    /**指定要解析的文件*/
    private static final String DEFAULT_XML = "AndroidManifest.xml";
    public static void main(String[] args) {
        String apkPath = "xml/source.apk";
        String content = getManifestXMLFromAPK(apkPath);
        writeFormatXmlToFile(content);
    }
    
    /**
     * 获取AndroidManifest.xml文件
     * @param apkPath apk路径
     */
    private static String getManifestXMLFromAPK(String apkPath) {
        ZipFile zipFile = null;
        StringBuilder xmlBuilder = new StringBuilder();
        try {
            // apk文件
            File apkFile = new File(apkPath);
            // 获取压缩文件
            zipFile = new ZipFile(apkFile, ZipFile.OPEN_READ);
            // 获取指定文件
            ZipEntry entry = zipFile.getEntry(DEFAULT_XML);
            
            // 创建XML文件资源解析器
            AXmlResourceParser parser = new AXmlResourceParser();
            // 打开文件
            parser.open(zipFile.getInputStream(entry));
            
            StringBuilder sb = new StringBuilder();
            final String indentStep = " ";
            int type;
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                switch (type) {
                case XmlPullParser.START_DOCUMENT:
                    log(xmlBuilder, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
                    break;
                case XmlPullParser.START_TAG:
                    log(false, xmlBuilder, "%s<%s%s", sb, getNamespacePrefix(parser.getPrefix()), parser.getName());
                    sb.append(indentStep);
                    
                    int nameSpaceCountBefore = parser.getNamespaceCount(parser.getDepth() - 1);
                    int nameSpaceCount = parser.getNamespaceCount(parser.getDepth());
                    
                    for (int i = nameSpaceCountBefore; i != nameSpaceCount; i++) {
                        log(xmlBuilder, "%sxmlns:%s=\"%s\"", 
                                i == nameSpaceCountBefore ? "  " : sb, 
                                        parser.getNamespacePrefix(i), parser.getNamespaceUri(i));
                    }
                    
                    for (int i = 0, size = parser.getAttributeCount(); i != size; i++) {
                        log(false, xmlBuilder, "%s%s%s=\"%s\"", " ",
                                getNamespacePrefix(parser.getAttributePrefix(i)),
                                parser.getAttributeName(i),
                                getAttributeValue(parser, i));
                    }
                    log(xmlBuilder, ">");
                    
                    break;
                case XmlPullParser.END_TAG:
                    sb.setLength(sb.length() - indentStep.length());
                    log(xmlBuilder, "%s</%s%s>", sb,
                            getNamespacePrefix(parser.getPrefix()),
                            parser.getName());
                    break;
                case XmlPullParser.TEXT:
                    log(xmlBuilder, "%s%s", sb, parser.getText());
                    break;
                default:
                    break;
                }
                
            }
            parser.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return xmlBuilder.toString();
    }
    
    /**
     * 把构造好的XML写入文件中
     */
    public static void writeFormatXmlToFile(String content) {
        FileWriter fw = null;
        try {
            fw = new FileWriter("xml/ApkParser_format.xml");
            fw.write(content);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 获取属性
     * @param parser 解析器
     * @param index 标识
     * @return
     */
    private static String getAttributeValue(AXmlResourceParser parser,int index) {
        int type=parser.getAttributeValueType(index);
        int data=parser.getAttributeValueData(index);
        if (type==TypedValue.TYPE_STRING) {
            return parser.getAttributeValue(index);
        }
        if (type==TypedValue.TYPE_ATTRIBUTE) {
            return String.format("?%s%08X",getPackage(data),data);
        }
        if (type==TypedValue.TYPE_REFERENCE) {
            return String.format("@%s%08X",getPackage(data),data);
        }
        if (type==TypedValue.TYPE_FLOAT) {
            return String.valueOf(Float.intBitsToFloat(data));
        }
        if (type==TypedValue.TYPE_INT_HEX) {
            return String.format("0x%08X",data);
        }
        if (type==TypedValue.TYPE_INT_BOOLEAN) {
            return data!=0?"true":"false";
        }
        if (type==TypedValue.TYPE_DIMENSION) {
            return Float.toString(complexToFloat(data))+
                DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
        }
        if (type==TypedValue.TYPE_FRACTION) {
            return Float.toString(complexToFloat(data))+
                FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
        }
        if (type>=TypedValue.TYPE_FIRST_COLOR_INT && type<=TypedValue.TYPE_LAST_COLOR_INT) {
            return String.format("#%08X",data);
        }
        if (type>=TypedValue.TYPE_FIRST_INT && type<=TypedValue.TYPE_LAST_INT) {
            return String.valueOf(data);
        }
        return String.format("<0x%X, type 0x%02X>",data,type);
    }
    
    private static String getPackage(int id) {
        if (id>>>24==1) {
            return "android:";
        }
        return "";
    }
    
    /**
     * 获取前缀
     * @param prefix
     * @return
     */
    private static String getNamespacePrefix(String prefix) {
        if (prefix==null || prefix.length()==0) {
            return "";
        }
        return prefix+":";
    }
    
    /**
     * 拼接字符串
     * @param xmlSb 字符串拼接器
     * @param format 格式化
     * @param arguments 参数
     */
    private static void log(StringBuilder xmlSb,String format,Object...arguments) {
        log(true,xmlSb, format, arguments);
    }
    
    /**
     * 拼接字符串
     * @param newLine 是否为新行
     * @param xmlSb 字符串拼接器
     * @param format 格式化
     * @param arguments 参数
     */
    private static void log(boolean newLine,StringBuilder xmlSb,String format,Object...arguments) {
        xmlSb.append(String.format(format, arguments));
        if(newLine) xmlSb.append("\n");
    }
    
    // ILLEGAL STUFF, DONT LOOK :)
    
    public static float complexToFloat(int complex) {
        return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3];
    }
    
    private static final float RADIX_MULTS[]={
        0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F
    };
    private static final String DIMENSION_UNITS[]={
        "px","dip","sp","pt","in","mm","",""
    };
    private static final String FRACTION_UNITS[]={
        "%","%p","","","","","",""
    };
}

参考文章

Android逆向之旅---解析编译之后的AndroidManifest文件格式

代码下载

目录
相关文章
|
23天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
62 2
|
2月前
|
Java
Java“解析时到达文件末尾”解决
在Java编程中,“解析时到达文件末尾”通常指在读取或处理文件时提前遇到了文件结尾,导致程序无法继续读取所需数据。解决方法包括:确保文件路径正确,检查文件是否完整,使用正确的文件读取模式(如文本或二进制),以及确保读取位置正确。合理设置缓冲区大小和循环条件也能避免此类问题。
418 2
|
2月前
|
ARouter Android开发
Android不同module布局文件重名被覆盖
Android不同module布局文件重名被覆盖
|
2月前
|
自然语言处理 数据处理 Python
python操作和解析ppt文件 | python小知识
本文将带你从零开始,了解PPT解析的工具、工作原理以及常用的基本操作,并提供具体的代码示例和必要的说明【10月更文挑战第4天】
399 60
|
28天前
|
Java Maven
maven项目的pom.xml文件常用标签使用介绍
第四届人文,智慧教育与服务管理国际学术会议(HWESM 2025) 2025 4th International Conference on Humanities, Wisdom Education and Service Management
81 8
|
29天前
|
存储
文件太大不能拷贝到U盘怎么办?实用解决方案全解析
当我们试图将一个大文件拷贝到U盘时,却突然跳出提示“对于目标文件系统目标文件过大”。这种情况让人感到迷茫,尤其是在急需备份或传输数据的时候。那么,文件太大为什么会无法拷贝到U盘?又该如何解决?本文将详细分析这背后的原因,并提供几个实用的方法,帮助你顺利将文件传输到U盘。
|
2月前
|
数据安全/隐私保护 流计算 开发者
python知识点100篇系列(18)-解析m3u8文件的下载视频
【10月更文挑战第6天】m3u8是苹果公司推出的一种视频播放标准,采用UTF-8编码,主要用于记录视频的网络地址。HLS(Http Live Streaming)是苹果公司提出的一种基于HTTP的流媒体传输协议,通过m3u8索引文件按序访问ts文件,实现音视频播放。本文介绍了如何通过浏览器找到m3u8文件,解析m3u8文件获取ts文件地址,下载ts文件并解密(如有必要),最后使用ffmpeg合并ts文件为mp4文件。
|
2月前
|
XML Web App开发 JavaScript
XML DOM 解析器
XML DOM 解析器
|
2月前
|
XML Web App开发 JavaScript
XML DOM 解析器
XML DOM 解析器
|
2月前
|
XML Web App开发 JavaScript
XML DOM 解析器
XML DOM 解析器