apktool反編譯資源分析

緣起

        我爲什嗎要研究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,所以發生了崩潰。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章