緣起
我爲什嗎要研究apktool的資源反編譯呢,這是因爲我最近在反編譯抖音620版本的時候發現不能反編譯成功,主要是對資源的反編譯過程失敗,我其實不太關注資源的反編譯,因爲我只想研究它的源碼,我可以通過-r選項來跳過資源的反編譯。 但是我比較好奇,爲啥不能反編譯資源成功呢,所以今天就來一探究竟。
編譯
下載apktool的源碼比較簡單,直接通過github下載,apktool使用gradle構建,所以直接導入android studio就可以編譯了。
代碼分析
爲了分析apktool我也是下足了功夫,把aapt和assertmanager都研究了一遍,沒有背景知識的讀者可以參考我的文章Android資源索引–resources.arsc。下面直接分析代碼:
brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java
public void decode() throws AndrolibException, IOException, DirectoryException {
try {
......
if (hasResources()) {
switch (mDecodeResources) {
case DECODE_RESOURCES_NONE:
......
break;
case DECODE_RESOURCES_FULL:
setTargetSdkVersion();
setAnalysisMode(mAnalysisMode, true);
if (hasManifest()) {
mAndrolib.decodeManifestWithResources(mApkFile, outDir, getResTable());
}
mAndrolib.decodeResourcesFull(mApkFile, outDir, getResTable());
break;
}
} else {
......
}
......
}
我們這裏只關注完全反編譯資源文件的情況,也就是DECODE_RESOURCES_FULL所在的分支下面的代碼,首先通過Androlib類的decodeManifestWithResources方法來解析AndroidManifest.xnk文件,這裏主要解析出包名,和sdk版本等信息。我們不深入分析。主要分析Androlib.decodeResourcesFull 函數。getRestable()函數主要創建一個ResTable類,用於表示resources.arsc反編譯後的數據結構。
public ResTable getResTable() throws AndrolibException {
if (mResTable == null) {
boolean hasResources = hasResources();
boolean hasManifest = hasManifest();
if (! (hasManifest || hasResources)) {
throw new AndrolibException(
"Apk doesn't contain either AndroidManifest.xml file or resources.arsc file");
}
mResTable = mAndrolib.getResTable(mApkFile, hasResources);
}
return mResTable;
}
ResTable的生成和數據結構的填充主要是通過Androlib.getResTable方法。
brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java
public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg)
throws AndrolibException {
ResTable resTable = new ResTable(this);
if (loadMainPkg) {
loadMainPkg(resTable, apkFile);
}
return resTable;
}
public ResPackage loadMainPkg(ResTable resTable, ExtFile apkFile)
throws AndrolibException {
LOGGER.info("Loading resource table...");
ResPackage[] pkgs = getResPackagesFromApk(apkFile, resTable, sKeepBroken);
ResPackage pkg = null;
switch (pkgs.length) {
case 1:
pkg = pkgs[0];
break;
case 2:
if (pkgs[0].getName().equals("android")) {
LOGGER.warning("Skipping \"android\" package group");
pkg = pkgs[1];
break;
} else if (pkgs[0].getName().equals("com.htc")) {
LOGGER.warning("Skipping \"htc\" package group");
pkg = pkgs[1];
break;
}
default:
pkg = selectPkgWithMostResSpecs(pkgs);
break;
}
if (pkg == null) {
throw new AndrolibException("arsc files with zero packages or no arsc file found.");
}
resTable.addPackage(pkg, true);
return pkg;
}
ResTable的數據填充主要通過loadMainPkg函數完成。loadMainPkg又通過getResPackagesFromApk函數完成。
private ResPackage[] getResPackagesFromApk(ExtFile apkFile,ResTable resTable, boolean keepBroken)
throws AndrolibException {
try {
Directory dir = apkFile.getDirectory();
BufferedInputStream bfi = new BufferedInputStream(dir.getFileInput("resources.arsc"));
try {
return ARSCDecoder.decode(bfi, false, keepBroken, resTable).getPackages();
} finally {
try {
bfi.close();
} catch (IOException ignored) {}
}
} catch (DirectoryException ex) {
throw new AndrolibException("Could not load resources.arsc from file: " + apkFile, ex);
}
}
層層調用到了ARSCDecoder.decode方法,真是繁瑣啊
public static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets, boolean keepBroken,
ResTable resTable)
throws AndrolibException {
try {
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
ResPackage[] pkgs = decoder.readTableHeader();
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
? null
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]), resTable);
} catch (IOException ex) {
throw new AndrolibException("Could not decode arsc file", ex);
}
}
public static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets, boolean keepBroken,
ResTable resTable)
throws AndrolibException {
try {
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
ResPackage[] pkgs = decoder.readTableHeader();
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
? null
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]), resTable);
} catch (IOException ex) {
throw new AndrolibException("Could not decode arsc file", ex);
}
}
來看ARSCDecoder調用readTableHeader,這個方法叫readTableHeader,實則把整個resources.arsc都解析了
private ResPackage[] readTableHeader() throws IOException, AndrolibException {
nextChunkCheckType(Header.TYPE_TABLE);
int packageCount = mIn.readInt();
mTableStrings = StringBlock.read(mIn);
ResPackage[] packages = new ResPackage[packageCount];
nextChunk();
for (int i = 0; i < packageCount; i++) {
mTypeIdOffset = 0;
packages[i] = readTablePackage();
}
return packages;
}
首先讀取Restable_header數據,也就是nextChunkCheckType(Header.TYPE_TABLE)函數,不明白的請參考Android資源索引–resources.arsc一文中的resources.arsc結構圖。StringBlock.read(mIn)函數讀取valueStrings字符串池。readTablePackage函數則是加載包信息。
87 private ResPackage readTablePackage() throws IOException, AndrolibException {
88 checkChunkType(Header.TYPE_PACKAGE);
89 int id = mIn.readInt();
90
91 if (id == 0) {
92 // This means we are dealing with a Library Package, we should just temporarily
93 // set the packageId to the next available id . This will be set at runtime regardless, but
94 // for Apktool's use we need a non-zero packageId.
95 // AOSP indicates 0x02 is next, as 0x01 is system and 0x7F is private.
96 id = 2;
97 if (mResTable.getPackageOriginal() == null && mResTable.getPackageRenamed() == null) {
98 mResTable.setSharedLibrary(true);
99 }
100 }
101
102 String name = mIn.readNullEndedString(128, true);
103 /* typeStrings */mIn.skipInt();
104 /* lastPublicType */mIn.skipInt();
105 /* keyStrings */mIn.skipInt();
106 /* lastPublicKey */mIn.skipInt();
107
108 // TypeIdOffset was added platform_frameworks_base/@f90f2f8dc36e7243b85e0b6a7fd5a590893c827e
109 // which is only in split/new applications.
110 int splitHeaderSize = (2 + 2 + 4 + 4 + (2 * 128) + (4 * 5)); // short, short, int, int, char[128], int * 4
111 if (mHeader.headerSize == splitHeaderSize) {
112 mTypeIdOffset = mIn.readInt();
113 }
114
115 if (mTypeIdOffset > 0) {
116 LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/1728");
117 }
118
119 mTypeNames = StringBlock.read(mIn);
120 mSpecNames = StringBlock.read(mIn);
121
122 mResId = id << 24;
123 mPkg = new ResPackage(mResTable, id, name);
124
125 nextChunk();
126 while (mHeader.type == Header.TYPE_LIBRARY) {
127 readLibraryType();
128 }
129
130 while (mHeader.type == Header.TYPE_SPEC_TYPE) {
131 readTableTypeSpec();
132 }
133
134 return mPkg;
135 }
88行讀取ResTable_package頭。
89行讀取包的id。
102行讀取包名。
103-106行後面跳過typeStrings,lastPublicType,keyStrings和lastPublicKey的值。
119行讀取類型字符串池。
120讀取名稱字符串池。
126行跳過庫相關信息。
130行讀取類型信息。
我們深入分析類型信息讀取,也就是readTableTypeSpec()函數。
155 private void readTableTypeSpec() throws AndrolibException, IOException {
156 mTypeSpec = readSingleTableTypeSpec();
157 addTypeSpec(mTypeSpec);
158
159 int type = nextChunk().type;
160 ResTypeSpec resTypeSpec;
161
162 while (type == Header.TYPE_SPEC_TYPE) {
163 resTypeSpec = readSingleTableTypeSpec();
164 addTypeSpec(resTypeSpec);
165 type = nextChunk().type;
166
167 // We've detected sparse resources, lets record this so we can rebuild in that same format (sparse/not)
168 // with aapt2. aapt1 will ignore this.
169 if (! mResTable.getSparseResources()) {
170 mResTable.setSparseResources(true);
171 }
172 }
173
174 while (type == Header.TYPE_TYPE) {
175 readTableType();
176
177 // skip "TYPE 8 chunks" and/or padding data at the end of this chunk
178 if (mCountIn.getCount() < mHeader.endPosition) {
179 LOGGER.warning("Unknown data detected. Skipping: " + (mHeader.endPosition - mCountIn.getCount()) + " byte(s)");
180 mCountIn.skip(mHeader.endPosition - mCountIn.getCount());
181 }
182
183 type = nextChunk().type;
184
185 addMissingResSpecs();
186 }
187 }
156行調用readSingleTableTypeSpec函數解析ResTable_typeSpec。
162-172讀取多個ResTable_typeSpec,用於讀取無效的ResTable_typeSpec。
174-186行調用readTableType函數讀取多個ResTable_type數據結構。
下面我們展開分析readSingleTableTypeSpec函數和readTableType函數。
private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_SPEC_TYPE);
int id = mIn.readUnsignedByte();
mIn.skipBytes(3);
int entryCount = mIn.readInt();
if (mFlagsOffsets != null) {
mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount));
}
/* flags */mIn.skipBytes(entryCount * 4);
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
mPkg.addType(mTypeSpec);
return mTypeSpec;
}
這裏主要解析Restable_typeSpec, 數據結構,這裏用ResTypeSpec類表示,將該結構添加到該包信息下。在來看readTableType函數
205 private ResType readTableType() throws IOException, AndrolibException {
206 checkChunkType(Header.TYPE_TYPE);
207 int typeId = mIn.readUnsignedByte() - mTypeIdOffset;
208 if (mResTypeSpecs.containsKey(typeId)) {
209 mResId = (0xff000000 & mResId) | mResTypeSpecs.get(typeId).getId() << 16;
210 mTypeSpec = mResTypeSpecs.get(typeId);
211 }
212
213 int typeFlags = mIn.readByte();
214 /* reserved */mIn.skipBytes(2);
215 int entryCount = mIn.readInt();
216 int entriesStart = mIn.readInt();
217 mMissingResSpecs = new boolean[entryCount];
218 Arrays.fill(mMissingResSpecs, true);
219
220 ResConfigFlags flags = readConfigFlags();
221 int position = (mHeader.startPosition + entriesStart) - (entryCount * 4);
222
223 // For some APKs there is a disconnect between the reported size of Configs
224 // If we find a mismatch skip those bytes.
225 if (position != mCountIn.getCount()) {
226 LOGGER.warning("Invalid data detected. Skipping: " + (position - mCountIn.getCount()) + " byte(s)");
227 mIn.skipBytes(position - mCountIn.getCount());
228 }
229
230 if (typeFlags == 1) {
231 LOGGER.info("Sparse type flags detected: " + mTypeSpec.getName());
232 }
233 int[] entryOffsets = mIn.readIntArray(entryCount);
234
235 if (flags.isInvalid) {
236 String resName = mTypeSpec.getName() + flags.getQualifiers();
237 if (mKeepBroken) {
238 LOGGER.warning("Invalid config flags detected: " + resName);
239 } else {
240 LOGGER.warning("Invalid config flags detected. Dropping resources: " + resName);
241 }
242 }
243
244 mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags);
245 HashMap<Integer, EntryData> offsetsToEntryData = new HashMap<Integer, EntryData>();
246
247 for (int offset : entryOffsets) {
248 if (offset == -1 || offsetsToEntryData.containsKey(offset)) {
249 continue;
250 }
251
252 offsetsToEntryData.put(offset, readEntryData());
253 }
254
255 for (int i = 0; i < entryOffsets.length; i++) {
256 if (entryOffsets[i] != -1) {
257 mMissingResSpecs[i] = false;
258 mResId = (mResId & 0xffff0000) | i;
259 EntryData entryData = offsetsToEntryData.get(entryOffsets[i]);
260 readEntry(entryData);
261 }
262 }
263
264 return mType;
265 }
213-233行讀取entry_offset信息。
247-253行根據entry_offset讀取entry的信息,主要通過函數readEntryData進行讀取。
255-262行對值信息進行引用解析。
private EntryData readEntryData() throws IOException, AndrolibException {
short size = mIn.readShort();
if (size < 0) {
throw new AndrolibException("Entry size is under 0 bytes.");
}
short flags = mIn.readShort();
int specNamesId = mIn.readInt();
ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ? readValue() : readComplexEntry();
EntryData entryData = new EntryData();
entryData.mFlags = flags;
entryData.mSpecNamesId = specNamesId;
entryData.mValue = value;
return entryData;
}
private ResIntBasedValue readValue() throws IOException, AndrolibException {
/* size */mIn.skipCheckShort((short) 8);
/* zero */mIn.skipCheckByte((byte) 0);
byte type = mIn.readByte();
int data = mIn.readInt();
return type == TypedValue.TYPE_STRING
? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
: mPkg.getValueFactory().factory(type, data, null);
}
readEntryData函數簡單的很,readValue()函數用於解析Restable_entry類型的值,readComplexEntry()用於解析Restable_map_entry類型的值(也就是attr類型的值)。
mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)用於讀取字符串類型的值。
brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java
public ResIntBasedValue factory(String value, int rawValue) {
if (value == null) {
return new ResFileValue("", rawValue);
}
if (value.startsWith("res/")) {
return new ResFileValue(value, rawValue);
}
if (value.startsWith("r/") || value.startsWith("R/")) { //AndroResGuard
return new ResFileValue(value, rawValue);
}
return new ResStringValue(value, rawValue);
}
對於String類型的值,如果值以res/開有或者 r/,R/開頭則被認爲是ResFileValue類型的值。其他字符串類型的值爲ResStringValue。這裏的做法其實有點問題,如果我們寫了一個字符串如下
<string>res/nihao</string> 則被識別爲ResFileValue類型的值,這顯然是錯誤的。我們關注下後面怎麼處理。
對於Complex資源值的解析如下:
329 private ResBagValue readComplexEntry() throws IOException, AndrolibException {
330 int parent = mIn.readInt();
331 int count = mIn.readInt();
332
333 ResValueFactory factory = mPkg.getValueFactory();
334 Duo<Integer, ResScalarValue>[] items = new Duo[count];
335 ResIntBasedValue resValue;
336 int resId;
337
338 for (int i = 0; i < count; i++) {
339 resId = mIn.readInt();
340 resValue = readValue();
341
342 if (resValue instanceof ResScalarValue) {
343 items[i] = new Duo<Integer, ResScalarValue>(resId, (ResScalarValue) resValue);
344 } else {
345 resValue = new ResStringValue(resValue.toString(), resValue.getRawIntValue());
346 items[i] = new Duo<Integer, ResScalarValue>(resId, (ResScalarValue) resValue);
347 }
348 }
349
350 return factory.bagFactory(parent, items, mTypeSpec);
351 }
這裏Duo來表示一個bag值,這裏Duo類似與Pair對象,用於保存兩個對象。
339讀取bag的id。
340讀取bag的值。
343行將id和值保存在Duo對象中。
345-346行是針對FileResValue, 把rawvalue和路徑保存在ResStringValue中保存。
350行factory.bagFactory(parent, items, mTypeSpec) 創建bag值返回。
87 public ResBagValue bagFactory(int parent, Duo<Integer, ResScalarValue>[] items, ResTypeSpec resTypeSpec) throws AndrolibException {
88 ResReferenceValue parentVal = newReference(parent, null);
89
90 if (items.length == 0) {
91 return new ResBagValue(parentVal);
92 }
93 int key = items[0].m1;
94 if (key == ResAttr.BAG_KEY_ATTR_TYPE) {
95 return ResAttr.factory(parentVal, items, this, mPackage);
96 }
97
98 String resTypeName = resTypeSpec.getName();
99
100 // Android O Preview added an unknown enum for c. This is hardcoded as 0 for now.
101 if (ResTypeSpec.RES_TYPE_NAME_ARRAY.equals(resTypeName)
102 || key == ResArrayValue.BAG_KEY_ARRAY_START || key == 0) {
103 return new ResArrayValue(parentVal, items);
104 }
105
106 if (ResTypeSpec.RES_TYPE_NAME_PLURALS.equals(resTypeName) ||
107 (key >= ResPluralsValue.BAG_KEY_PLURALS_START && key <= ResPluralsValue.BAG_KEY_PLURALS_END)) {
108 return new ResPluralsValue(parentVal, items);
109 }
110
111 if (ResTypeSpec.RES_TYPE_NAME_STYLES.equals(resTypeName)) {
112 return new ResStyleValue(parentVal, items, this);
113 }
114
115 if (ResTypeSpec.RES_TYPE_NAME_ATTR.equals(resTypeName)) {
116 return new ResAttr(parentVal, 0, null, null, null);
117 }
118
119 throw new AndrolibException("unsupported res type name for bags. Found: " + resTypeName);
120 }
94行如果item[0]的bag是^type,bagkey 是^type對應的bagkey,則說明這是一個attr類型。所以這裏使用ResAttr.factory(parentVal, items, this, mPackage)來創建attr。
95行該資源爲一個attr類型的entry。
103爲 數組類型的entry
106爲PLURAL類型的entry
112爲style類型。
116爲attr類型。
這說明支持bag類型的資源包括attr,arrat,plurals,style類型。
我們來看下BAG_KEY_ATTR_TYPE情況下創建attr。
68 public static ResAttr factory(ResReferenceValue parent,
69 Duo<Integer, ResScalarValue>[] items, ResValueFactory factory,
70 ResPackage pkg) throws AndrolibException {
71
72 int type = ((ResIntValue) items[0].m2).getValue();
73 int scalarType = type & 0xffff;
74 Integer min = null, max = null;
75 Boolean l10n = null;
76 int i;
77 for (i = 1; i < items.length; i++) {
78 switch (items[i].m1) {
79 case BAG_KEY_ATTR_MIN:
80 min = ((ResIntValue) items[i].m2).getValue();
81 continue;
82 case BAG_KEY_ATTR_MAX:
83 max = ((ResIntValue) items[i].m2).getValue();
84 continue;
85 case BAG_KEY_ATTR_L10N:
86 l10n = ((ResIntValue) items[i].m2).getValue() != 0;
87 continue;
88 }
89 break;
90 }
91
92 if (i == items.length) {
93 return new ResAttr(parent, scalarType, min, max, l10n);
94 }
95 Duo<ResReferenceValue, ResIntValue>[] attrItems = new Duo[items.length
96 - i];
97 int j = 0;
98 for (; i < items.length; i++) {
99 int resId = items[i].m1;
100 pkg.addSynthesizedRes(resId);
101 attrItems[j++] = new Duo<ResReferenceValue, ResIntValue>(
102 factory.newReference(resId, null),
103 (ResIntValue) items[i].m2);
104 }
105 switch (type & 0xff0000) {
106 case TYPE_ENUM:
107 return new ResEnumAttr(parent, scalarType, min, max, l10n,
108 attrItems);
109 case TYPE_FLAGS:
110 return new ResFlagsAttr(parent, scalarType, min, max, l10n,
111 attrItems);
112 }
113
114 throw new AndrolibException("Could not decode attr value");
115 }
71行獲取^type對應的值,也就是整個attr entry的類型。
77-88行對integer類型的attr的min屬性和max屬性以及BAG_KEY_ATTR_L10N。
89-104行對bag創建數據結構Duo<ResReferenceValue, ResIntValue>,左邊的ResReferenceValue是bagkey_id的引用,右邊ResIntValue用於保存bagvalue,因爲bagvalue爲32位整數,所以用ResIntValue描述。
105-112行創建對應的Entry數據結構,這部分會根據不同的entry類型創建不同的數據結構,包含兩種TYPE_ENUM爲attr中enmu類型的資源。TYPE_FLAGS則爲attr中flags類型的資源。
到這裏整個數據resources.arsc的解析就大概明瞭了。
下面再看解析好的數據如何反編譯爲資源文件。
brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java
public void decodeResourcesFull(ExtFile apkFile, File outDir, ResTable resTable)
throws AndrolibException {
mAndRes.decode(resTable, apkFile, outDir);
}
Androlib不但負責反編譯資源信息還要負責反編譯代碼信息。它主要使用AndrolibResources類來進行反編譯資源文件的。這裏調用了AndrolibResources.decode函數來反編譯資源。
brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java
225 public void decode(ResTable resTable, ExtFile apkFile, File outDir)
226 throws AndrolibException {
227 Duo<ResFileDecoder, AXmlResourceParser> duo = getResFileDecoder();
228 ResFileDecoder fileDecoder = duo.m1;
229 ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();
230
231 attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next());
232 Directory inApk, in = null, out;
233
234 try {
235 out = new FileDirectory(outDir);
236
237 inApk = apkFile.getDirectory();
238 out = out.createDir("res");
239 if (inApk.containsDir("res")) {
240 in = inApk.getDir("res");
241 }
242 if (in == null && inApk.containsDir("r")) {
243 in = inApk.getDir("r");
244 }
245 if (in == null && inApk.containsDir("R")) {
246 in = inApk.getDir("R");
247 }
248 } catch (DirectoryException ex) {
249 throw new AndrolibException(ex);
250 }
251
252 ExtMXSerializer xmlSerializer = getResXmlSerializer();
253 for (ResPackage pkg : resTable.listMainPackages()) {
254 attrDecoder.setCurrentPackage(pkg);
255
256 LOGGER.info("Decoding file-resources...");
257 for (ResResource res : pkg.listFiles()) {
258 fileDecoder.decode(res, in, out);
259 }
260
261 LOGGER.info("Decoding values */* XMLs...");
262 for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
263 generateValuesFile(valuesFile, out, xmlSerializer);
264 }
265 generatePublicXml(pkg, out, xmlSerializer);
266 }
267
268 AndrolibException decodeError = duo.m2.getFirstError();
269 if (decodeError != null) {
270 throw decodeError;
271 }
272 }
258行解析文件類型的資源,我們就不看了。
268行解析value類型的資源。我們詳細分析一下。
這個過程有點麻煩,所有類型的value都重寫了serializeToResValuesXml方法,用於反序列化value值。我們只看ResEnumAttr類型的值的反序列化。它的反序列化函數在父類ResAttr中實現。
@Override
public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException {
String type = getTypeAsString();
serializer.startTag(null, "attr");
serializer.attribute(null, "name", res.getResSpec().getName());
if (type != null) {
serializer.attribute(null, "format", type);
}
if (mMin != null) {
serializer.attribute(null, "min", mMin.toString());
}
if (mMax != null) {
serializer.attribute(null, "max", mMax.toString());
}
if (mL10n != null && mL10n) {
serializer.attribute(null, "localization", "suggested");
}
serializeBody(serializer, res);
serializer.endTag(null, "attr");
}
這個函數很簡單,不詳細說明了,我們只看下serializeBody方法,在ResAttr中的serializeBody是一個空方法,在ResEnumAttr中有自己的實現。我們具體分析他。
49 @Override
50 protected void serializeBody(XmlSerializer serializer, ResResource res)
51 throws AndrolibException, IOException {
52 for (Duo<ResReferenceValue, ResIntValue> duo : mItems) {
53 int intVal = duo.m2.getValue();
54
55 serializer.startTag(null, "enum");
56 serializer.attribute(null, "name", duo.m1.getReferent().getName());
57 serializer.attribute(null, "value", String.valueOf(intVal));
58 serializer.endTag(null, "enum");
59 }
60 }
對於枚舉類型的body寫入方法,如下:
舉個例子吧,
<attr name="attr_enum" >
<enum name="enum1" value="1"/>
<enum name="enum2" value="2"/>
</attr>
body就是 這個xml node包裹的值。這個方法就是用於生成xml的這些標籤和值。
56行獲取name屬性,從bag的bagkey_id獲取,也就是duo.m1.getReferent().getName()。
57行則解析對應的值。
我們來看下我反翻譯抖音620版本遇到的問題:
Disconnected from the target VM, address: '127.0.0.1:57659', transport: 'socket'
Exception in thread "main" java.lang.NullPointerException
at brut.androlib.res.data.value.ResEnumAttr.serializeBody(ResEnumAttr.java:56)
at brut.androlib.res.data.value.ResAttr.serializeToResValuesXml(ResAttr.java:64)
at brut.androlib.res.AndrolibResources.generateValuesFile(AndrolibResources.java:742)
at brut.androlib.res.AndrolibResources.decode(AndrolibResources.java:263)
at brut.androlib.Androlib.decodeResourcesFull(Androlib.java:129)
at brut.androlib.ApkDecoder.decode(ApkDecoder.java:124)
at brut.apktool.Main.cmdDecode(Main.java:170)
at brut.apktool.Main.main(Main.java:76)
serializer.attribute(null, “name”, duo.m1.getReferent().getName()); 這行代碼出了問題
也就是說這個bagkey_id沒有對應的id entry,所以發生了崩潰。