안드로이드 바이너리 XML 의 구조.
APK 파일의 압축을 풀면 classes.dex 파일과 그리고 String 과 drawable 과 같은 리소스 맵핑 정보를 담고 있는 resources.arsc 파일등과 AndroidManifest.xml 파일 외에 res 폴더 내에 여러 XML 파일등이 존재한다. 하지만 이 XML 파일은 우리가 흔히 아는 형식으로 되어있지 않다.
왜 APK 파일 내부에 있는 AndroidManifast.xml 을 비롯한 XML 파일들은 우리가 알고 있는 XML 형식이 아닌걸까? 아마도 공간을 정약하고 런타임 환경에서 빨리 리소스 정보를 읽어들일 수 있도록 바이너리 타입으로 만들었을 것이다.
안드로이드에서 사용하는 바이너리 타입의 XML 포맷에 대하여 아래 사이트에서 자세하게 설명하고 있다.
https://justanapplication.wordpress.com/category/android/android-binary-xml/
위의 링크를 타고 들어가서 언급되는 내용을 보면 알겠지만, 안드로이드 바이너리 XML 의 구조는 잘 들여다보면 이해하기 쉽고 단순한 구조로 되어있다.
위 그림에서 나온 구조를 정리해 보자면...
1. Header
- 2byte. xml 바이너리의 사이즈를 담고 있다.
2. StringPool
- 0x08 위치부터 시작한다.
- XML 내부의 String 값들에 대한 배열을 갖고 있다. 외부에서 이 String 배열의 인덱스 값을 참고하여 접근한다.
3. XMLResouceMap
- 리소스 맵에 대한 정보를 갖고 있다.
4. Namespace - EndNamesapce
- Start 와 end 타입으로 분류된다.
- Start 와 End 사이에 있는 Element 들은 자식 노드(Element)가 된다.
5.Element
- 엘리먼트. 내부에 Attribute 에 대한 배열을 갖고 있다.
- NameSpace 와 마찬가지로 Start 와 End 타입으로 분류되고 그 사이에는 자식 노드(Element)가 들어간다.
와 같은 구조로 간단하게 되어있다.
중요한 점은 Namespace 와 Element 에 대한 Chunk 는 Start 와 End 값을 갖고 있으며 그 사이에 들어가는 값들은 자식 노드가 된다.
또, 각 data 들의 String 값은 integer 타입으로 되어 있는데, StringPool 내부의 String 배열에 대한 인덱스 값을 나타내기 때문이다. 즉 StringPool 의 배열로부터 인덱스 값을 이용하여 실제 String 데이터를 찾을 수 있다.
구조를 좀 더 확실하게 이해하기 위하여
https://justanapplication.wordpress.com/category/android/android-binary-xml/
에 나온 설명을 이용하여 파서를 만들어 보았다.
코드 주소 : https://github.com/ice3x2/AndroidXMLReader
퍼포먼스는 전~~~혀 고려하지 않고, 그냥 구조를 확인하기 위하여 코드만 작성했는데 정신차려보니 파서가 완성되어 있었다. ㅡ , , ㅡ;;
애초에 실제 구조만 확인하려고 만든 것이기 때문에 테스트 케이스 같은 것은 안중에도 없었다. 혹시나 버그가 있을지도 모르니 참고하기 바란다... (물론 앞으로 계속 개선하여 좀 쓸만한 모듈로 만들 생각이다;;)
어쨌든 이 코드는 안드로이드 APK 내부의 바이너리 타입 XML 구조를 확인하는데 여러분들께 큰 도움이 될 것 이다.
이 것을 이용하여 JAVA 코드 상에서 APK 내부의 AndroidManifest.XML 내용을 읽어오는 방법은 다음과 같다.
AndroidXML androidXML = AndroidXMLReader.readAndroidManifest("test.apk"); Element rootElement = androidXML.getRootElement(); String packageName = rootElement.findAttribute("package")[0].getValue(); String versionName = rootElement.findAttribute("versionName")[0].getValue(); String versionCode = rootElement.findAttribute("versionCode")[0].getValue(); Element[] userPermissions = rootElement.findElements("uses-permission"); System.out.println("Package : " + packageName); System.out.println("Version name : " + versionName); System.out.println("Version code : " + versionCode); System.out.println("uses-permission"); for(Element element : userPermissions) { Attribute attribute = element.getAttribute(0); System.out.println("\t" + attribute.getName() + " : " + attribute.getValue()); }
이 시리즈의 다음 포스팅에선 resources.arsc 의 내부 구조와 파싱 방법에 대하여 알아보겠다.