編輯:關於Android編程
今天學習了一下增量更新,這個技術已經出現很長時間了,但是現實中,估計只有大廠才利用了這一技術在做產品!
國內有些第三方服務平台,像友盟提供自動更新的服務,也是用的增量方式!
其他的像QQ,Sina微博,陌陌,蘑菇街等都用到了,解壓一下它們的apk,看一下lib目錄:
QQ:libbspatch.so
微博:libbsdiffjni.so
陌陌:libbsdiff.so
蘑菇街:libpatcher.so
這些只不過so的名字不一樣而已,但都用到了增量更新.
其它的一些主流app解包後,都看到了libandfix.so這個庫,這個是阿裡推出的一個支持ART和Dalvik的熱修復的框架,在線修復bug.
今天學習增量更新而非熱更新
主角:http://www.daemonology.net/bsdiff
官方說明:
(1)bsdiff 和 bspatch是編譯,安裝補丁到二進制文件的一個工具,
(2)這個工具用到了bzip2的壓縮功能,所以補丁的大小小於新舊版本的一個差值
(3)bsdiff非常吃內存
開干,我用的是ubuntu,點擊here下源代碼,編譯
解壓,可以看到一共5個文件<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
shone@Dell:~/Soft/bsdiff-4.3$ ls
bsdiff.1 bsdiff.c bspatch.1 bspatch.c Makefile
然後編譯make,報錯
Makefile:13: *** missing separator. Stop.
查看一下Makefile
無奈,在網上找了一下,找到了解決方法.
http://kinggoo.com/bsdiffupdate.htm
原因是:目標體下一行的,命令要用TAB鍵開頭,且不能隔一行。
也就是說在.ifndef的前面要有TAB開頭才可以~因為他是安裝的下一個子集命令。
接著編譯,結果報錯,找不到頭文件
bsdiff.c:33:19: fatal error: bzlib.h: No such file or directory #include^ compilation terminated.
先前說過bsdiff依賴bzip2,有圖
好,下載bzip2,解壓
http://www.bzip.org/downloads.html
make
sudo make install
安裝的時候,會創建一些文件,所以給權限
然後編譯
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
第一個反應還是,連接bzip2庫除了問題,其實不用makefile腳本也可以得到2個二進制工具
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
仔細看下這個Makefile, bsdiff和bspatch分別是2個模塊,互不依賴,用make僅僅是多了可以指定目標到/usr/local目錄,然後提供了一鍵安裝功能,就是部署二進制工具和幫助(man)命令到/usr/local, Makefile僅僅是大項目管理的利器,還好這個文件不多,可以2次手動編譯就完成了!
gcc bsdiff.c -lbz2 -o bsdiff
好可以生成,bzip2庫之前已經安裝在系統標准目錄
gcc bspatch.c -lbz2 -o bspatch
2個工具都可以正常生成!
linux下鏈接庫通常會去3個文件下找
/lib是內核級的,/usr/lib是系統級的,/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的規則 目標 : 需要的條件 (注意冒號兩邊有空格) 命令 (注意前面用tab鍵開頭) 解釋一下: 1 目標可以是一個或多個,可以是Object File,也可以是執行文件,甚至可以是一個標簽。 2 需要的條件就是生成目標所需要的文件或目標 3 命令就是生成目標所需要執行的腳本
差分生成補丁方式
bsdiff old_version.apk new_version.apk diff.patch
分別是3個參數,老版本,新版本,補丁
還沒有apk,首要任務先生成2個版本的apk
這裡要把補丁合成功能移植到手機客戶端上,那麼就要用到NDK了
bsdiff是二進制差分工具,其對應的bspatch是相應的補丁合成工具
鏡頭切換到androidstudio上
2.1 先定義一個調度native層的類
public class Updater {
public static native void applyPatch(String oldPath, String newPath, String patchPath);
}
2.2 生成.h頭文件
因為as的版本原因,生成.h的方式也不一樣,我用的androidstudio版本是2.1.1v,網上方法大多不可行,正確的姿勢是
shone@Dell:~/Public/work_androidstudio/PatchUpdate/app/build/intermediates/classes/debug$ javah com.sugar.patch.Updater
主要是先編譯一下模塊,然後進入到debug目錄,用javah命令
好了,如果沒有問題,會看見一個.h文件
2.3 編寫補丁合成代碼
<1>首先建立一個jni的文件夾,將.h頭文件移動過來
<2>因為補丁合成用到bzip2解壓,所以要把zip2的包拷貝過來
<3>然後將bspatch.c拷貝到jni下面
bspatch.c實際上只有2個函數,一個offtin和main(),增加一個native函數
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的都知道,這個函數名是不能亂寫的,必須把.h頭文件的那個聲明函數拷貝過來,然後參數修改和增加主體,java調native會傳值到這個函數裡來!
然後這裡會調用main()方法,這裡我把main方法名字改成了domain,因為main是C程序的入口,這裡不需要這個入口
下面是我自己的測試案例:
native代碼寫好了,那麼java需要傳入3個參數的值
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);
服務端差分補丁
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
上一篇我們說到的逐幀動畫和補間動畫,這篇我們著重說下屬性動畫:先看下面兩幅動畫: 上面兩幅動畫就是通過屬性動畫做出來的,是不是比較炫呢!不僅有顯示,也有交互點擊事件。下面
在Android3.0上開始引入了一個新概念叫Fragment。它有自己的布局文件,可以作為組件排布,也可以相互組合去實現不同的布局顯示。使用Fragment可以重復利用
最近在研究android自定義控件屬性,學到了TypedArray以及attrs。大家也可以結合《理解Android中的自定義屬性》這篇文章進行學習,後續一篇還有應用。1
本文實例講述了Android中自定義一個View的方法。分享給大家供大家參考,具體如下:Android中自定義View的實現比較簡單,無非就是繼承父類,然後重載方法,即便