├── settings.gradle ├── xml ├── .gitignore ├── build.gradle └── src │ ├── main │ └── java │ │ ├── common │ │ └── utils │ │ │ ├── StringUtils.java │ │ │ └── FileUtils.java │ │ ├── com │ │ └── axml │ │ │ ├── chunk │ │ │ ├── base │ │ │ │ ├── ChunkType.java │ │ │ │ ├── BaseContentChunk.java │ │ │ │ └── BaseChunk.java │ │ │ ├── EndTagChunk.java │ │ │ ├── ResourceChunk.java │ │ │ ├── NamespaceChunk.java │ │ │ ├── StringChunk.java │ │ │ └── StartTagChunk.java │ │ │ └── AndroidBinaryXml.java │ │ └── android │ │ └── util │ │ └── TypedValue.java │ └── test │ └── java │ └── Main.java ├── alipay.png ├── wechatpay.png ├── AndroidBinaryXml.png ├── .gitignore └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'xml' 2 | -------------------------------------------------------------------------------- /xml/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /build -------------------------------------------------------------------------------- /alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/senswrong/AndroidBinaryXml/HEAD/alipay.png -------------------------------------------------------------------------------- /wechatpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/senswrong/AndroidBinaryXml/HEAD/wechatpay.png -------------------------------------------------------------------------------- /AndroidBinaryXml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/senswrong/AndroidBinaryXml/HEAD/AndroidBinaryXml.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | .DS_Store 5 | /build 6 | /out 7 | /gradle 8 | gradlew 9 | gradlew.bat 10 | -------------------------------------------------------------------------------- /xml/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | version 'unspecified' 6 | 7 | sourceCompatibility = 1.8 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | testCompile group: 'junit', name: 'junit', version: '4.12' 15 | } 16 | -------------------------------------------------------------------------------- /xml/src/main/java/common/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package common.utils; 2 | 3 | /** 4 | * Created by Sens on 2021/8/27. 5 | */ 6 | public class StringUtils { 7 | public static boolean isEmpty(String string) { 8 | return string == null || string.trim().length() == 0; 9 | } 10 | 11 | public static boolean isNotEmpty(String string) { 12 | return !isEmpty(string); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /xml/src/test/java/Main.java: -------------------------------------------------------------------------------- 1 | import com.axml.AndroidBinaryXml; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | /** 7 | * Created by Sens on 2021/8/27. 8 | */ 9 | public class Main { 10 | 11 | public static void main(String[] args) throws IOException { 12 | File file = new File(args[0]); 13 | System.out.println("file->" + file); 14 | 15 | AndroidBinaryXml androidBinaryXml = new AndroidBinaryXml(file); 16 | 17 | byte[] datas = androidBinaryXml.toBytes(); 18 | 19 | AndroidBinaryXml manifest = new AndroidBinaryXml(datas); 20 | System.out.println(manifest); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /xml/src/main/java/common/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package common.utils; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * Created by Sens on 2021/8/27. 7 | */ 8 | public class FileUtils { 9 | public static byte[] getFileData(File file) { 10 | try { 11 | BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file)); 12 | int available = inputStream.available(); 13 | byte[] datas = new byte[available]; 14 | inputStream.read(datas); 15 | inputStream.close(); 16 | return datas; 17 | } catch (IOException e) { 18 | e.printStackTrace(); 19 | } 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/chunk/base/ChunkType.java: -------------------------------------------------------------------------------- 1 | package com.axml.chunk.base; 2 | 3 | /** 4 | * Created by Sens on 2021/8/27. 5 | */ 6 | public enum ChunkType { 7 | CHUNK_STRING /*chunk type*/(0x0001), 8 | CHUNK_RESOURCE /*chunk type*/(0x0180), 9 | CHUNK_START_NAMESPACE /*chunk type*/(0x0100), 10 | CHUNK_END_NAMESPACE /*chunk type*/(0x0101), 11 | CHUNK_START_TAG /*chunk type*/(0x0102), 12 | CHUNK_END_TAG /*chunk type*/(0x0103), 13 | ; 14 | public final int TYPE; 15 | 16 | ChunkType(int TYPE) { 17 | this.TYPE = TYPE; 18 | } 19 | 20 | public static ChunkType valueOf(int TYPE) { 21 | for (ChunkType value : ChunkType.values()) 22 | if (value.TYPE == TYPE) return value; 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GOTO[CSDN](https://blog.csdn.net/song6974272/article/details/119991751) 2 | ![image](https://raw.githubusercontent.com/senswrong/AndroidBinaryXml/main/AndroidBinaryXml.png) 3 | 4 |

5 | GitHub watchers 6 | GitHub stars 7 | GitHub forks 8 | java 9 |

10 | 11 | 12 | ## 杨 13 |

14 | 15 | 16 |

17 | 18 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/chunk/EndTagChunk.java: -------------------------------------------------------------------------------- 1 | package com.axml.chunk; 2 | 3 | import com.axml.chunk.base.BaseContentChunk; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.ByteOrder; 9 | 10 | /** 11 | * Created by Sens on 2021/8/27. 12 | */ 13 | public class EndTagChunk extends BaseContentChunk { 14 | public final int namespaceUri; 15 | public final int name; 16 | 17 | public EndTagChunk(ByteBuffer byteBuffer, StringChunk stringChunk) { 18 | super(byteBuffer, stringChunk); 19 | namespaceUri = byteBuffer.getInt(); 20 | name = byteBuffer.getInt(); 21 | 22 | byteBuffer.position(ChunkStartPosition + chunkSize); 23 | } 24 | 25 | @Override 26 | protected void toBytes(ByteArrayOutputStream stream) throws IOException { 27 | super.toBytes(stream); 28 | ByteBuffer byteBuffer = ByteBuffer.allocate(8); 29 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 30 | byteBuffer.putInt(this.namespaceUri); 31 | byteBuffer.putInt(this.name); 32 | stream.write(byteBuffer.array()); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "\n"; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/chunk/ResourceChunk.java: -------------------------------------------------------------------------------- 1 | package com.axml.chunk; 2 | 3 | import com.axml.chunk.base.BaseChunk; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.ByteOrder; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by Sens on 2021/8/27. 14 | * Used system resource ID [android.R.**] 15 | */ 16 | public class ResourceChunk extends BaseChunk { 17 | public final List resourceIDs; 18 | 19 | public ResourceChunk(ByteBuffer byteBuffer) { 20 | super(byteBuffer); 21 | int idCount = chunkSize / 4 - 2; 22 | resourceIDs = new ArrayList<>(idCount); 23 | for (int i = 0; i < idCount; i++) 24 | resourceIDs.add(byteBuffer.getInt()); 25 | byteBuffer.position(ChunkStartPosition + chunkSize); 26 | } 27 | 28 | @Override 29 | protected void toBytes(ByteArrayOutputStream stream) throws IOException { 30 | ByteBuffer byteBuffer = ByteBuffer.allocate(resourceIDs.size() * 4); 31 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 32 | for (Integer integer : resourceIDs) 33 | byteBuffer.putInt(integer); 34 | stream.write(byteBuffer.array()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/chunk/base/BaseContentChunk.java: -------------------------------------------------------------------------------- 1 | package com.axml.chunk.base; 2 | 3 | import com.axml.chunk.StringChunk; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.ByteOrder; 9 | 10 | /** 11 | * Created by Sens on 2021/8/27. 12 | */ 13 | public class BaseContentChunk extends BaseChunk { 14 | public final int lineNumber; 15 | public final int comment; 16 | 17 | protected final StringChunk stringChunk; 18 | 19 | public BaseContentChunk(ByteBuffer byteBuffer, StringChunk stringChunk) { 20 | super(byteBuffer); 21 | lineNumber = byteBuffer.getInt(); 22 | comment = byteBuffer.getInt(); 23 | 24 | this.stringChunk = stringChunk; 25 | } 26 | 27 | @Override 28 | protected void toBytes(ByteArrayOutputStream stream) throws IOException { 29 | ByteBuffer byteBuffer = ByteBuffer.allocate(8); 30 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 31 | byteBuffer.putInt(this.lineNumber); 32 | byteBuffer.putInt(this.comment); 33 | stream.write(byteBuffer.array()); 34 | } 35 | 36 | protected String getString(int index) { 37 | if (index == -1) return ""; 38 | return stringChunk.getString(index); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/chunk/NamespaceChunk.java: -------------------------------------------------------------------------------- 1 | package com.axml.chunk; 2 | 3 | import com.axml.chunk.base.BaseContentChunk; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.ByteOrder; 9 | 10 | /** 11 | * Created by Sens on 2021/8/27. 12 | */ 13 | public class NamespaceChunk extends BaseContentChunk { 14 | public final int prefix; 15 | public final int uri; 16 | 17 | public NamespaceChunk(ByteBuffer byteBuffer, StringChunk stringChunk) { 18 | super(byteBuffer, stringChunk); 19 | this.prefix = byteBuffer.getInt(); 20 | this.uri = byteBuffer.getInt(); 21 | byteBuffer.position(ChunkStartPosition + chunkSize); 22 | } 23 | 24 | @Override 25 | protected void toBytes(ByteArrayOutputStream stream) throws IOException { 26 | super.toBytes(stream); 27 | ByteBuffer byteBuffer = ByteBuffer.allocate(8); 28 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 29 | byteBuffer.putInt(this.prefix); 30 | byteBuffer.putInt(this.uri); 31 | stream.write(byteBuffer.array()); 32 | } 33 | 34 | public String getXmlNameSpace() { 35 | return new StringBuilder().append("xmlns:").append(getString(prefix)).append("=\"").append(getString(uri)).append('"').toString(); 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return ""; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/chunk/base/BaseChunk.java: -------------------------------------------------------------------------------- 1 | package com.axml.chunk.base; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.nio.ByteBuffer; 6 | import java.nio.ByteOrder; 7 | 8 | /** 9 | * Created by Sens on 2021/8/27. 10 | */ 11 | public abstract class BaseChunk { 12 | public final int ChunkStartPosition; 13 | 14 | public final short chunkType; 15 | public final short headerSize; 16 | public int chunkSize; 17 | 18 | public BaseChunk(ByteBuffer byteBuffer) { 19 | byteBuffer.position(byteBuffer.position() - 2); 20 | ChunkStartPosition = byteBuffer.position(); 21 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 22 | this.chunkType = byteBuffer.getShort(); 23 | this.headerSize = byteBuffer.getShort(); 24 | this.chunkSize = byteBuffer.getInt(); 25 | } 26 | 27 | protected abstract void toBytes(ByteArrayOutputStream stream) throws IOException; 28 | 29 | public byte[] toBytes() throws IOException { 30 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 31 | toBytes(stream); 32 | byte[] bytes = stream.toByteArray(); 33 | this.chunkSize = bytes.length + 8; 34 | ByteBuffer byteBuffer = ByteBuffer.allocate(8 + bytes.length); 35 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 36 | byteBuffer.putShort(this.chunkType); 37 | byteBuffer.putShort(this.headerSize); 38 | byteBuffer.putInt(this.chunkSize); 39 | byteBuffer.put(bytes); 40 | return byteBuffer.array(); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return ""; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/AndroidBinaryXml.java: -------------------------------------------------------------------------------- 1 | package com.axml; 2 | 3 | import com.axml.chunk.*; 4 | import com.axml.chunk.base.BaseChunk; 5 | import com.axml.chunk.base.ChunkType; 6 | import common.utils.FileUtils; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.nio.ByteBuffer; 12 | import java.nio.ByteOrder; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Created by Sens on 2021/8/27. 18 | * {@see AndroidBinaryXml} 19 | */ 20 | public class AndroidBinaryXml { 21 | public short fileType; 22 | public short headerSize; 23 | public int fileSize; 24 | public StringChunk stringChunk; 25 | public ResourceChunk resourceChunk; 26 | public List structList = new ArrayList(); 27 | 28 | public AndroidBinaryXml(File androidManifest) { 29 | this(FileUtils.getFileData(androidManifest)); 30 | } 31 | 32 | public AndroidBinaryXml(byte[] datas) { 33 | int available = datas.length; 34 | ByteBuffer byteBuffer = ByteBuffer.wrap(datas); 35 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 36 | fileType = byteBuffer.getShort(); 37 | headerSize = byteBuffer.getShort(); 38 | fileSize = byteBuffer.getInt(); 39 | List namespaceChunkList = new ArrayList<>(); 40 | while (byteBuffer.position() < available) { 41 | short Type = byteBuffer.getShort(); 42 | ChunkType chunkType = ChunkType.valueOf(Type); 43 | if (chunkType == null) break; 44 | switch (chunkType) { 45 | case CHUNK_STRING: 46 | stringChunk = new StringChunk(byteBuffer); 47 | break; 48 | case CHUNK_RESOURCE: 49 | resourceChunk = new ResourceChunk(byteBuffer); 50 | break; 51 | case CHUNK_START_NAMESPACE: 52 | NamespaceChunk namespaceChunk = new NamespaceChunk(byteBuffer, stringChunk); 53 | namespaceChunkList.add(namespaceChunk); 54 | structList.add(namespaceChunk); 55 | break; 56 | case CHUNK_START_TAG: 57 | structList.add(new StartTagChunk(byteBuffer, stringChunk, namespaceChunkList)); 58 | break; 59 | case CHUNK_END_TAG: 60 | structList.add(new EndTagChunk(byteBuffer, stringChunk)); 61 | break; 62 | case CHUNK_END_NAMESPACE: 63 | structList.add(new NamespaceChunk(byteBuffer, stringChunk)); 64 | break; 65 | } 66 | } 67 | } 68 | 69 | public byte[] toBytes() throws IOException { 70 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 71 | if (stringChunk != null) stream.write(stringChunk.toBytes()); 72 | if (resourceChunk != null) stream.write(resourceChunk.toBytes()); 73 | for (BaseChunk chunk : structList) 74 | stream.write(chunk.toBytes()); 75 | fileSize = 8 + stream.size(); 76 | ByteBuffer byteBuffer = ByteBuffer.allocate(fileSize); 77 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 78 | byteBuffer.putShort(fileType); 79 | byteBuffer.putShort(headerSize); 80 | byteBuffer.putInt(fileSize); 81 | byteBuffer.put(stream.toByteArray()); 82 | return byteBuffer.array(); 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | StringBuilder sb = new StringBuilder("\n"); 88 | boolean hasXmlns = false; 89 | for (BaseChunk baseChunk : structList) { 90 | if (!hasXmlns && baseChunk instanceof StartTagChunk) { 91 | ((StartTagChunk) baseChunk).addXmlns(); 92 | hasXmlns = true; 93 | } 94 | sb.append(baseChunk); 95 | } 96 | return sb.toString(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/chunk/StringChunk.java: -------------------------------------------------------------------------------- 1 | package com.axml.chunk; 2 | 3 | import com.axml.chunk.base.BaseChunk; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.ByteOrder; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by Sens on 2021/8/27. 15 | */ 16 | public class StringChunk extends BaseChunk { 17 | public int stringCount; 18 | public int styleCount; 19 | public boolean isUTF8; 20 | public boolean isSorted; 21 | public int stringStart; 22 | public int styleStart; 23 | 24 | public int[] stringOffsets; 25 | public int[] styleOffsets; 26 | 27 | public List stringList; 28 | 29 | public StringChunk(ByteBuffer byteBuffer) { 30 | super(byteBuffer); 31 | this.stringCount = byteBuffer.getInt(); 32 | this.styleCount = byteBuffer.getInt(); 33 | //utf8(0x0100) or default utf16le(0x0000) 34 | this.isUTF8 = byteBuffer.getShort() != 0; 35 | this.isSorted = byteBuffer.getShort() != 0; 36 | this.stringStart = byteBuffer.getInt(); 37 | this.styleStart = byteBuffer.getInt(); 38 | 39 | stringOffsets = new int[stringCount]; 40 | for (int i = 0; i < stringOffsets.length; i++) 41 | stringOffsets[i] = byteBuffer.getInt(); 42 | 43 | styleOffsets = new int[styleCount]; 44 | for (int i = 0; i < styleOffsets.length; i++) 45 | styleOffsets[i] = byteBuffer.getInt(); 46 | 47 | stringList = new ArrayList<>(stringCount); 48 | 49 | for (int i = 0; i < stringCount; i++) { 50 | byteBuffer.position(ChunkStartPosition + stringStart + stringOffsets[i]); 51 | int byteCount; 52 | if (isUTF8) { 53 | int strCount = byteBuffer.get() & 0xFF; 54 | byteCount = byteBuffer.get() & 0xFF; 55 | } else 56 | byteCount = byteBuffer.getShort() * 2; 57 | 58 | byte[] string = new byte[byteCount]; 59 | byteBuffer.get(string); 60 | if (isUTF8) stringList.add(new String(string, StandardCharsets.UTF_8)); 61 | else stringList.add(new String(string, StandardCharsets.UTF_16LE)); 62 | } 63 | //styleCount always = 0 [skip] 64 | byteBuffer.position(ChunkStartPosition + chunkSize); 65 | } 66 | 67 | public String getString(int index) { 68 | return stringList.get(index); 69 | } 70 | 71 | private void stringToBytes(ByteArrayOutputStream stream, String str) throws IOException { 72 | ByteBuffer byteBuffer; 73 | if (isUTF8) { 74 | byte[] chars = str.getBytes(StandardCharsets.UTF_8); 75 | byteBuffer = ByteBuffer.allocate(2 + chars.length + 1); 76 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 77 | byteBuffer.put((byte) str.length()); 78 | byteBuffer.put((byte) chars.length); 79 | byteBuffer.put(chars); 80 | byteBuffer.put((byte) 0); 81 | } else { 82 | byte[] chars = str.getBytes(StandardCharsets.UTF_16LE); 83 | byteBuffer = ByteBuffer.allocate(2 + chars.length + 2); 84 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 85 | byteBuffer.putShort((short) str.length()); 86 | byteBuffer.put(chars); 87 | byteBuffer.putShort((short) 0); 88 | } 89 | stream.write(byteBuffer.array()); 90 | } 91 | 92 | @Override 93 | protected void toBytes(ByteArrayOutputStream stream) throws IOException { 94 | stringCount = stringList.size(); 95 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 * 4 + stringOffsets.length * 4 + styleOffsets.length * 4); 96 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 97 | byteBuffer.putInt(stringCount); 98 | byteBuffer.putInt(styleCount); 99 | byteBuffer.order(ByteOrder.BIG_ENDIAN); 100 | byteBuffer.putShort((short) (isUTF8 ? 1 : 0)); 101 | byteBuffer.putShort((short) (isSorted ? 1 : 0)); 102 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 103 | byteBuffer.putInt(stringStart); 104 | byteBuffer.putInt(styleStart); 105 | int stringOffset = 0; 106 | if (stringOffsets.length != stringCount) 107 | stringOffsets = new int[stringCount]; 108 | for (int i = 0; i < stringCount; i++) { 109 | stringOffsets[i] = stringOffset; 110 | byteBuffer.putInt(stringOffset); 111 | if (isUTF8) 112 | stringOffset += 3 + stringList.get(i).getBytes(StandardCharsets.UTF_8).length; 113 | else 114 | stringOffset += 4 + stringList.get(i).getBytes(StandardCharsets.UTF_16LE).length; 115 | } 116 | //styleCount always = 0 [skip] 117 | for (int offset : styleOffsets) byteBuffer.putInt(offset); 118 | stream.write(byteBuffer.array()); 119 | for (String str : stringList) 120 | stringToBytes(stream, str); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /xml/src/main/java/com/axml/chunk/StartTagChunk.java: -------------------------------------------------------------------------------- 1 | package com.axml.chunk; 2 | 3 | import android.util.TypedValue; 4 | import com.axml.chunk.base.BaseContentChunk; 5 | import common.utils.StringUtils; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | import java.nio.ByteBuffer; 10 | import java.nio.ByteOrder; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by Sens on 2021/8/27. 16 | */ 17 | public class StartTagChunk extends BaseContentChunk { 18 | public final int namespaceUri; 19 | public final int name; 20 | 21 | public short attributeStart; 22 | public short attributeSize; 23 | public short attributeCount; 24 | public short idIndex; 25 | public short classIndex; 26 | public short styleIndex; 27 | 28 | public List attributes; 29 | 30 | public StartTagChunk(ByteBuffer byteBuffer, StringChunk stringChunk, List namespaceChunkList) { 31 | super(byteBuffer, stringChunk); 32 | namespaceUri = byteBuffer.getInt(); 33 | name = byteBuffer.getInt(); 34 | attributeStart = byteBuffer.getShort(); 35 | attributeSize = byteBuffer.getShort(); 36 | attributeCount = byteBuffer.getShort(); 37 | idIndex = byteBuffer.getShort(); 38 | classIndex = byteBuffer.getShort(); 39 | styleIndex = byteBuffer.getShort(); 40 | 41 | attributes = new ArrayList<>(attributeCount); 42 | for (int i = 0; i < attributeCount; i++) 43 | attributes.add(new Attribute(byteBuffer)); 44 | 45 | this.namespaceChunkList = namespaceChunkList; 46 | byteBuffer.position(ChunkStartPosition + chunkSize); 47 | } 48 | 49 | public static class Attribute { 50 | public final int namespaceUri; 51 | public final int name; 52 | public final int value; 53 | public final short structureSize; 54 | public final int Res0; 55 | public final int type; 56 | public final int data; 57 | 58 | public Attribute(ByteBuffer byteBuffer) { 59 | namespaceUri = byteBuffer.getInt(); 60 | name = byteBuffer.getInt(); 61 | value = byteBuffer.getInt(); 62 | structureSize = byteBuffer.getShort(); 63 | Res0 = byteBuffer.get() & 0xFF; 64 | type = byteBuffer.get() & 0xFF; 65 | data = byteBuffer.getInt(); 66 | } 67 | 68 | protected void toBytes(ByteArrayOutputStream stream) throws IOException { 69 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 * 4); 70 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 71 | byteBuffer.putInt(namespaceUri); 72 | byteBuffer.putInt(name); 73 | byteBuffer.putInt(value); 74 | byteBuffer.putShort(structureSize); 75 | byteBuffer.put((byte) Res0); 76 | byteBuffer.put((byte) type); 77 | byteBuffer.putInt(data); 78 | stream.write(byteBuffer.array()); 79 | } 80 | } 81 | 82 | @Override 83 | protected void toBytes(ByteArrayOutputStream stream) throws IOException { 84 | super.toBytes(stream); 85 | this.attributeCount = (short) attributes.size(); 86 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 * 4); 87 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 88 | byteBuffer.putInt(namespaceUri); 89 | byteBuffer.putInt(name); 90 | byteBuffer.putShort(attributeStart); 91 | byteBuffer.putShort(attributeSize); 92 | byteBuffer.putShort(attributeCount); 93 | byteBuffer.putShort(idIndex); 94 | byteBuffer.putShort(classIndex); 95 | byteBuffer.putShort(styleIndex); 96 | stream.write(byteBuffer.array()); 97 | for (Attribute attribute : attributes) 98 | attribute.toBytes(stream); 99 | } 100 | 101 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 102 | protected final List namespaceChunkList; 103 | 104 | protected String getPrefix(int uri) { 105 | for (NamespaceChunk namespaceChunk : namespaceChunkList) 106 | if (namespaceChunk.uri == uri) 107 | return getString(namespaceChunk.prefix); 108 | return null; 109 | } 110 | 111 | protected String getNameSpace() { 112 | StringBuilder nsBuilder = new StringBuilder(); 113 | if (namespaceChunkList != null)//add namespace 114 | for (NamespaceChunk namespaceChunk : namespaceChunkList) 115 | nsBuilder.append(" ").append(namespaceChunk.getXmlNameSpace()); 116 | return nsBuilder.toString(); 117 | } 118 | 119 | private boolean addxmlns = false; 120 | 121 | public void addXmlns() { 122 | addxmlns = true; 123 | } 124 | 125 | @Override 126 | public String toString() { 127 | StringBuilder tagBuilder = new StringBuilder(); 128 | if (comment > -1) tagBuilder.append("").append("\n"); 129 | tagBuilder.append('<'); 130 | 131 | String tagName = getString(name); 132 | tagBuilder.append(tagName); 133 | 134 | if (addxmlns) tagBuilder.append(getNameSpace()); 135 | 136 | for (Attribute attribute : attributes) { 137 | tagBuilder.append(" "); 138 | if (attribute.namespaceUri != -1) tagBuilder.append(getPrefix(attribute.namespaceUri)).append(":"); 139 | String data = TypedValue.coerceToString(attribute.type, attribute.data); 140 | tagBuilder.append(getString(attribute.name)) 141 | .append("=") 142 | .append('"') 143 | .append(StringUtils.isEmpty(data) ? getString(attribute.value) : data) 144 | .append('"'); 145 | } 146 | return tagBuilder.append(">\n").toString(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /xml/src/main/java/android/util/TypedValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package android.util; 18 | 19 | 20 | /** 21 | * Container for a dynamically typed data value. Primarily used with 22 | * {@link android.content.res.Resources} for holding resource values. 23 | */ 24 | public class TypedValue { 25 | /** 26 | * The value contains no data. 27 | */ 28 | public static final int TYPE_NULL = 0x00; 29 | 30 | /** 31 | * The data field holds a resource identifier. 32 | */ 33 | public static final int TYPE_REFERENCE = 0x01; 34 | /** 35 | * The data field holds an attribute resource 36 | * identifier (referencing an attribute in the current theme 37 | * style, not a resource entry). 38 | */ 39 | public static final int TYPE_ATTRIBUTE = 0x02; 40 | /** 41 | * The string field holds string data. In addition, if 42 | * data is non-zero then it is the string block 43 | * index of the string and assetCookie is the set of 44 | * assets the string came from. 45 | */ 46 | public static final int TYPE_STRING = 0x03; 47 | /** 48 | * The data field holds an IEEE 754 floating point number. 49 | */ 50 | public static final int TYPE_FLOAT = 0x04; 51 | /** 52 | * The data field holds a complex number encoding a 53 | * dimension value. 54 | */ 55 | public static final int TYPE_DIMENSION = 0x05; 56 | /** 57 | * The data field holds a complex number encoding a fraction 58 | * of a container. 59 | */ 60 | public static final int TYPE_FRACTION = 0x06; 61 | 62 | /** 63 | * Identifies the start of plain integer values. Any type value 64 | * from this to {@link #TYPE_LAST_INT} means the 65 | * data field holds a generic integer value. 66 | */ 67 | public static final int TYPE_FIRST_INT = 0x10; 68 | 69 | /** 70 | * The data field holds a number that was 71 | * originally specified in decimal. 72 | */ 73 | public static final int TYPE_INT_DEC = 0x10; 74 | /** 75 | * The data field holds a number that was 76 | * originally specified in hexadecimal (0xn). 77 | */ 78 | public static final int TYPE_INT_HEX = 0x11; 79 | /** 80 | * The data field holds 0 or 1 that was originally 81 | * specified as "false" or "true". 82 | */ 83 | public static final int TYPE_INT_BOOLEAN = 0x12; 84 | 85 | /** 86 | * Identifies the start of integer values that were specified as 87 | * color constants (starting with '#'). 88 | */ 89 | public static final int TYPE_FIRST_COLOR_INT = 0x1c; 90 | 91 | /** 92 | * The data field holds a color that was originally 93 | * specified as #aarrggbb. 94 | */ 95 | public static final int TYPE_INT_COLOR_ARGB8 = 0x1c; 96 | /** 97 | * The data field holds a color that was originally 98 | * specified as #rrggbb. 99 | */ 100 | public static final int TYPE_INT_COLOR_RGB8 = 0x1d; 101 | /** 102 | * The data field holds a color that was originally 103 | * specified as #argb. 104 | */ 105 | public static final int TYPE_INT_COLOR_ARGB4 = 0x1e; 106 | /** 107 | * The data field holds a color that was originally 108 | * specified as #rgb. 109 | */ 110 | public static final int TYPE_INT_COLOR_RGB4 = 0x1f; 111 | 112 | /** 113 | * Identifies the end of integer values that were specified as color 114 | * constants. 115 | */ 116 | public static final int TYPE_LAST_COLOR_INT = 0x1f; 117 | 118 | /** 119 | * Identifies the end of plain integer values. 120 | */ 121 | public static final int TYPE_LAST_INT = 0x1f; 122 | 123 | /* ------------------------------------------------------------ */ 124 | 125 | /** 126 | * Complex data: bit location of unit information. 127 | */ 128 | public static final int COMPLEX_UNIT_SHIFT = 0; 129 | /** 130 | * Complex data: mask to extract unit information (after shifting by 131 | * {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as 132 | * defined below. 133 | */ 134 | public static final int COMPLEX_UNIT_MASK = 0xf; 135 | 136 | /** 137 | * {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. 138 | */ 139 | public static final int COMPLEX_UNIT_PX = 0; 140 | /** 141 | * {@link #TYPE_DIMENSION} complex unit: Value is Device Independent 142 | * Pixels. 143 | */ 144 | public static final int COMPLEX_UNIT_DIP = 1; 145 | /** 146 | * {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. 147 | */ 148 | public static final int COMPLEX_UNIT_SP = 2; 149 | /** 150 | * {@link #TYPE_DIMENSION} complex unit: Value is in points. 151 | */ 152 | public static final int COMPLEX_UNIT_PT = 3; 153 | /** 154 | * {@link #TYPE_DIMENSION} complex unit: Value is in inches. 155 | */ 156 | public static final int COMPLEX_UNIT_IN = 4; 157 | /** 158 | * {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. 159 | */ 160 | public static final int COMPLEX_UNIT_MM = 5; 161 | 162 | /** 163 | * {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall 164 | * size. 165 | */ 166 | public static final int COMPLEX_UNIT_FRACTION = 0; 167 | /** 168 | * {@link #TYPE_FRACTION} complex unit: A fraction of the parent size. 169 | */ 170 | public static final int COMPLEX_UNIT_FRACTION_PARENT = 1; 171 | 172 | /** 173 | * Complex data: where the radix information is, telling where the decimal 174 | * place appears in the mantissa. 175 | */ 176 | public static final int COMPLEX_RADIX_SHIFT = 4; 177 | /** 178 | * Complex data: mask to extract radix information (after shifting by 179 | * {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point 180 | * representations as defined below. 181 | */ 182 | public static final int COMPLEX_RADIX_MASK = 0x3; 183 | 184 | /** 185 | * Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0 186 | */ 187 | public static final int COMPLEX_RADIX_23p0 = 0; 188 | /** 189 | * Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn 190 | */ 191 | public static final int COMPLEX_RADIX_16p7 = 1; 192 | /** 193 | * Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn 194 | */ 195 | public static final int COMPLEX_RADIX_8p15 = 2; 196 | /** 197 | * Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn 198 | */ 199 | public static final int COMPLEX_RADIX_0p23 = 3; 200 | 201 | /** 202 | * Complex data: bit location of mantissa information. 203 | */ 204 | public static final int COMPLEX_MANTISSA_SHIFT = 8; 205 | /** 206 | * Complex data: mask to extract mantissa information (after shifting by 207 | * {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision; 208 | * the top bit is the sign. 209 | */ 210 | public static final int COMPLEX_MANTISSA_MASK = 0xffffff; 211 | 212 | /* ------------------------------------------------------------ */ 213 | 214 | /** 215 | * {@link #TYPE_NULL} data indicating the value was not specified. 216 | */ 217 | public static final int DATA_NULL_UNDEFINED = 0; 218 | /** 219 | * {@link #TYPE_NULL} data indicating the value was explicitly set to null. 220 | */ 221 | public static final int DATA_NULL_EMPTY = 1; 222 | 223 | /* ------------------------------------------------------------ */ 224 | 225 | /** 226 | * If {@link #density} is equal to this value, then the density should be 227 | * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}. 228 | */ 229 | public static final int DENSITY_DEFAULT = 0; 230 | 231 | /** 232 | * If {@link #density} is equal to this value, then there is no density 233 | * associated with the resource and it should not be scaled. 234 | */ 235 | public static final int DENSITY_NONE = 0xffff; 236 | 237 | /* ------------------------------------------------------------ */ 238 | 239 | /** 240 | * The type held by this value, as defined by the constants here. 241 | * This tells you how to interpret the other fields in the object. 242 | */ 243 | public int type; 244 | 245 | /** 246 | * If the value holds a string, this is it. 247 | */ 248 | public CharSequence string; 249 | 250 | /** 251 | * Basic data in the value, interpreted according to {@link #type} 252 | */ 253 | public int data; 254 | 255 | /** 256 | * Additional information about where the value came from; only 257 | * set for strings. 258 | */ 259 | public int assetCookie; 260 | 261 | /** 262 | * If the Value came from a resource, this holds the corresponding pixel density. 263 | */ 264 | public int density; 265 | 266 | /* ------------------------------------------------------------ */ 267 | 268 | /** 269 | * Return the data for this value as a float. Only use for values 270 | * whose type is {@link #TYPE_FLOAT}. 271 | */ 272 | public final float getFloat() { 273 | return Float.intBitsToFloat(data); 274 | } 275 | 276 | private static final float MANTISSA_MULT = 277 | 1.0f / (1 << TypedValue.COMPLEX_MANTISSA_SHIFT); 278 | private static final float[] RADIX_MULTS = new float[]{ 279 | 1.0f * MANTISSA_MULT, 1.0f / (1 << 7) * MANTISSA_MULT, 280 | 1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT 281 | }; 282 | 283 | /** 284 | * Retrieve the base value from a complex data integer. This uses the 285 | * {@link #COMPLEX_MANTISSA_MASK} and {@link #COMPLEX_RADIX_MASK} fields of 286 | * the data to compute a floating point representation of the number they 287 | * describe. The units are ignored. 288 | * 289 | * @param complex A complex data value. 290 | * @return A floating point value corresponding to the complex data. 291 | */ 292 | public static float complexToFloat(int complex) { 293 | return (complex & (TypedValue.COMPLEX_MANTISSA_MASK 294 | << TypedValue.COMPLEX_MANTISSA_SHIFT)) 295 | * RADIX_MULTS[(complex >> TypedValue.COMPLEX_RADIX_SHIFT) 296 | & TypedValue.COMPLEX_RADIX_MASK]; 297 | } 298 | 299 | /** 300 | * Return the complex unit type for this value. For example, a dimen type 301 | * with value 12sp will return {@link #COMPLEX_UNIT_SP}. Only use for values 302 | * whose type is {@link #TYPE_DIMENSION}. 303 | * 304 | * @return The complex unit type. 305 | */ 306 | public int getComplexUnit() { 307 | return COMPLEX_UNIT_MASK & (data >> TypedValue.COMPLEX_UNIT_SHIFT); 308 | } 309 | 310 | 311 | /** 312 | * Converts a complex data value holding a fraction to its final floating 313 | * point value. The given data must be structured as a 314 | * {@link #TYPE_FRACTION}. 315 | * 316 | * @param data A complex data value holding a unit, magnitude, and 317 | * mantissa. 318 | * @param base The base value of this fraction. In other words, a 319 | * standard fraction is multiplied by this value. 320 | * @param pbase The parent base value of this fraction. In other 321 | * words, a parent fraction (nn%p) is multiplied by this 322 | * value. 323 | * @return The complex floating point value multiplied by the appropriate 324 | * base value depending on its unit. 325 | */ 326 | public static float complexToFraction(int data, float base, float pbase) { 327 | switch ((data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK) { 328 | case COMPLEX_UNIT_FRACTION: 329 | return complexToFloat(data) * base; 330 | case COMPLEX_UNIT_FRACTION_PARENT: 331 | return complexToFloat(data) * pbase; 332 | } 333 | return 0; 334 | } 335 | 336 | /** 337 | * Return the data for this value as a fraction. Only use for values whose 338 | * type is {@link #TYPE_FRACTION}. 339 | * 340 | * @param base The base value of this fraction. In other words, a 341 | * standard fraction is multiplied by this value. 342 | * @param pbase The parent base value of this fraction. In other 343 | * words, a parent fraction (nn%p) is multiplied by this 344 | * value. 345 | * @return The complex floating point value multiplied by the appropriate 346 | * base value depending on its unit. 347 | */ 348 | public float getFraction(float base, float pbase) { 349 | return complexToFraction(data, base, pbase); 350 | } 351 | 352 | /** 353 | * Regardless of the actual type of the value, try to convert it to a 354 | * string value. For example, a color type will be converted to a 355 | * string of the form #aarrggbb. 356 | * 357 | * @return CharSequence The coerced string value. If the value is 358 | * null or the type is not known, null is returned. 359 | */ 360 | public final CharSequence coerceToString() { 361 | int t = type; 362 | if (t == TYPE_STRING) { 363 | return string; 364 | } 365 | return coerceToString(t, data); 366 | } 367 | 368 | private static final String[] DIMENSION_UNIT_STRS = new String[]{ 369 | "px", "dip", "sp", "pt", "in", "mm" 370 | }; 371 | private static final String[] FRACTION_UNIT_STRS = new String[]{ 372 | "%", "%p" 373 | }; 374 | 375 | /** 376 | * Perform type conversion as per {@link #coerceToString()} on an 377 | * explicitly supplied type and data. 378 | * 379 | * @param type The data type identifier. 380 | * @param data The data value. 381 | * @return String The coerced string value. If the value is 382 | * null or the type is not known, null is returned. 383 | */ 384 | public static final String coerceToString(int type, int data) { 385 | switch (type) { 386 | case TYPE_NULL: 387 | return null; 388 | case TYPE_REFERENCE: 389 | return "@" + data; 390 | case TYPE_ATTRIBUTE: 391 | return "?" + data; 392 | case TYPE_FLOAT: 393 | return Float.toString(Float.intBitsToFloat(data)); 394 | case TYPE_DIMENSION: 395 | return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[ 396 | (data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK]; 397 | case TYPE_FRACTION: 398 | return Float.toString(complexToFloat(data) * 100) + FRACTION_UNIT_STRS[ 399 | (data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK]; 400 | case TYPE_INT_HEX: 401 | return "0x" + Integer.toHexString(data); 402 | case TYPE_INT_BOOLEAN: 403 | return data != 0 ? "true" : "false"; 404 | } 405 | 406 | if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) { 407 | return "#" + Integer.toHexString(data); 408 | } else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) { 409 | return Integer.toString(data); 410 | } 411 | 412 | return null; 413 | } 414 | 415 | 416 | } 417 | --------------------------------------------------------------------------------