淺析android應用增量升級

來源:互聯網
上載者:User

                             By
何明桂(http://blog.csdn.net/hmg25) 轉載請註明出處

       很久沒有更新部落格了,真是墮落啊,幾次想提起筆,卻總是被各種瑣事耽擱,以後會多寫文章記錄點滴。

背景        隨著android應用體積的不斷增大,以及應用版本發布的不斷更迭,使用者的升級成了一個問題,google也意識到不斷更新應用對使用者流量的損耗,在Google I/O 上提及的 Smart App update,即應用增量升級,或者叫做差分升級的做法,並在新版本的Google Play中得到支援,某天在和群友聊天是扯到這方面的話題,好奇就稍微研究了一下。增量升級的原理        今天我們就來實作類別似的應用的增量升級。其實增量升級的原理很簡單,即首先將應用的舊版本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系統差分包的製作,使用命令:
  ./build/tools/releasetools/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校正,保證基礎包的一致性。小實驗       多說無益,實踐才是王道。下面就來簡單實踐一下,檢測之前理論的正確性。下載實驗包 (http://download.csdn.net/detail/hmg25/4676737),解壓後檔案如下
├── 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             //手機端的測試載入器├── iReader1.6.2.0(v35).apk      // 舊版本的apk└── iReader1.8.0.1(v40).apk    //新版本的apk

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

   bsdiff.exe   ../iReader1.6.2.0(v35).apk   ../iReader1.8.0.1(v40).apk   ../ireader.patch

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

  bspatch.exe  ../iReader1.6.2.0(v35).apk   ../new.apk    ../ireader.patch

     執行後得到名為new.apk 的合成版本應用,我在ubuntu下進行校正可以看出他們是一樣的。

       下面我們在手機端合成看看,將根目錄下的bspatch(此為手機端啟動並執行)、iReader1.6.2.0(v35).apk 和ireader.patch ,通過adb push到你有許可權操作的目錄,最好是在/sdcard/下,然後設定bspatch的執行許可權,重複操作上述命令,可以合成新版本的apk,稍後安裝查看驗證版本即可,不詳述。擴充閱讀      使用bsdiff 進行差分升級,還並不是最優的方式,google在它的Chromium項目中,對這個差分演算法進行了最佳化,最佳化後的版本叫做小胡瓜Courgette,據說效能最佳化了很多不是一個數量級了,官方的一個例子:
Full update       10,385,920bsdiff update 
   704,512Courgette update  
   78,848
       大牛們可以去研究下。
      最近有些小忙,稍後有時間會對增量升級進行封裝下,將合成的代碼弄成一個lib庫,供java調用。有興趣的童鞋可以自己操作一下~~~            By
何明桂(http://blog.csdn.net/hmg25) 轉載請註明出處   原裝正版,盜版必究 ^_^

補充:很多人不知道怎麼進行封裝為lib,其實這個和一般的android的C庫是一樣的,不明白的看看jni相關的知識,原來的測試工程已經找不到了,下邊我給出個簡單的例子,拋磚引玉,給大家參考下。

 

#include <stdio.h>#include "com_hmg25_newstart_BSpatch.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,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 *old, *new;    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) ||        ((old=malloc(oldsize+1))==NULL) ||        (lseek(fd,0,SEEK_SET)!=0) ||        (read(fd,old,oldsize)!=oldsize) ||        (close(fd)==-1)) err(1,"%s",argv[1]);    if((new=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, new + 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))                new[newpos+i]+=old[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, new + 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,new,newsize)!=newsize) || (close(fd)==-1))        err(1,"%s",argv[2]);    free(new);    free(old);    return 0;}JNIEXPORT jint JNICALL Java_com_hmg25_newstart_BSpatch_applyPatch(JNIEnv *env,        jobject obj, jstring old, jstring new , jstring patch){  int argc=4;  char * argv[argc];  argv[0]="bspatch";  argv[1]=(*env)->GetStringUTFChars(env,old, 0);  argv[2]=(*env)->GetStringUTFChars(env,new, 0);  argv[3]=(*env)->GetStringUTFChars(env,patch, 0);    int ret=applypatch(argc, argv);     (*env)->ReleaseStringUTFChars(env,old,argv[1]);   (*env)->ReleaseStringUTFChars(env,new,argv[2]);   (*env)->ReleaseStringUTFChars(env,patch,argv[3]);   return ret;}

Android.mk:

LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)# This is the target being built.LOCAL_MODULE:= libbspatch# All of the source files that we will compile.LOCAL_SRC_FILES:= \  com_hmg25_newstart_BSpatch.c# No static libraries.LOCAL_STATIC_LIBRARIES := \     libbz# Also need the JNI headers.LOCAL_C_INCLUDES += \    $(JNI_H_INCLUDE) external/bzip2# No special compiler flags.LOCAL_CFLAGS +=include $(BUILD_SHARED_LIBRARY)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.