今天學(xué)習(xí)了一下增量更新,這個技術(shù)已經(jīng)出現(xiàn)很長時間了,但是現(xiàn)實中,估計只有大廠才利用了這一技術(shù)在做產(chǎn)品!
國內(nèi)有些第三方服務(wù)平臺,像友盟提供自動更新的服務(wù),也是用的增量方式!
其他的像QQ,Sina微博,陌陌,蘑菇街等都用到了,解壓一下它們的apk,看一下lib目錄:
QQ:libbspatch.so
微博:libbsdiffjni.so
陌陌:libbsdiff.so
蘑菇街:libpatcher.so
這些只不過so的名字不一樣而已,但都用到了增量更新.
其它的一些主流app解包后,都看到了libandfix.so這個庫,這個是阿里推出的一個支持ART和Dalvik的熱修復(fù)的框架,在線修復(fù)bug.
今天學(xué)習(xí)增量更新而非熱更新
主角:http://www.daemonology.net/bsdiff
官方說明:
(1)bsdiff 和 bspatch是編譯,安裝補丁到二進(jìn)制文件的一個工具,
(2)這個工具用到了bzip2的壓縮功能,所以補丁的大小小于新舊版本的一個差值
(3)bsdiff非常吃內(nèi)存
1.編譯生成工具
開干,我用的是ubuntu,點擊here下源代碼,編譯
解壓,可以看到一共5個文件
shone@Dell:~/Soft/bsdiff-4.3$ ls
bsdiff.1 bsdiff.c bspatch.1 bspatch.c Makefile
然后編譯make,報錯
Makefile:13: *** missing separator. Stop.
查看一下Makefile
無奈,在網(wǎng)上找了一下,找到了解決方法.
http://kinggoo.com/bsdiffupdate.htm#respond
原因是:目標(biāo)體下一行的遣蚀,命令要用TAB鍵開頭莹妒,且不能隔一行卡啰。
也就是說在.ifndef的前面要有TAB開頭才可以~因為他是安裝的下一個子集命令。
接著編譯,結(jié)果報錯,找不到頭文件
bsdiff.c:33:19: fatal error: bzlib.h: No such file or directory
#include <bzlib.h>
^
compilation terminated.
先前說過bsdiff依賴bzip2,有圖
好,下載bzip2,解壓
http://www.bzip.org/downloads.html
make
sudo make install
安裝的時候,會創(chuàng)建一些文件,所以給權(quán)限
然后編譯
cc -O3 -lbz2 bsdiff.c -o bsdiff
/tmp/cctTxPKV.o: In function `main':
bsdiff.c:(.text.startup+0x2aa): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0x9e9): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xb2c): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xc7b): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xccf): undefined reference to `BZ2_bzWriteClose'
bsdiff.c:(.text.startup+0xd22): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0xd4d): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xd73): undefined reference to `BZ2_bzWriteClose'
bsdiff.c:(.text.startup+0xdc6): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0xdf1): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xe17): undefined reference to `BZ2_bzWriteClose'
collect2: error: ld returned 1 exit status
make: *** [bsdiff] Error 1
第一個反應(yīng)還是,連接bzip2庫除了問題,其實不用makefile腳本也可以得到2個二進(jìn)制工具
CFLAGS += -O3 -lbz2
PREFIX ?= /usr/local
INSTALL_PROGRAM ?= ${INSTALL} -c -s -m 555
INSTALL_MAN ?= ${INSTALL} -c -m 444
all: bsdiff bspatch
bsdiff: bsdiff.c
bspatch: bspatch.c
install:
${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
仔細(xì)看下這個Makefile, bsdiff和bspatch分別是2個模塊,互不依賴,用make僅僅是多了可以指定目標(biāo)到/usr/local目錄,然后提供了一鍵安裝功能,就是部署二進(jìn)制工具和幫助(man)命令到/usr/local, Makefile僅僅是大項目管理的利器,還好這個文件不多,可以2次手動編譯就完成了!
gcc bsdiff.c -lbz2 -o bsdiff
好可以生成,bzip2庫之前已經(jīng)安裝在系統(tǒng)標(biāo)準(zhǔn)目錄
gcc bspatch.c -lbz2 -o bspatch
2個工具都可以正常生成!
linux下鏈接庫通常會去3個文件下找
/lib是內(nèi)核級的,/usr/lib是系統(tǒng)級的,/usr/local/lib是用戶級的
想了下,Makefile估計還是有點問題,然后自己改了下
CC=gcc
LDFLAGS=
CFLAGS=-Wall -O3 -g -lbz2
PREFIX ?= /usr/local
INSTALL_PROGRAM ?= ${INSTALL} -c -s -m 555
INSTALL_MAN ?= ${INSTALL} -c -m 444
all: bsdiff bspatch
bsdiff: bsdiff.c
$(CC) bsdiff.c $(CFLAGS) $(LDFLAGS) -o bsdiff
bspatch: bspatch.c
$(CC) bspatch.c $(CFLAGS) $(LDFLAGS) -o bspatch
install:
${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
這樣make就可以成功生成2個工具了
Makefile的規(guī)則
目標(biāo) : 需要的條件 (注意冒號兩邊有空格)
命令 〉「住(注意前面用tab鍵開頭)
解釋一下:
1 目標(biāo)可以是一個或多個,可以是Object File求厕,也可以是執(zhí)行文件年柠,甚至可以是一個標(biāo)簽。
2 需要的條件就是生成目標(biāo)所需要的文件或目標(biāo)
3 命令就是生成目標(biāo)所需要執(zhí)行的腳本
2.生成補丁文件
差分生成補丁方式
bsdiff old_version.apk new_version.apk diff.patch
分別是3個參數(shù),老版本,新版本,補丁
還沒有apk,首要任務(wù)先生成2個版本的apk
這里要把補丁合成功能移植到手機客戶端上,那么就要用到NDK了
bsdiff是二進(jìn)制差分工具,其對應(yīng)的bspatch是相應(yīng)的補丁合成工具
鏡頭切換到androidstudio上
2.1 先定義一個調(diào)度native層的類
public class Updater {
public static native void applyPatch(String oldPath, String newPath, String patchPath);
}
2.2 生成.h頭文件
因為as的版本原因,生成.h的方式也不一樣,我用的androidstudio版本是2.1.1v,網(wǎng)上方法大多不可行,正確的姿勢是
shone@Dell:~/Public/work_androidstudio/PatchUpdate/app/build/intermediates/classes/debug$ javah com.sugar.patch.Updater
主要是先編譯一下模塊,然后進(jìn)入到debug目錄,用javah命令
好了,如果沒有問題,會看見一個.h文件
2.3 編寫補丁合成代碼
<1>首先建立一個jni的文件夾,將.h頭文件移動過來
<2>因為補丁合成用到bzip2解壓,所以要把zip2的包拷貝過來
<3>然后將bspatch.c拷貝到j(luò)ni下面
bspatch.c實際上只有2個函數(shù),一個offtin和main(),增加一個native函數(shù)
JNIEXPORT void JNICALL Java_com_sugar_patch_Updater_applyPatch
(JNIEnv *env, jclass clazz, jstring old_path, jstring new_path, jstring patch){
int argc=4;
char * argv[argc];
argv[0]="bspatch";
argv[1]=(*env)->GetStringUTFChars(env, old_path, 0);
argv[2]=(*env)->GetStringUTFChars(env, new_path, 0);
argv[3]=(*env)->GetStringUTFChars(env, patch, 0);
int ret = domain(argc, argv);
(*env)->ReleaseStringUTFChars(env,old_path, argv[1]);
(*env)->ReleaseStringUTFChars(env,new_path, argv[2]);
(*env)->ReleaseStringUTFChars(env,patch, argv[3]);
return ret;
}
懂jni的都知道,這個函數(shù)名是不能亂寫的,必須把.h頭文件的那個聲明函數(shù)拷貝過來,然后參數(shù)修改和增加主體,java調(diào)native會傳值到這個函數(shù)里來!
然后這里會調(diào)用main()方法,這里我把main方法名字改成了domain,因為main是C程序的入口,這里不需要這個入口
下面是我自己的測試案例:
native代碼寫好了,那么java需要傳入3個參數(shù)的值
java核心代碼
String oldVersionPath = AppUtils.getOldVersionPath(context.get());
Updater.applyPatch(oldVersionPath, newVersionPath, downPatchPath);
這里需要三個路徑,定義常量
public interface Contants {
String rootDir = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "PatchCache" + File.separator;
String downPatchPath = rootDir + "apk.patch";
String newVersionPath = rootDir + "PatchUpdate_v_2_0.apk";
}
oldVersionPath為app在手機上的安裝包路徑,從ApplicationInfo這個類獲得,newVersionPath為新版本apk包合成地址,downPatchPath為補丁地址
為了測試,這里把補丁文件apk.patch會先放到sd卡/PatchCache/這個目錄下,等app合成完成后,新的apk會輸出到sd卡/PatchCache/下
然后有了合成后的,就可以安裝更新了
AppUtils.install(context.get(), newVersionPath);
服務(wù)端差分補丁
shone@Dell:~/Soft/bsdiff-4.3$ ls
app_v_1_0.apk bsdiff bsdiff.c bspatch.1 Makefile
app_v_2_0.apk bsdiff.1 bspatch bspatch.c
app_v_1_0.apk
和app_v_2_0.apk
分別是1.0和2.0版本,測試的話
我下了幾張高清圖片放到了2.0版本的assets下面,這樣2.0版本的apk大小會比較大,1.0版本大小是1.7M,2.0版本是16.9M,差分后apk.patch有14M!
shone@Dell:~/Soft/bsdiff-4.3$ ./bsdiff app_v_1_0.apk app_v_2_0.apk apk.patch
shone@Dell:~/Soft/bsdiff-4.3$ ls
apk.patch app_v_1_0.apk app_v_2_0.apk bsdiff bsdiff.1 bsdiff.c bspatch bspatch.1 bspatch.c Makefile
好了,分析到這了
下面是我的測試項目,可以自己測測
https://github.com/shonegg/PatchUpdate