如何将标准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资源:
- 准备最小化资源目录结构:
temp-res/ drawable/ your_vector.xml values/ strings.xml <!-- 空文件或仅含根标签 --> AndroidManifest.xml <!-- 仅含基础结构:<manifest package="your.temp.pkg"></manifest> --> - 用aapt编译生成临时资源APK:
aapt package -f -M AndroidManifest.xml -S temp-res/ -I $ANDROID_HOME/platforms/android-34/android.jar -F temp-res.apk - 代码中反射加载该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字节:
- 添加Gradle依赖:
implementation 'com.github.iBotPeaches:Apktool:2.9.3' - 转换代码:
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(); } - 解析转换后的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
相关产品推荐
相关产品推荐

