一種動態寫入apk數據的方法(用於用戶關係綁定、添加渠道號等)

一種動態寫入apk數據的方法(用於用戶關係綁定、添加渠道號等)

背景

正在開發的APP須要記錄業務員與客戶的綁定關係。具體應用場景以下: html

由流程圖可知,並無用戶填寫業務人員信息這一步,所以在用戶下載的APP中就已經攜帶了業務人員的信息。java

因爲業務人員衆多,不可能針對於每個業務人員單獨生成一個安裝包,因而就有了動態修改APP安裝包的想法。android

原理

Android使用的apk包的壓縮方式是zip,與zip有相同的文件結構(zip文件結構見zip文件格式說明),在zipEOCD區域中包含一個Comment區域。安全

若是咱們可以正確修改該區域,就能夠在不破壞壓縮包、不從新打包的前提下快速給apk文件寫入本身想要的數據。app

apk默認狀況下沒有Comment,因此Comment lengthshort兩個字節爲0,咱們須要把這個值修改成咱們的Comment長度,並把Comment追加到後面便可。dom

總體過程

服務端實現

實現下載接口

@RequestMapping(value = "/download", method = RequestMethod.GET)
public void download(@RequestParam String token, HttpServletResponse response) throws Exception {

    // 獲取乾淨的apk文件
    Resource resource = new ClassPathResource("app-release.apk");
    File file = resource.getFile();

    // 拷貝一份新文件(在新文件基礎上進行修改)
    File realFile = copy(file.getPath(), file.getParent() + "/" + new Random().nextLong() + ".apk");

    // 寫入註釋信息
    writeApk(realFile, token);

    // 若是文件名存在,則進行下載
    if (realFile != null && realFile.exists()) {
        // 配置文件下載
        response.setHeader("content-type", "application/octet-stream");
        response.setContentType("application/octet-stream");
        // 下載文件能正常顯示中文
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realFile.getName(), "UTF-8"));

        // 實現文件下載
        byte[] buffer = new byte[1024];
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        try {
            fis = new FileInputStream(realFile);
            bis = new BufferedInputStream(fis);
            OutputStream os = response.getOutputStream();
            int i = bis.read(buffer);
            while (i != -1) {
                os.write(buffer, 0, i);
                i = bis.read(buffer);
            }
            System.out.println("Download successfully!");
        } catch (Exception e) {
            System.out.println("Download failed!");
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
複製代碼

拷貝文件

private File copy(String source, String target) {
    Path sourcePath = Paths.get(source);
    Path targetPath = Paths.get(target);

    try {
        return Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING).toFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
複製代碼

往apk中寫入信息

public static void writeApk(File file, String comment) {
    ZipFile zipFile = null;
    ByteArrayOutputStream outputStream = null;
    RandomAccessFile accessFile = null;
    try {
        zipFile = new ZipFile(file);

        // 若是已有comment,則不進行寫入操做(其實能夠先擦除再寫入)
        String zipComment = zipFile.getComment();
        if (zipComment != null) {
            return;
        }

        byte[] byteComment = comment.getBytes();
        outputStream = new ByteArrayOutputStream();

        // comment內容
        outputStream.write(byteComment);
        // comment長度(方便讀取)
        outputStream.write(short2Stream((short) byteComment.length));

        byte[] data = outputStream.toByteArray();

        accessFile = new RandomAccessFile(file, "rw");
        accessFile.seek(file.length() - 2);

        // 重寫comment實際長度
        accessFile.write(short2Stream((short) data.length));
        // 寫入comment內容
        accessFile.write(data);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (zipFile != null) {
                zipFile.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
            if (accessFile != null) {
                accessFile.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製代碼

其中:ide

private static byte[] short2Stream(short data) {
    ByteBuffer buffer = ByteBuffer.allocate(2);
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    buffer.putShort(data);
    buffer.flip();
    return buffer.array();
}
複製代碼

客戶端實現

獲取comment信息並寫入TextView

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    TextView textView = findViewById(R.id.tv_world);

    // 獲取包路徑(安裝包所在路徑)
    String path = getPackageCodePath();
    // 獲取業務員信息
    String content = readApk(path);

    textView.setText(content);
}
複製代碼

讀取comment信息

public String readApk(String path) {
    byte[] bytes = null;
    try {
        File file = new File(path);
        RandomAccessFile accessFile = new RandomAccessFile(file, "r");
        long index = accessFile.length();

        // 文件最後兩個字節表明了comment的長度
        bytes = new byte[2];
        index = index - bytes.length;
        accessFile.seek(index);
        accessFile.readFully(bytes);

        int contentLength = bytes2Short(bytes, 0);

        // 獲取comment信息
        bytes = new byte[contentLength];
        index = index - bytes.length;
        accessFile.seek(index);
        accessFile.readFully(bytes);

        return new String(bytes, "utf-8");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
複製代碼

其中:測試

private static short bytes2Short(byte[] bytes, int offset) {
    ByteBuffer buffer = ByteBuffer.allocate(2);
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    buffer.put(bytes[offset]);
    buffer.put(bytes[offset + 1]);
    return buffer.getShort(0);
}
複製代碼

遇到的問題

修改完comment以後沒法安裝成功

最開始遇到的就是沒法安裝的問題,一開始覺得是下載接口寫的有問題,通過屢次調試以後發現是修改完comment以後apk就沒法安裝了。spa

查詢谷歌官方文檔可知 線程

所以,只須要打包的時候簽名方式只選擇V1不選擇V2就行。


多人同時下載搶佔文件致使的線程安全問題

這個問題暫時的考慮方案是每當有下載請求就會先複製一份,將複製的文件進行修改,客戶端下載成功再刪除。

可是未作測試,不知是否會產生問題。

思考

  • 服務端和客戶端不同,服務端的任何請求都須要考慮線程同步問題;
  • 既然客戶端能夠獲取到安裝包,則其實也能夠經過修改包名來進行業務人員信息的傳遞;
  • 利用該方法能夠傳遞其餘數據用來實現其餘一些功能,不侷限於業務人員的信息。
相關文章
相關標籤/搜索