很久沒有更新博客了,真是墮落啊,幾次想提起筆,卻總是被各種瑣事耽擱,以后會多寫文章記錄點滴。
背景
隨著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系統差分包的制作,使用命令:
[html]?view plaincopyprint?
./build/tools/releasetools/ota_from_target_files?-n?-i?<舊包>?<新包>?<差分包名>?? 在查閱ota_from_target_files 的代碼可知,是在函數WriteIncrementalOTAPackage里生成差分包的,在這個函數里邊創建了common.Difference這個類,我們繼續跟進,在common.py中的類 ? class Difference(object):里可以看到: ? ?
[html]?view plaincopyprint?
diff_program?=?DIFF_PROGRAM_BY_EXT.get(ext,?"bsdiff")?? ? ? ? ?至此我們就看到了android中提供我們用來制作差分增量升級包的工具,"bsdiff",這是一個很牛X開源的二進制差分工具.相關的介紹傳送門 相關的代碼地址?或者在android的代碼目錄下 \external\bsdiff bsdiff是二進制差分工具,其對應的bspatch是相應的補丁合成工具 需要注意的是增量升級的補丁包,是需要在服務器端,即PC端完成,大致流程如,制作補丁時調用bsdiff函數,根據兩個不同版本的二進制文件,生成補丁文件。?
[html]?view plaincopyprint?
命令: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庫,供手機端調用。
[html]?view plaincopyprint?
bspatch的命令格式為:??bspatch?oldfile?newfile?patchfile?? 和差分時的參數一樣。合成新的apk便可以用于安裝。 以上只是簡單的操作原理,增量升級還涉及很多其他方面,例如,升級補丁校驗等問題,可以參考android源碼中bootable\recovery\applypatch的相關操作,本文只是淺析,在此不表。
不足
增量升級并非完美無缺的升級方式,至少存在以下兩點不足: 1.增量升級是以兩個應用版本之間的差異來生成補丁的,你無法保證用戶每次的及時升級到最新,所以你必須對你所發布的每一個版本都和最新的版本作差分,以便使所有版本的用戶都可以差分升級,這樣操作相對于原來的整包升級較為繁瑣,不過可以通過自動化的腳本批量生成。 2.增量升級成功的前提是,用戶手機端必須有能夠讓你拷貝出來且與你服務器用于差分的版本一致的apk,這樣就存在,例如,系統內置的apk無法獲取到,無法進行增量升級;對于某些與你差分版本一致,但是內容有過修改的(比如破解版apk),這樣也是無法進行增量升級的,為了防止合成補丁錯誤,最好在補丁合成前對舊版本的apk進行sha1sum校驗,保證基礎包的一致性。
小實驗
多說無益,實踐才是王道。下面就來簡單實踐一下,檢測之前理論的正確性。下載實驗包?(http://download.csdn.net/detail/hmg25/4676737),解壓后文件如下
[html]?view plaincopyprint?
├──?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文件夾,并下運行命令:
[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,流量節省了很多。
? ? ?下面先在電腦端將他們合并。
? ??
[html]?view plaincopyprint?
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,920 bsdiff update? ? ?704,512 Courgette update?? ? ?78,848
? ? ? ?大牛們可以去研究下。
? ? ? 最近有些小忙,稍后有時間會對增量升級進行封裝下,將合成的代碼弄成一個lib庫,供java調用。有興趣的童鞋可以自己操作一下~~~ By 何明桂(http://blog.csdn.net/hmg25) 轉載請注明出處 ? 原裝正版,盜版必究 ^_^
補充: 很多人不知道怎么進行封裝為lib,其實這個和一般的android的C庫是一樣的,不明白的看看jni相關的知識,原來的測試工程已經找不到了,下邊我給出個簡單的例子,拋磚引玉,給大家參考下。 ?
[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
轉載于:https://www.cnblogs.com/MrZz/p/4448236.html
總結
以上是生活随笔為你收集整理的【Android-功能】Android应用增量更新的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。