You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将标准XML转为二进制AXML以实现运行时加载视图与Drawable?

通用标准XML转二进制AXML的可行方案求问

我需要将标准XML(如下示例)转换为二进制AXML格式,用于运行时加载视图和Drawable,但试了几种方法都没找到通用方案:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="30dp"
    android:height="30dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
  <path
      android:fillColor="#5CDD06"
      android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
</vector>

已尝试的方法及问题

1. 直接读取XML字节反射调用XmlBlock

读取XML文件字节后,通过反射XmlBlock解析时抛出InvocationTargetException:
读取代码:

FileInputStream is = new FileInputStream("file.xml");
byte[] arr = new byte[is.available()];
is.read(arr);

反射代码:

@SuppressLint("PrivateApi")
Class<?> xmlBlock = Class.forName("android.content.res.XmlBlock");
Constructor xmlBlockConstr = xmlBlock.getConstructor(byte[].class);
Method xmlParserNew = xmlBlock.getDeclaredMethod("newParser");
xmlBlockConstr.setAccessible(true);
xmlParserNew.setAccessible(true);
XmlPullParser parser = (XmlPullParser) xmlParserNew.invoke(xmlBlockConstr.newInstance((Object) arr)); // 抛出InvocationTargetException
Drawable.createFromXml(context.getResources(), parser);

2. 手动构造二进制AXML

该方法可正常运行,但仅适配特定Drawable结构,无法处理通用XML:

public static byte[] createBinaryDrawableXml(int width, int height,
                                                  float viewportWidth, float viewportHeight,
                                                  List<PathData> paths) {
        List<byte[]> stringPool = new ArrayList<>(Arrays.asList(BIN_XML_STRINGS));
        for (PathData path : paths) {
            stringPool.add(path.data);
        }

        ByteBuffer bb = ByteBuffer.allocate(8192);  // Capacity might have to be greater.
        bb.order(ByteOrder.LITTLE_ENDIAN);

        int posBefore;

        // ==== XML chunk ====
        bb.putShort(CHUNK_TYPE_XML);  // Type
        bb.putShort((short) 8);  // Header size
        int xmlSizePos = bb.position();
        bb.position(bb.position() + 4);

        // ==== String pool chunk ====
        int spStartPos = bb.position();
        bb.putShort(CHUNK_TYPE_STR_POOL);  // Type
        bb.putShort((short) 28);  // Header size
        int spSizePos = bb.position();
        bb.position(bb.position() + 4);
        bb.putInt(stringPool.size());  // String count
        bb.putInt(0);  // Style count
        bb.putInt(1 << 8);  // Flags set: encoding is UTF-8
        int spStringsStartPos = bb.position();
        bb.position(bb.position() + 4);
        bb.putInt(0);  // Styles start

        // String offsets
        int offset = 0;
        for (byte[] str : stringPool) {
            bb.putInt(offset);
            offset += str.length + (str.length > 127 ? 5 : 3);
        }

        posBefore = bb.position();
        bb.putInt(spStringsStartPos, bb.position() - spStartPos);
        bb.position(posBefore);

        // String pool
        for (byte[] str : stringPool) {
            if (str.length > 127) {
                byte high = (byte) ((str.length & 0xFF00 | 0x8000) >>> 8);
                byte low = (byte) (str.length & 0xFF);
                bb.put(high);
                bb.put(low);
                bb.put(high);
                bb.put(low);
            } else {
                byte len = (byte) str.length;
                bb.put(len);
                bb.put(len);
            }
            bb.put(str);
            bb.put((byte) 0);
        }

        if (bb.position() % 4 != 0) {
            // Padding to align on 32-bit
            bb.put(new byte[4 - (bb.position() % 4)]);
        }

        // Write string pool chunk size
        posBefore = bb.position();
        bb.putInt(spSizePos, bb.position() - spStartPos);
        bb.position(posBefore);

        // ==== Resource map chunk ====
        bb.putShort(CHUNK_TYPE_RES_MAP);  // Type
        bb.putShort((short) 8);  // Header size
        bb.putInt(8 + BIN_XML_ATTRS.length * 4);  // Chunk size
        for (int attr : BIN_XML_ATTRS) {
            bb.putInt(attr);
        }

        // ==== Vector start tag ====
        int vstStartPos = bb.position();
        int vstSizePos = putStartTag(bb, 7, 4);

        // Attributes
        // android:width="24dp", value type: dimension (dp)
        putAttribute(bb, 0, -1, VALUE_TYPE_DIMENSION, (width << 8) + 1);

        // android:height="24dp", value type: dimension (dp)
        putAttribute(bb, 1, -1, VALUE_TYPE_DIMENSION, (height << 8) + 1);

        // android:viewportWidth="24", value type: float
        putAttribute(bb, 2, -1, VALUE_TYPE_FLOAT, Float.floatToRawIntBits(viewportWidth));

        // android:viewportHeight="24", value type: float
        putAttribute(bb, 3, -1, VALUE_TYPE_FLOAT, Float.floatToRawIntBits(viewportHeight));

        // Write vector start tag chunk size
        posBefore = bb.position();
        bb.putInt(vstSizePos, bb.position() - vstStartPos);
        bb.position(posBefore);

        for (int i = 0; i < paths.size(); i++) {
            // ==== Path start tag ====
            int pstStartPos = bb.position();
            int pstSizePos = putStartTag(bb, 6, 2);

            // android:fillColor="#aarrggbb", value type: #rgb.
            putAttribute(bb, 4, -1, VALUE_TYPE_COLOR, paths.get(i).color);

            // android:pathData="...", value type: string
            putAttribute(bb, 5, 9 + i, VALUE_TYPE_STRING, 9 + i);

            // Write path start tag chunk size
            posBefore = bb.position();
            bb.putInt(pstSizePos, bb.position() - pstStartPos);
            bb.position(posBefore);

            // ==== Path end tag ====
            putEndTag(bb, 6);
        }

        // ==== Vector end tag ====
        putEndTag(bb, 7);

        // Write XML chunk size
        posBefore = bb.position();
        bb.putInt(xmlSizePos, bb.position());
        bb.position(posBefore);

        // Return binary XML byte array
        byte[] binXml = new byte[bb.position()];
        bb.rewind();
        bb.get(binXml);

        return binXml;
    }

调用代码:

List<PathData> pathList = Arrays.asList(new PathData("M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z", Color.parseColor("#5CDD06")));

byte[] arr2 = createBinaryDrawableXml(30, 30, 24, 24, pathList);

XmlPullParser parser = (XmlPullParser) xmlParserNew.invoke(xmlBlockConstr.newInstance((Object) arr2));

Drawable.createFromXml(context.getResources(), parser);

3. 使用aapt工具

aapt会将所有资源打包进APK,不符合仅转换单个XML的需求。

4. 使用xml2axml工具

转换时抛出xmlpullparser exception at line 7 height must be > 0错误,无法完成转换。

求通用的标准XML转二进制AXML的可行方案。


解决方案

方案1:基于aapt+反射的轻量编译方案

无需打包完整APK,仅编译单个XML资源:

  1. 准备最小化资源目录结构:
    temp-res/
      drawable/
        your_vector.xml
      values/
        strings.xml  <!-- 空文件或仅含根标签 -->
    AndroidManifest.xml  <!-- 仅含基础结构:<manifest package="your.temp.pkg"></manifest> -->
    
  2. 用aapt编译生成临时资源APK:
    aapt package -f -M AndroidManifest.xml -S temp-res/ -I $ANDROID_HOME/platforms/android-34/android.jar -F temp-res.apk
    
  3. 代码中反射加载该APK的资源,直接获取编译后的AXML对应的Drawable:
    @SuppressLint("PrivateApi")
    public static Drawable getCompiledDrawable(Context context, String apkPath, String resName) throws Exception {
        AssetManager assetManager = AssetManager.class.newInstance();
        Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
        addAssetPath.invoke(assetManager, apkPath);
    
        Resources res = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
        int resId = res.getIdentifier(resName, "drawable", "your.temp.pkg");
        return res.getDrawable(resId, context.getTheme());
    }
    

方案2:使用Apktool的AXML序列化API

利用Apktool内置的成熟AXML处理逻辑,直接转换XML字符串到AXML字节:

  1. 添加Gradle依赖:
    implementation 'com.github.iBotPeaches:Apktool:2.9.3'
    
  2. 转换代码:
    public static byte[] xmlToAxml(String xmlContent) throws Exception {
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new StringReader(xmlContent));
    
        AXmlSerializer serializer = new AXmlSerializer();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        serializer.setOutput(outputStream, "UTF-8");
    
        int eventType = parser.getEventType();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            switch (eventType) {
                case XmlPullParser.START_DOCUMENT:
                    serializer.startDocument(null, true);
                    break;
                case XmlPullParser.START_TAG:
                    serializer.startTag(null, parser.getName());
                    for (int i = 0; i < parser.getAttributeCount(); i++) {
                        serializer.attribute(null, parser.getAttributeName(i), parser.getAttributeValue(i));
                    }
                    break;
                case XmlPullParser.END_TAG:
                    serializer.endTag(null, parser.getName());
                    break;
                case XmlPullParser.TEXT:
                    serializer.text(parser.getText());
                    break;
            }
            eventType = parser.next();
        }
        serializer.endDocument();
        return outputStream.toByteArray();
    }
    
  3. 解析转换后的AXML:
    byte[] axmlBytes = xmlToAxml(xmlContent);
    Class<?> xmlBlockCls = Class.forName("android.content.res.XmlBlock");
    Constructor<?> constructor = xmlBlockCls.getConstructor(byte[].class);
    constructor.setAccessible(true);
    Object xmlBlock = constructor.newInstance(axmlBytes);
    Method newParser = xmlBlockCls.getDeclaredMethod("newParser");
    newParser.setAccessible(true);
    XmlPullParser parser = (XmlPullParser) newParser.invoke(xmlBlock);
    Drawable drawable = Drawable.createFromXml(context.getResources(), parser);
    

方案3:修复xml2axml工具错误

你遇到的height must be >0错误是工具对带单位的尺寸解析异常,可:

  • 升级到工具最新版本,或修改源码中尺寸解析逻辑,正确识别dp等单位并转换为AXML支持的维度类型值;
  • 命令行转换时显式指定命名空间参数,确保工具正确解析Android属性。

内容的提问来源于stack exchange,提问作者Seif Ashraf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 23:50:29