現(xiàn)在的應(yīng)用市場(chǎng),很多都有省流量更新,到底是使用什么技術(shù)實(shí)現(xiàn)的呢宽档?了解一番后,原來(lái)翩活,是使用到了增量更新,而且實(shí)現(xiàn)的手段便贵,其實(shí)也挺簡(jiǎn)單的菠镇。
基本流程就是這樣的:用戶手機(jī)上已經(jīng)安裝的應(yīng)用,比如版本為1.0承璃,下載新版本2.0與1.0的增量包(也可以稱為差分包)利耍,然后1.0與增量包合并為新的2.0包,重新安裝盔粹。
結(jié)合上面的流程隘梨,基本技術(shù)點(diǎn)就是生成和合并增量包:
- 生成增量包
- 合并增量包
生成bsdiff和bspatch可執(zhí)行文件
下載
增量包的生成,目前已經(jīng)有很多開源庫(kù)了舷嗡,可以直接使用bsdiff轴猎,進(jìn)去bsdiff下載頁(yè)面,點(diǎn)擊here
就可以下載并解壓进萄∧聿保可以看到有以下文件
? bsdiff-4.3 ls
Makefile bsdiff.1 bsdiff.c bspatch.1 bspatch.c
bsdiff:比較兩個(gè)文件的二進(jìn)制數(shù)據(jù),生成差分包中鼠。一般會(huì)在你的存儲(chǔ)服務(wù)器當(dāng)中執(zhí)行郎仆,接下來(lái)以Mac系統(tǒng)環(huán)境下演示
bspatch:合并舊的文件與差分包,生成新文件兜蠕。一般在Android環(huán)境中執(zhí)行,也就是會(huì)集成到Android項(xiàng)目中抛寝。
編譯
# 執(zhí)行make命令
? bsdiff-4.3 make
Makefile:13: *** missing separator. Stop.
出現(xiàn)錯(cuò)誤提示的原因是:在makefile中熊杨,命令行要以tab鍵開頭,所以我們要對(duì)MakeFile進(jìn)行編輯
# 原來(lái)的代碼
install:
${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
# 添加tab開頭后的代碼
install:
${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
修改完成后盗舰,重新執(zhí)行make命令
? bsdiff-4.3 make
cc -O3 -lbz2 bsdiff.c -o bsdiff
cc -O3 -lbz2 bspatch.c -o bspatch
bspatch.c:39:21: error: unknown type name 'u_char'; did you mean 'char'?
static off_t offtin(u_char *buf)
^~~~~~
char
成功生成了bsdiff可執(zhí)行文件晶府,但是在生成bspathc的時(shí)候,卻出現(xiàn)錯(cuò)誤提示钻趋,原因是:找不到u_char的聲明川陆,所以需要引入#include <sys/types.h>
到bspatch.c
文件中,再次執(zhí)行make蛮位。
? bsdiff-4.3 make
cc -O3 -lbz2 bspatch.c -o bspatch
? bsdiff-4.3 ls
Makefile bsdiff bsdiff.1 bsdiff.c bspatch bspatch.1 bspatch.c
可以發(fā)現(xiàn)多出了bsdiff
和bspatch
文件
合并增量文件
生成差分包的步驟较沪,就先放在后面鳞绕,先介紹怎樣在項(xiàng)目中實(shí)現(xiàn)合并增量包,然后再編譯不同的包進(jìn)行增量更新尸曼。
創(chuàng)建項(xiàng)目
創(chuàng)建NDK項(xiàng)目们何,最簡(jiǎn)單的就是在創(chuàng)建時(shí),勾選Include C++ Support控轿,拷貝bspatch.c
到src/main/cpp
文件中冤竹,修改CMakeList.txt文件。
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp
# 新增
src/main/cpp/bspatch.c)
嘗試編譯運(yùn)行茬射,提示出錯(cuò)如下鹦蠕,找不到bzlib.h
fatal error: 'bzlib.h' file not found
#include <bzlib.h>
^~~~~~~~~
因?yàn)閎spathc依賴于bzip2(zip壓縮庫(kù)),所以需要導(dǎo)入bzip2庫(kù)文件在抛,bizp2下載地址钟病,直接解壓,然后在cpp文件下創(chuàng)建bzip文件(文件名可以隨便人ā)档悠,把其中的.c
和.h
文件拷貝進(jìn)去。
如果不想全部拷貝進(jìn)去望浩,可以分析MakeFile文件辖所,生成bzip2的庫(kù),需要下列這些文件磨德,只需要對(duì)應(yīng)找其中的.c
文件缘回,執(zhí)行編譯就通過(guò)錯(cuò)誤提示不斷導(dǎo)入需要的文件。
OBJS= blocksort.o \
huffman.o \
crctable.o \
randtable.o \
compress.o \
decompress.o \
bzlib.o
并且需要修改CMakeList.txt文件
# 新增
file(GLOB bzip_source src/main/cpp/bzip/*.c)
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp
src/main/cpp/bspatch.c
# 新增
${bzip_source})
導(dǎo)入bzlib.h
文件后典挑,編譯運(yùn)行仍然出現(xiàn)如下
fatal error: 'bzlib.h' file not found
#include <bzlib.h>
^~~~~~~~~
再修改CMakeList.txt文件酥宴,不斷添加文件直到編譯成功不報(bào)錯(cuò)
include_directories(src/main/cpp/bzip)
編寫native方法
導(dǎo)入bzlib庫(kù)成功后,新建一個(gè)native方法您觉,用于執(zhí)行合并拆分包
/**
* 合并差分包
* @param oldApk 當(dāng)前項(xiàng)目路徑
* @param newApk 差分包路徑
* @param patchFile 合并后新包路徑
*/
private native void native_bspatch(String oldApk,String newApk,String patchFile);
修改native-lib.cpp文件
#include <jni.h>
// 執(zhí)行合并差分包拙寡,實(shí)際上就是bspatch.c中的main()方法
// 由于native-lib.cpp為c++,bspatch.c為c琳水,所以需要使用extern
extern "C" {
extern int main(int argc,char * argv[]);
}
extern "C"
JNIEXPORT void JNICALL
Java_cn_guidongyuan_studypatch_MainActivity_native_1bspatch(JNIEnv *env, jobject instance,
jstring oldApk_, jstring newApk_,
jstring patchFile_) {
const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
const char *newApk = env->GetStringUTFChars(newApk_, 0);
const char *pathFile = env->GetStringUTFChars(patchFile_, 0);
char * argv[4] = {"", const_cast<char *>(oldApk), const_cast<char *>(newApk),
const_cast<char *>(pathFile)};
main(4, argv);
env->ReleaseStringUTFChars(oldApk_, oldApk);
env->ReleaseStringUTFChars(newApk_, newApk);
env->ReleaseStringUTFChars(patchFile_, pathFile);
}
在MainActivity中肆糕,點(diǎn)擊按鈕,執(zhí)行更新
/**
* 模擬下載在孝,合并應(yīng)用包诚啃,跳轉(zhuǎn)安裝
*/
public void downLoadApkPatch(View view) {
new AsyncTask<Void,Void,File>() {
@Override
protected File doInBackground(Void... voids) {
// 此處模擬用戶去服務(wù)器下載增量包,實(shí)際項(xiàng)目中私沮,一般會(huì)通過(guò)發(fā)送當(dāng)前用戶的版本號(hào)向服務(wù)器請(qǐng)求差分包始赎,并且服務(wù)器會(huì)返回合成新包后的md5值作為驗(yàn)證,此處就省略
// 獲取當(dāng)前應(yīng)用的路徑
String oldPath = getApplication().getApplicationInfo().sourceDir;
String newPath = Environment.getExternalStorageDirectory().getPath() + File.separator + "new.apk";
String patchPath = Environment.getExternalStorageDirectory().getPath() + File.separator + "patch.diff";
if (new File(patchPath).exists()) {
native_bspatch(oldPath,newPath,patchPath);
return new File(newPath);
}else {
Log.w(TAG, "patch file is not exists");
return null;
}
}
@Override
protected void onPostExecute(File file) {
if (file != null) {
installNewApk(file);
}
}
}.execute();
}
差分包下載后就需要執(zhí)行安裝,不同版本的執(zhí)行流程不一樣需要額外處理
/**
* 跳轉(zhuǎn)到安裝頁(yè)面
* @param file 新包路徑
*/
private void installNewApk(File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}else {
// 聲明需要的臨時(shí)權(quán)限
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String packageName = getApplication().getPackageName();
Uri contentUri = FileProvider.getUriForFile(MainActivity.this, packageName + ".fileProvider", file);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
}
startActivity(intent);
}
另外造垛,還要需要修改AndroidManifest.xml魔招,開啟必要的權(quán)限
// 讀寫SDCard權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
// 7.0 執(zhí)行安裝聲明
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="cn.guidongyuan.studypatch.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
創(chuàng)建xml目錄下的文件file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<resource>
<paths>
<external-path name="bspatch" path="" />
</paths>
</resource>
生成增量包
編譯以上項(xiàng)目,生成old.apk筋搏,修改版本號(hào)為2.0仆百,再次編譯生成new.apk。都拷貝到與bsdiff同一個(gè)目錄下奔脐,執(zhí)行
# ./bsdiff 舊版本apk 新版本apk 差分包(文件名和格式可以隨便取)
? bsdiff-4.3 ./bsdiff old.apk new.apk patch.diff
拷貝patch.diff到手機(jī)根目錄中俄周,執(zhí)行舊項(xiàng)目代碼,就可以看到跳轉(zhuǎn)到應(yīng)用安裝界面
完整項(xiàng)目
完整項(xiàng)目demo代碼髓迎,可以自行到github看
錯(cuò)誤
-
編譯的時(shí)候峦朗,出現(xiàn)錯(cuò)誤提示如下,應(yīng)該是提示鏈接庫(kù)失敗
Build command failed. Error while executing process ‘...省略忘記先復(fù)制完整錯(cuò)誤信息了....bzip2.c.o', missing and no known rule to make it
修改方法:Build -> Refresh Linked C++ Projects
參考資料
-
鴻洋的文章
-
介紹通過(guò)DownloadManager下載文件排龄,還介紹了root模式下自動(dòng)安裝