通過ZIP文件格式的多渠道打包技術(shù)

原理

由于Android apk包使用的是壓縮方式是zip。在zip中有一個區(qū)域,可以存放數(shù)據(jù)。若正確的修改這個部分,就可以在不破壞包同時不用重新打包的前提下,給apk寫入數(shù)據(jù)。

在每一個zip文件的結(jié)尾,都有這樣一組數(shù)據(jù)[資料來源wiki](https://en.wikipedia.org/wiki/Zip_(file_format))

End of central directory record (EOCD)

Offset Bytes Description[25]
0 4 End of central directory signature = 0x06054b50
4 2 Number of this disk
6 2 Disk where central directory starts
8 2 Number of central directory records on this disk
10 2 Total number of central directory records
12 4 Size of central directory (bytes)
16 4 Offset of start of central directory, relative to start of archive
20 2 Comment length (n)
22 n Comments

倒數(shù)第一個是,注釋的內(nèi)容,倒數(shù)第二個是注釋內(nèi)容的長度。

核心讀寫過程

寫入過程

定義一個流,將需要的數(shù)據(jù)按照寫入RandomAccessFile.因為無法讀知道寫入內(nèi)容的長度,因此需要寫入長度。其次是定義一個Flag,方便程序在讀取的時候找到數(shù)據(jù)。

private static void write(File path, byte[] content, String password) throws Exception {
        
        ZipFile zipFile = new ZipFile(path);
        boolean isIncludeComment = zipFile.getComment() != null;
        zipFile.close();
        if (isIncludeComment) {
            throw new IllegalStateException("Zip comment is exists, Repeated write is not recommended.");
        }
        
        boolean isEncrypt = password != null && password.length() > 0;
        byte[] bytesContent = isEncrypt ? encrypt(password, content) : content;
        byte[] bytesVersion = VERSION_1_1.getBytes(CHARSET_NAME);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(bytesContent); // 寫入內(nèi)容;
        baos.write(short2Stream((short) bytesContent.length)); // 寫入內(nèi)容長度;
        baos.write(isEncrypt ? 1 : 0); // 寫入是否加密標示;
        baos.write(bytesVersion); // 寫入版本號;
        baos.write(short2Stream((short) bytesVersion.length)); // 寫入版本號長度;
        baos.write(SIG.getBytes(CHARSET_NAME)); // 寫入SIG標記;
        byte[] data = baos.toByteArray();
        baos.close();
        if (data.length > Short.MAX_VALUE) {
            throw new IllegalStateException("Zip comment length > 32767.");
        }
        
        // Zip文件末尾數(shù)據(jù)結(jié)構(gòu):{@see java.util.zip.ZipOutputStream.writeEND}
        RandomAccessFile raf = new RandomAccessFile(path, "rw");
        raf.seek(path.length() - 2); // comment長度是short類型
        raf.write(short2Stream((short) data.length)); // 重新寫入comment長度,注意Android apk文件使用的是ByteOrder.LITTLE_ENDIAN(小端序);
        raf.write(data);
        raf.close();
    }

讀取過程

讀取的過程可以直接使用Java的getComment()非常的方便,但是實際測試發(fā)現(xiàn),這個方法只支持Java7以上的,因此對于Android 4.4.x之前是無法支持的。所以,先要去讀取到最后面咋們加入的comment注釋的長度,根據(jù)這個長度才能得到真真的內(nèi)容。

private static byte[] read(File path, String password) throws Exception {
        byte[] bytesContent = null;
        byte[] bytesMagic = SIG.getBytes(CHARSET_NAME);
        byte[] bytes = new byte[bytesMagic.length];
        RandomAccessFile raf = new RandomAccessFile(path, "r");
        Object[] versions = getVersion(raf);
        long index = (long) versions[0];
        String version = (String) versions[1];
        if (VERSION_1_1.equals(version)) {
            bytes = new byte[1];
            index -= bytes.length;
            readFully(raf, index, bytes); // 讀取內(nèi)容長度;
            boolean isEncrypt = bytes[0] == 1;

            bytes = new byte[2];
            index -= bytes.length;
            readFully(raf, index, bytes); // 讀取內(nèi)容長度;
            int lengthContent = stream2Short(bytes, 0);

            bytesContent = new byte[lengthContent];
            index -= lengthContent;
            readFully(raf, index, bytesContent); // 讀取內(nèi)容;

            if (isEncrypt && password != null && password.length() > 0) {
                bytesContent = decrypt(password, bytesContent);
            }
        }
        raf.close();
        return bytesContent;
    }

使用方法

用法:java -jar MCPTool.jar [-path] [arg] [-contents] [arg] [-password] [arg]
-path APK文件路徑
-outdir 輸出路徑(可選),默認輸出到APK文件同一目錄
-contents 寫入內(nèi)容集合,多個內(nèi)容之間用“#”分割,如:googleplay#0_1039_1039008
-password 加密密鑰(可選),長度8位以上,如果沒有該參數(shù),不加密
例如:
寫入:java -jar MCPTool.jar -path D:/test.apk -outdir ./ -contents googleplay#0_1039_1039008 -password 12345678
讀取:java -jar MCPTool.jar -path D:/test.apk -password 12345678

前端使用

通過Python,寫一個簡單的讀取文件,將其變成參數(shù)后,通過引入os包調(diào)用改方法。

后續(xù)可以的改進

  • 現(xiàn)在的參數(shù)password是比較簡單的,沒有做校驗的,再后面可以優(yōu)化的這一部分
  • Python腳本目前的地址寫的是絕對地址
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,590評論 19 139
  • 1.創(chuàng)建文件夾 !/bin/sh mkdir -m 777 "%%1" 2.創(chuàng)建文件 !/bin/sh touch...
    BigJeffWang閱讀 10,495評論 3 53
  • 之前發(fā)過一個帖子,但是那個帖子有點問題我就重新發(fā)一個吧,下面的源碼是我從今年開始不斷整理源碼區(qū)和其他網(wǎng)站上的安卓例...
    passiontim閱讀 22,108評論 181 334
  • 如果射手座愛你。 (1)愛到不像射手座 (2)粘人 會吃醋 沒有安全感 (3)會變乖 都聽你的都聽你的 你說得對 ...
    滄山映水閱讀 710評論 0 0
  • 嚴重“拖延癥患者”的我終于在新課程開始前“一字一句”地讀完了本次課程的全部內(nèi)容。感觸太多,擇重點而發(fā)。 良好的“夫...
    老鷹歸來閱讀 558評論 1 4

友情鏈接更多精彩內(nèi)容