Android增量升級簡單實現(附源碼)

來源:互聯網
上載者:User

標籤:android   源碼   java   

 

隨著現在手機硬體不斷的提升,解析度提高手機的安裝包也是越來越大了。當年NOKIA,MOTO時代,一個手機APP如果有1MB那都是算大的,2MB已經不得了了。雖然網路、儲存都已經大大提升,但是流量還不至於廉價到APP改了一個標題要去下載一個幾兆的程式安裝包。今天就介紹安卓增量下載的實現。有耐心的先看原理,後面實踐!

增量升級的原理

今天我們就來實作類別似的應用的增量升級。其實增量升級的原理很簡單,即首先將應用的舊版本Apk與新版本Apk做差分,得到更新的部分的補丁,例如舊版本的APK有5M,新版的有8M,更新的部分則可能只有3M左右(這裡需要說明的是,得到的差分包大小並不是簡單的相減,因為其實需要包含一些上下文相關的東西),使用差分升級的好處顯而易見,那麼你不需要下載完整的8M檔案,只需要下載更新部分就可以,而更新部分可能只有3、4M,可以很大程度上減少流量的損失。
在使用者下載了差分包之後,需要在手機端將他們組合起來。可以參考的做法是先將手機端的舊版本軟體(多半在/data/下),複製到SD卡或者cache中,將它們和之前的差分patch進行組合,得到一個新版本的apk應用,如果不出意外的話,這個產生的apk和你之前做差分的apk是一致的。

增量升級的操作

在瞭解基本的原理之後,我們來逐步解決其中的各個痛點。首先是差分包patch的產生。如果做過android手機OTA升級的同學應該注意到,在update.zip中的patch檔案夾中有需要與系統檔案同名但是以xxx.p 為尾碼的檔案,他們就是產生的差分patch檔案。我們可以借鑒OTA系統升級的差分產生工具來產生我們單個應用apk的差分patch檔案。OTA系統差分包的製作,使用命令:

./ota_from_target_files -n -i <舊包> <新包> <差分包名>
在查閱ota_from_target_files 的代碼可知,是在函數WriteIncrementalOTAPackage裡產生差分包的,在這個函數裡邊建立了common.Difference這個類,我們繼續跟進,在common.py中的類 class Difference(object):裡可以看到:

diff_program = DIFF_PROGRAM_BY_EXT.get(ext, “bsdiff”)

至此我們就看到了android中提供我們用來製作差分增量升級包的工具,”bsdiff”,這是一個很牛X開源的二進位差分工具.相關的介紹傳送門 相關的代碼地址 或者在android的代碼目錄下 \external\bsdiff bsdiff是二進位差分工具,其對應的bspatch是相應的補丁合成工具 需要注意的是增量升級的補丁包,是需要在伺服器端,即PC端完成,大致流程如,製作補丁時調用bsdiff函數,根據兩個不同版本的二進位檔案,產生補丁檔案。

命令:bsdiff oldfile newfile patchfile
例如: bsdiff xx_v1.0.apk xx_v2.0.apk xx.patch

將產生的補丁包 xx.patch放置在升級伺服器上,供使用者下載升級,對應多版本需要對不同的版本進行差分,對於版本跨度較大的,建議整包升級。 使用者在下載了 xx.patch補丁包後,需要用到補丁所對應的apk,即原來系統安裝的舊版本apk和補丁合成的bspatch工具。系統舊版本的apk可以通過copy系統data/app目錄下的apk檔案擷取,而補丁合成的bspatch可以通過將bspatch源碼稍作修改,封裝成一個so庫,供手機端調用。

bspatch的命令格式為:
bspatch oldfile newfile patchfile

和差分時的參數一樣。合成新的apk便可以用於安裝。 以上只是簡單的操作原理,增量升級還涉及很多其他方面,例如,升級補丁校正等問題,可以參考android源碼中bootable\recovery\applypatch的相關操作,本文只是淺析,在此不表。 不足 增量升級並非完美無缺的升級方式,至少存在以下兩點不足: 1.增量升級是以兩個應用版本之間的差異來產生補丁的,你無法保證使用者每次的及時升級到最新,所以你必須對你所發布的每一個版本都和最新的版本作差分,以便使所有版本的使用者都可以差分升級,這樣操作相對於原來的整包升級較為繁瑣,不過可以通過自動化的指令碼批量產生。 2.增量升級成功的前提是,使用者手機端必須有能夠讓你拷貝出來且與你伺服器用於差分的版本一致的apk,這樣就存在,例如,系統內建的apk無法擷取到,無法進行增量升級;對於某些與你差分版本一致,但是內容有過修改的(比如破解版apk),這樣也是無法進行增量升級的,為了防止合成補丁錯誤,最好在補丁合成前對舊版本的apk進行sha1sum校正,保證基礎包的一致性。 小實驗 多說無益,實踐才是王道。下面就來簡單實踐一下,檢測之前理論的正確性。

├── bsdiff-4.3 //bsdiff的源碼路徑,官網擷取
│ ├── bsdiff.1
│ ├── bsdiff.c
│ ├── bspatch.1
│ ├── bspatch.c
│ └── Makefile
├── bsdiff-4.3.tar.gz
├── bsdiff4.3-win32 //windows PC端的測試載入器
│ ├── Binary diff.txt
│ ├── bsdiff.exe
│ ├── bspatch.exe
│ └── LICENSE
├── bspatch //手機端的測試載入器
├── oldAPK1.6.2.apk // 舊版本的apk
└── newAPK1.8.0.apk //新版本的apk

APK來做測試,在shell進入test\bsdiff4.3-win32檔案夾,並下運行命令:

1 bsdiff.exe oldAPK1.6.2.apk newAPK1.8.0.apk apk.patch

原來的apk(2.94M),新版本的(3.24M),得到的patch檔案為1.77M,使用者需要下載的就只是1.77M,流量節省了很多。 下面先在電腦端將他們合并。


bspatch.exe oldAPK1.6.2.apk new.apk apk.patch

執行後得到名為new.apk 的合成版本應用。這個和我們newAPK1.8.0.apk其實是一樣的。
現在寫一個安卓小DEMO出來,測試一下這個工具。直接在建立安卓工程的時候添加native支援,在CPP檔案中添加以下代碼

#include "com_droidupdate_jni_PatchUtil.h"#include "bzlib_private.h"#include "bzlib.h"#include <stdlib.h>#include <stdio.h>#include <string.h>#include <err.h>#include <unistd.h>#include <fcntl.h>#include <android/log.h>static off_t offtin(u_char *buf) {off_t y;y = buf[7] & 0x7F;y = y * 256;y += buf[6];y = y * 256;y += buf[5];y = y * 256;y += buf[4];y = y * 256;y += buf[3];y = y * 256;y += buf[2];y = y * 256;y += buf[1];y = y * 256;y += buf[0];if (buf[7] & 0x80)y = -y;return y;}int applypatch(int argc, const char* argv[]) {FILE * f, *cpf, *dpf, *epf;BZFILE * cpfbz2, *dpfbz2, *epfbz2;int cbz2err, dbz2err, ebz2err;int fd;ssize_t oldsize, newsize;ssize_t bzctrllen, bzdatalen;u_char header[32], buf[8];u_char *oldStr, *newStr;off_t oldpos, newpos;off_t ctrl[3];off_t lenread;off_t i;if (argc != 4)errx(1, "usage: %s oldfile newfile patchfile\n", argv[0]);/* Open patch file */if ((f = fopen(argv[3], "r")) == NULL)err(1, "fopen(%s)", argv[3]);/* File format: 0   8   "BSDIFF40" 8   8   X 16  8   Y 24  8   sizeof(newfile) 32  X   bzip2(control block) 32+X    Y   bzip2(diff block) 32+X+Y  ??? bzip2(extra block) with control block a set of triples (x,y,z) meaning "add x bytes from oldfile to x bytes from the diff block; copy y bytes from the extra block; seek forwards in oldfile by z bytes". *//* Read header */if (fread(header, 1, 32, f) < 32) {if (feof(f))errx(1, "Corrupt patch\n");err(1, "fread(%s)", argv[3]);}/* Check for appropriate magic */if (memcmp(header, "BSDIFF40", 8) != 0)errx(1, "Corrupt patch\n");/* Read lengths from header */bzctrllen = offtin(header + 8);bzdatalen = offtin(header + 16);newsize = offtin(header + 24);if ((bzctrllen < 0) || (bzdatalen < 0) || (newsize < 0))errx(1, "Corrupt patch\n");/* Close patch file and re-open it via libbzip2 at the right places */if (fclose(f))err(1, "fclose(%s)", argv[3]);if ((cpf = fopen(argv[3], "r")) == NULL)err(1, "fopen(%s)", argv[3]);if (fseeko(cpf, 32, SEEK_SET))err(1, "fseeko(%s, %lld)", argv[3], (long long) 32);if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);if ((dpf = fopen(argv[3], "r")) == NULL)err(1, "fopen(%s)", argv[3]);if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))err(1, "fseeko(%s, %lld)", argv[3], (long long) (32 + bzctrllen));if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);if ((epf = fopen(argv[3], "r")) == NULL)err(1, "fopen(%s)", argv[3]);if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))err(1, "fseeko(%s, %lld)", argv[3],(long long) (32 + bzctrllen + bzdatalen));if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);if (((fd = open(argv[1], O_RDONLY, 0)) < 0)|| ((oldsize = lseek(fd, 0, SEEK_END)) == -1) || ((oldStr =(u_char*) malloc(oldsize + 1)) == NULL)|| (lseek(fd, 0, SEEK_SET) != 0)|| (read(fd, oldStr, oldsize) != oldsize) || (close(fd) == -1))err(1, "%s", argv[1]);if ((newStr = (u_char*) malloc(newsize + 1)) == NULL)err(1, NULL);oldpos = 0;newpos = 0;while (newpos < newsize) {/* Read control data */for (i = 0; i <= 2; i++) {lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);if ((lenread < 8)|| ((cbz2err != BZ_OK) && (cbz2err != BZ_STREAM_END)))errx(1, "Corrupt patch\n");ctrl[i] = offtin(buf);};/* Sanity-check */if (newpos + ctrl[0] > newsize)errx(1, "Corrupt patch\n");/* Read diff string */lenread = BZ2_bzRead(&dbz2err, dpfbz2, newStr + newpos, ctrl[0]);if ((lenread < ctrl[0])|| ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))errx(1, "Corrupt patch\n");/* Add old data to diff string */for (i = 0; i < ctrl[0]; i++)if ((oldpos + i >= 0) && (oldpos + i < oldsize))newStr[newpos + i] += oldStr[oldpos + i];/* Adjust pointers */newpos += ctrl[0];oldpos += ctrl[0];/* Sanity-check */if (newpos + ctrl[1] > newsize)errx(1, "Corrupt patch\n");/* Read extra string */lenread = BZ2_bzRead(&ebz2err, epfbz2, newStr + newpos, ctrl[1]);if ((lenread < ctrl[1])|| ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))errx(1, "Corrupt patch\n");/* Adjust pointers */newpos += ctrl[1];oldpos += ctrl[2];};/* Clean up the bzip2 reads */BZ2_bzReadClose(&cbz2err, cpfbz2);BZ2_bzReadClose(&dbz2err, dpfbz2);BZ2_bzReadClose(&ebz2err, epfbz2);if (fclose(cpf) || fclose(dpf) || fclose(epf))err(1, "fclose(%s)", argv[3]);/* Write the new file */if (((fd = open(argv[2], O_CREAT | O_TRUNC | O_WRONLY, 0666)) < 0)|| (write(fd, newStr, newsize) != newsize) || (close(fd) == -1))err(1, "%s", argv[2]);free(newStr);free(oldStr);return 0;}jint JNICALL Java_com_droidupdate_jni_PatchUtil_applyPatchToOldApk(JNIEnv *pEnv,jclass clazz, jstring oldPath, jstring newPath, jstring patchPath) {const char* pOldPath = pEnv->GetStringUTFChars(oldPath, JNI_FALSE);const char* pNewPath = pEnv->GetStringUTFChars(newPath, JNI_FALSE);const char* pPatchPath = pEnv->GetStringUTFChars(patchPath, JNI_FALSE);const char* argv[4];argv[0] = "bspatch";argv[1] = pOldPath;argv[2] = pNewPath;argv[3] = pPatchPath;int ret = -1;ret = applypatch(4, argv);pEnv->ReleaseStringUTFChars(oldPath, pOldPath);pEnv->ReleaseStringUTFChars(newPath, pNewPath);pEnv->ReleaseStringUTFChars(patchPath, pPatchPath);return ret;}

需要發布升級包的時候,把新打好的包用windows的bsdiff.exe製作好.patch檔案,然後我們程式檢測到新版本的時候就下載這 個.patch檔案,然後調用這個JNI函數把.patch檔案和當前的版本比較產生一個最新版本的apk檔案,然後通過 application/vnd.android.package-archive來安裝即可!

下面是工具和安卓端測試源碼我放在我另外一個部落格上面了。需要的直接存取下載。

 

IT十萬為什麼 ? Android增量升級詳解


Android增量升級簡單實現(附源碼)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.