很久沒有更新博客了,真是墮落啊,幾次想提起筆,卻總是被各種瑣事耽擱,以后會(huì)多寫文章記錄點(diǎn)滴。
背景
隨著android應(yīng)用體積的不斷增大,以及應(yīng)用版本發(fā)布的不斷更迭,用戶的升級(jí)成了一個(gè)問題,google也意識(shí)到不斷更新應(yīng)用對(duì)用戶流量的損耗,在Google I/O 上提及的 Smart App update,即應(yīng)用增量升級(jí),或者叫做差分升級(jí)的做法,并在新版本的Google Play中得到支持,某天在和群友聊天是扯到這方面的話題,好奇就稍微研究了一下。
增量升級(jí)的原理
今天我們就來實(shí)現(xiàn)類似的應(yīng)用的增量升級(jí)。其實(shí)增量升級(jí)的原理很簡(jiǎn)單,即首先將應(yīng)用的舊版本Apk與新版本Apk做差分,得到更新的部分的補(bǔ)丁,例如舊版本的APK有5M,新版的有8M,更新的部分則可能只有3M左右(這里需要說明的是,得到的差分包大小并不是簡(jiǎn)單的相減,因?yàn)槠鋵?shí)需要包含一些上下文相關(guān)的東西),使用差分升級(jí)的好處顯而易見,那么你不需要下載完整的8M文件,只需要下載更新部分就可以,而更新部分可能只有3、4M,可以很大程度上減少流量的損失。 在用戶下載了差分包之后,需要在手機(jī)端將他們組合起來。可以參考的做法是先將手機(jī)端的舊版本軟件(多半在/data/下),復(fù)制到SD卡或者cache中,將它們和之前的差分patch進(jìn)行組合,得到一個(gè)新版本的apk應(yīng)用,如果不出意外的話,這個(gè)生成的apk和你之前做差分的apk是一致的。
增量升級(jí)的操作
在了解基本的原理之后,我們來逐步解決其中的各個(gè)難點(diǎn)。首先是差分包patch的生成。如果做過android手機(jī)OTA升級(jí)的同學(xué)應(yīng)該注意到,在update.zip中的patch文件夾中有需要與系統(tǒng)文件同名但是以xxx.p 為后綴的文件,他們就是生成的差分patch文件。我們可以借鑒OTA系統(tǒng)升級(jí)的差分生成工具來生成我們單個(gè)應(yīng)用apk的差分patch文件。 OTA系統(tǒng)差分包的制作,使用命令:
[html]?view plaincopyprint?
./build/tools/releasetools/ota_from_target_files?-n?-i?<舊包>?<新包>?<差分包名>?? 在查閱ota_from_target_files 的代碼可知,是在函數(shù)WriteIncrementalOTAPackage里生成差分包的,在這個(gè)函數(shù)里邊創(chuàng)建了common.Difference這個(gè)類,我們繼續(xù)跟進(jìn),在common.py中的類 ? class Difference(object):里可以看到: ? ?
[html]?view plaincopyprint?
diff_program?=?DIFF_PROGRAM_BY_EXT.get(ext,?"bsdiff")?? ? ? ? ?至此我們就看到了android中提供我們用來制作差分增量升級(jí)包的工具,"bsdiff",這是一個(gè)很牛X開源的二進(jìn)制差分工具.相關(guān)的介紹傳送門 相關(guān)的代碼地址?或者在android的代碼目錄下 \external\bsdiff bsdiff是二進(jìn)制差分工具,其對(duì)應(yīng)的bspatch是相應(yīng)的補(bǔ)丁合成工具 需要注意的是增量升級(jí)的補(bǔ)丁包,是需要在服務(wù)器端,即PC端完成,大致流程如,制作補(bǔ)丁時(shí)調(diào)用bsdiff函數(shù),根據(jù)兩個(gè)不同版本的二進(jìn)制文件,生成補(bǔ)丁文件。?
[html]?view plaincopyprint?
命令:bsdiff?oldfile?newfile?patchfile??例如:?bsdiff?xx_v1.0.apk?xx_v2.0.apk?xx.patch?? 將生成的補(bǔ)丁包 xx.patch放置在升級(jí)服務(wù)器上,供用戶下載升級(jí),對(duì)應(yīng)多版本需要對(duì)不同的版本進(jìn)行差分,對(duì)于版本跨度較大的,建議整包升級(jí)。 用戶在下載了 xx.patch補(bǔ)丁包后,需要用到補(bǔ)丁所對(duì)應(yīng)的apk,即原來系統(tǒng)安裝的舊版本apk和補(bǔ)丁合成的bspatch工具。系統(tǒng)舊版本的apk可以通過copy系統(tǒng)data/app目錄下的apk文件獲取,而補(bǔ)丁合成的bspatch可以通過將bspatch源碼稍作修改,封裝成一個(gè)so庫(kù),供手機(jī)端調(diào)用。
[html]?view plaincopyprint?
bspatch的命令格式為:??bspatch?oldfile?newfile?patchfile?? 和差分時(shí)的參數(shù)一樣。合成新的apk便可以用于安裝。 以上只是簡(jiǎn)單的操作原理,增量升級(jí)還涉及很多其他方面,例如,升級(jí)補(bǔ)丁校驗(yàn)等問題,可以參考android源碼中bootable\recovery\applypatch的相關(guān)操作,本文只是淺析,在此不表。
不足
增量升級(jí)并非完美無(wú)缺的升級(jí)方式,至少存在以下兩點(diǎn)不足: 1.增量升級(jí)是以兩個(gè)應(yīng)用版本之間的差異來生成補(bǔ)丁的,你無(wú)法保證用戶每次的及時(shí)升級(jí)到最新,所以你必須對(duì)你所發(fā)布的每一個(gè)版本都和最新的版本作差分,以便使所有版本的用戶都可以差分升級(jí),這樣操作相對(duì)于原來的整包升級(jí)較為繁瑣,不過可以通過自動(dòng)化的腳本批量生成。 2.增量升級(jí)成功的前提是,用戶手機(jī)端必須有能夠讓你拷貝出來且與你服務(wù)器用于差分的版本一致的apk,這樣就存在,例如,系統(tǒng)內(nèi)置的apk無(wú)法獲取到,無(wú)法進(jìn)行增量升級(jí);對(duì)于某些與你差分版本一致,但是內(nèi)容有過修改的(比如破解版apk),這樣也是無(wú)法進(jìn)行增量升級(jí)的,為了防止合成補(bǔ)丁錯(cuò)誤,最好在補(bǔ)丁合成前對(duì)舊版本的apk進(jìn)行sha1sum校驗(yàn),保證基礎(chǔ)包的一致性。
小實(shí)驗(yàn)
多說無(wú)益,實(shí)踐才是王道。下面就來簡(jiǎn)單實(shí)踐一下,檢測(cè)之前理論的正確性。下載實(shí)驗(yàn)包?(http://download.csdn.net/detail/hmg25/4676737),解壓后文件如下
[html]?view plaincopyprint?
├──?bsdiff-4.3????????//bsdiff的源碼路徑,官網(wǎng)獲取??│???├──?bsdiff.1??│???├──?bsdiff.c??│???├──?bspatch.1??│???├──?bspatch.c??│???└──?Makefile??├──?bsdiff-4.3.tar.gz??????├──?bsdiff4.3-win32?????//windows?PC端的測(cè)試工具??│???├──?Binary?diff.txt??│???├──?bsdiff.exe??│???├──?bspatch.exe??│???└──?LICENSE??├──?bspatch?????????????//手機(jī)端的測(cè)試工具??├──?iReader1.6.2.0(v35).apk??????//?舊版本的apk??└──?iReader1.8.0.1(v40).apk????//新版本的apk?? ? ? ? ?以附帶的iReader來做測(cè)試,在shell進(jìn)入test\bsdiff4.3-win32文件夾,并下運(yùn)行命令:
[html]?view plaincopyprint?
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,流量節(jié)省了很多。
? ? ?下面先在電腦端將他們合并。
? ??
[html]?view plaincopyprint?
bspatch.exe??../iReader1.6.2.0(v35).apk???../new.apk????../ireader.patch?? ? ? ?執(zhí)行后得到名為new.apk 的合成版本應(yīng)用,我在ubuntu下進(jìn)行校驗(yàn)可以看出他們是一樣的。
? ? 下面我們?cè)谑謾C(jī)端合成看看,將根目錄下的bspatch(此為手機(jī)端運(yùn)行的)、iReader1.6.2.0(v35).apk 和ireader.patch ,通過adb push到你有權(quán)限操作的目錄,最好是在/sdcard/下,然后設(shè)置bspatch的執(zhí)行權(quán)限,重復(fù)操作上述命令,可以合成新版本的apk,稍后安裝查看驗(yàn)證版本即可,不詳述。
擴(kuò)展閱讀
使用bsdiff 進(jìn)行差分升級(jí),還并不是最優(yōu)的方式,google在它的Chromium項(xiàng)目中,對(duì)這個(gè)差分算法進(jìn)行了優(yōu)化,優(yōu)化后的版本叫做小胡瓜Courgette,據(jù)說性能優(yōu)化了很多不是一個(gè)數(shù)量級(jí)了,官方的一個(gè)例子:
Full update ? ? ??10,385,920 bsdiff update? ? ?704,512 Courgette update?? ? ?78,848
? ? ? ?大牛們可以去研究下。
? ? ? 最近有些小忙,稍后有時(shí)間會(huì)對(duì)增量升級(jí)進(jìn)行封裝下,將合成的代碼弄成一個(gè)lib庫(kù),供java調(diào)用。有興趣的童鞋可以自己操作一下~~~ By 何明桂(http://blog.csdn.net/hmg25) 轉(zhuǎn)載請(qǐng)注明出處 ? 原裝正版,盜版必究 ^_^
補(bǔ)充: 很多人不知道怎么進(jìn)行封裝為lib,其實(shí)這個(gè)和一般的android的C庫(kù)是一樣的,不明白的看看jni相關(guān)的知識(shí),原來的測(cè)試工程已經(jīng)找不到了,下邊我給出個(gè)簡(jiǎn)單的例子,拋磚引玉,給大家參考下。 ?
[java]?view plaincopyprint?
#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]);????????????if?((f?=?fopen(argv[3],?"r"))?==?NULL)??????????err(1,?"fopen(%s)",?argv[3]);??????????????????if?(fread(header,?1,?32,?f)?<?32)?{??????????if?(feof(f))??????????????errx(1,?"Corrupt?patch\n");??????????err(1,?"fread(%s)",?argv[3]);??????}????????????if?(memcmp(header,?"BSDIFF40",?8)?!=?0)??????????errx(1,?"Corrupt?patch\n");????????????bzctrllen=offtin(header+8);??????bzdatalen=offtin(header+16);??????newsize=offtin(header+24);??????if((bzctrllen<0)?||?(bzdatalen<0)?||?(newsize<0))??????????errx(1,"Corrupt?patch\n");????????????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)?{??????????????????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);??????????};????????????????????if(newpos+ctrl[0]>newsize)??????????????errx(1,"Corrupt?patch\n");????????????????????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");????????????????????for(i=0;i<ctrl[0];i++)??????????????if((oldpos+i>=0)?&&?(oldpos+i<oldsize))??????????????????new[newpos+i]+=old[oldpos+i];????????????????????newpos+=ctrl[0];??????????oldpos+=ctrl[0];????????????????????if(newpos+ctrl[1]>newsize)??????????????errx(1,"Corrupt?patch\n");????????????????????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");????????????????????newpos+=ctrl[1];??????????oldpos+=ctrl[2];??????};????????????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]);????????????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:
[java]?view plaincopyprint?
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) ? ----摘自何明桂(http://blog.csdn.net/hmg25
轉(zhuǎn)載于:https://www.cnblogs.com/MrZz/p/4448236.html
總結(jié)
以上是生活随笔為你收集整理的【Android-功能】Android应用增量更新的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。