前言:
增量更新已經(jīng)出來幾年了,而一些大的公司早就實現(xiàn)了增量更新。增量更新相較于全量更新的好處不言而喻,利用差分算法獲得1.0版本到2.0版本的差分包,這樣在安裝了1.0的設備上只要下載這個差分包就能夠完成由1.0-2.0的更新。
存在一個1.0版本的apk:

存在一個2.0版本的apk:

這樣如果進行全量更新則需要下載完整的25.9大小的apk文件,進行安裝。而如果使用增量更新則只需要下載如下 25.3的差分包。

下載數(shù)據(jù)減少了0.6M(這里只是為了做了功能,patch包和最新的安裝包大小差不多的情況:建議做全量更新)。這樣做的好處不僅僅在于對于流量的節(jié)省。對于用戶來說現(xiàn)在流量可能并不值錢,或者使用wifi再進行更新,但是從下載時間能夠得到一個良好的優(yōu)化,同時也減小了服務器的壓力。
先來看效果圖:


Step1:實現(xiàn)增量更新流程:

Step2:實現(xiàn)bsdiff、bspatch源文件的編譯
需要實現(xiàn)增量更新,現(xiàn)在有各種開源的制作與合并差分包的開源庫,比如:bsdiff、hdiff等等。因此我們只需要獲得源碼來使用即可。
bsdiff 下載地址: http://www.daemonology.net/bsdiff/
bsdiff 依賴bzip2(zip壓縮庫)
https://nchc.dl.sourceforge.net/project/gnuwin32/bzip2/1.0.5/bzip2-1.0.5-src.zip
下載完成后解壓:

其中:
Makefile(編譯bsdiff、bapatch)
bsdiff: 比較兩個文件的二進制數(shù)據(jù),生成差分包
bspatch: 合并舊的文件與差分包,生成新文件
很顯然,bspatch我們需要在Android環(huán)境下來執(zhí)行,而bsdiff 一般會在你的存儲服務器當中執(zhí)行即電腦環(huán)境下執(zhí)行(win或linux)
編譯:
對于windows,可以直接從 https://github.com/cnSchwarzer/bsdiff-win/releases 下載。
而Linux/Mac則可以自行編譯:
在Linux中的解壓目錄直接執(zhí)行:make 會產(chǎn)生錯誤。需要修改:
install:
${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
#上面這段makefile片段顯然有問題(lsn9資料,指令必須以tab開頭)
#因此需要修改為:
install:
${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
#也就是在 `.if` 和 `.endif` 前加一個 tab
修改后再來執(zhí)行make 如果出現(xiàn)找不到bzip2 no file found bzlib.h之類的錯誤,則需要先安裝bzip2:
Ubuntu:
apt install libbz2-dev
Centos:
yum -y install bzip2-devel.x86_64
Mac:
brew install bzip2
如果已經(jīng)安裝bzip2,執(zhí)行make還是出現(xiàn):
bsdiff.c:(.text.startup+0x2aa): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0xcfa): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xe37): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xf80): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xfe1): undefined reference to `BZ2_bzWriteClose'
bsdiff.c:(.text.startup+0x1034): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0x105c): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0x1082): undefined reference to `BZ2_bzWriteClose'
bsdiff.c:(.text.startup+0x10d5): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0x1100): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0x1126): undefined reference to `BZ2_bzWriteClose'
則修改Makefile為:
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
cc bsdiff.c ${CFLAGS} -o bsdiff #增加
bspatch: bspatch.c
cc bspatch.c ${CFLAGS} -o bspatch #增加
install:
${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
cc bsdiff.c ${CFLAGS} -o bsdiff #增加
cc bspatch.c ${CFLAGS} -o bspatch #增加
猜測可能是編譯器的原因。
Step3:AS工程中去配置
1、創(chuàng)建一個AS(NDK)工程
2、在app目錄下的grade文件中增加編譯架構(gòu)類型
externalNativeBuild {
cmake {
cppFlags ""
abiFilters 'armeabi-v7a'//加入
}
}
3、將下載好的bsdiff 解壓之后,將bspatch.c復制進cpp目錄下。同時也將bspatch.c依賴的bzip2庫的源文件和頭文件復制進cpp目錄下。

4、配置CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
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}
)
include_directories(src/main/cpp/bzip)
find_library(
log-lib
log )
target_link_libraries(
native-lib
${log-lib} )
5、配置好之后,寫一個native方法
/**
*
* @param oldApk 當前運行的APK
* @param patch 差分包
* @param output 合成后新的APK輸出到
*/
native void bspatch(String oldApk,String patch,String output);
extern "C" {
//bspatch.c中在執(zhí)行合成方法就是其中main(int argc,char * argv[])
//所以在這里需要引入該方法
extern int main(int argc,char * argv[]);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_ly_chen_incrementalupdates_MainActivity_bspatch(JNIEnv *env, jobject instance,
jstring oldApk_, jstring patch_,
jstring output_) {
const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
const char *patch = env->GetStringUTFChars(patch_, 0);
const char *output = env->GetStringUTFChars(output_, 0);
char * argv[4] = {"", const_cast<char *>(oldApk), const_cast<char *>(output),
const_cast<char *>(patch)};
main(4,argv);
env->ReleaseStringUTFChars(oldApk_, oldApk);
env->ReleaseStringUTFChars(patch_, patch);
env->ReleaseStringUTFChars(output_, output);
}
6、接下我們做合成了安裝包了,不過的注意這個操作是一個耗時操作
//1、合成 apk
//先從服務器下載到差分包
new AsyncTask<Void,Void,File>(){
@Override
protected File doInBackground(Void... voids) {
//拿到已經(jīng)安裝版本路徑
String old = getApplication().getApplicationInfo().sourceDir;
//合成新的安裝包(備注:記得路徑 等會放置 patch包時候需要)
bspatch(old,"/sdcard/patch","/sdcard/new.apk");
//返回新的APK文件
return new File("/sdcard/new.apk");
}
@Override
protected void onPostExecute(File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
//兼容7.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
} else {
// 聲明需要的臨時權限
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 第二個參數(shù),即第一步中配置的authorities
String packageName = getApplication().getPackageName();
Uri contentUri = FileProvider.getUriForFile(MainActivity.this, packageName + ".fileprovider", file);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}.execute();
7、兼容7.0和8.0
兼容7.0:在AndroidManifest.xml配置
<!--四大組件之一 內(nèi)容提供者 可以跨進程使用,7.0安裝必須使用它-->
<provider
android:authorities="com.ly.chen.incrementalupdates.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
新建一個xml文件

暴露手機SD卡的路徑
<?xml version="1.0" encoding="utf-8"?>
<resource>
<paths>
<external-path name="haah" path="" />
</paths>
</resource>
適配8.0:加入未知來源權限。備注:只是做Demo演示,沒有動態(tài)申請權限,需手動開啟相關權限
<!--存儲-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--位未知來源權限,適配8.0-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
分別打包 V1.0.0版本 V2.0.0版本
Step3:生成差分包 這里我直接用的是Windows編譯的bsdiff.exe生成的差分包(對于windows,可以直接從 https://github.com/cnSchwarzer/bsdiff-win/releases 下載。)

上圖:
其中app1.apk:V1.0.0
其中app2.apk:V2.0.0
其中patch:差分包
下載好windows 版 解壓出現(xiàn):
bsdiff.exe:用于生產(chǎn)差分包
bspatch.exe:用于合成差分包玉低版本APK
在當前文件夾下輸入:cmd
bsdiff.exe app1.apk app2.apk patch
生成patch
將patch包放置進 手機SD卡
adb push patch /sdcard/
安裝低版本APK
adb install app1.apk
好了,安裝完成之后(注意手動開啟相關權限,不然會閃退),點擊更新按鈕,就開始更新了。。。不出意外的話,你會看到版本已經(jīng)變了。
github:https://github.com/MrRightChen/IncrementalUpdates