在前幾年,整體移動網絡環境相比現在差很多,加之流量費用又相對較高,因此每當我們發布新版本的時候,一些用戶升級并不是很積極,這就造成了新版本的升級率并不高。而google為了解決了這個問題,提出了Smart App Update,即增量更新(也叫做差分升級)。
盡管現在網絡環境有了很大的提升,但一個不爭的事實就是應用越做越大,因此,增量更新在目前的仍然是一種解決APP更新包過大的有效方案。今天,我們就來聊聊增量更新。
什么是增量更新?
增量更新的關鍵在于如何理解增量一詞。來想想平時我們的開發過程,往往都是今天在昨天的基礎上修改一些代碼,app的更新也是類似的:往往都是在舊版本的app上進行修改。這樣看來,增量更新就是原有app的基礎上只更新發生變化的地方,其余保持原樣。
與原來每次更新都要下載完整apk包的做法相比,這樣做的好處顯而易見:每次變化的地方總是比較少,因此更新包的體積就會小很多。比如“師父說”安裝包的體積在6m左右,如果不采用增量更新,用戶每次更新都需要下載大約6m左右的安裝包,而采用增量更新這種方案之后每次只需要下載2m左右的更新包即可,相比原來做法大大減少了用戶下載等待的時間。
增量更新的原理
增量更新的原理非常簡單,簡單的說就是通過某種算法找出新版本和舊版本不一樣的地方(這個過程也叫做差分),然后將不一樣的地方抽取出來形成所謂的更新補?。╬atch),也稱之為差分包??蛻舳嗽跈z測到更新的時候,只需要下載差分包到本地,然后將差分包合并至本地的安裝包,形成新版本的安裝包,文件校驗通過后再執行安裝即可。本地的安裝包通過提取當前已安裝應用的apk得到。
演示:差分包的生成與合并
如下圖所示:
現在的問題在于如何生成差分包以及合并差分包。這里,我們借助開源庫bsdiff來解決以上兩個問題。首先我們先演示一下差分包的形成與合并。
下載bsdiff_win_exe.zip,解壓到本地。如下圖:
然后,我們先打出一個安裝包,假設為old.apk。對源碼做修改后,再打出一個新的安裝包new.apk。此處old.apk相當于老版本的應用,而new.apk相當于新版本的應用。接下來,我們利用bsdiff來生成差分包patch.patch。
生成差分包
將上面的old.apk和new.apk放入bsdiff解壓后的目錄,然后在控制臺中執行命令bsdiff old.apk new.apk patch.patch
,稍等一會便可以生成差分包patch.patch,如下
合并差分包
合并old.apk和patch.patch,生成新的安裝包new.apk。只要此處合并出來的new.apk和上面我們自己打出來的new.apk一樣,那么就可以認為它就是我們需要的新版本安裝包。
我們來看看如何合并。將old.apk和patch.patch放入bsdiff文件夾,合并之前為:
然后執行命令bspatch old.apk new.apk patch.patch
,稍等一會之后便可以看到合并出的new.apk.如下:
不出意外,合并而來的new.apk應該和我們自己打出來的new.apk是一模一樣的,這可以通過驗證兩者的md5來認定。
導線在為止,我們已經弄明白增量更行是怎么一回事。下面,我們就以“師父說”為對象進行實踐一把。
實踐:讓師父說支持增量更新
客戶端支持增量更新總體和上面的演示差不多,唯一的區別在于客戶端要自行編譯bspatch.c來實現合并差分包,也就是所謂的ndk開發,這里我們首先要下載bsdiff的源碼以及bszip的源碼,以便后面使用。在as中如何進行ndk開放不是本文的重點,因此不做詳細說明。
1.編寫BsPatchUtil類
BsPatchUtil中只有一個natvie方法patch(String oldApkPath,String newApkPath,String patchPath)
用于實現增量包的合并:
public class BsPatchUtil {
static {
System.loadLibrary("apkpatch");
}
public static native int patch(String oldApkPath, String newApkPath, String patchPath);
}
2.編寫C代碼
在實現BsPatchUtil之前,我們需要將bspatch.c以及bzip的相關代碼拷貝到jni目錄下(bzip只保留.h頭文件和.c文件)。并將bspatch.c中的main()方法名修改為executePatch()
,并且修改其中bzip的引入頭為#include "bzip2/bzlib.h"
.目錄結構如下:
注意:上圖當中的em.c是一個空文件,用來避免在window下編譯產生的未知錯誤。
接下來我們就可以在bspatch_util.c中實現相關的代碼了:
//
// Created by God on 2016/10/25.
//
#include "com_closedevice_fastapp_util_BsPatchUtil.h"
JNIEXPORT jint JNICALL Java_com_closedevice_fastapp_util_BsPatchUtil_patch
(JNIEnv *env, jclass clazz, jstring old, jstring new, jstring patch){
int args=4;
char *argv[args];
argv[0] = "bspatch";
argv[1] = (char*)((*env)->GetStringUTFChars(env, old, 0));
argv[2] = (char*)((*env)->GetStringUTFChars(env, new, 0));
argv[3] = (char*)((*env)->GetStringUTFChars(env, patch, 0));
//此處executePathch()就是上面我們修改出的
int result = executePatch(args, argv);
(*env)->ReleaseStringUTFChars(env, old, argv[1]);
(*env)->ReleaseStringUTFChars(env, new, argv[2]);
(*env)->ReleaseStringUTFChars(env, patch, argv[3]);
return result;
}
至此,大部分工作已經完成了。配置app moudle中的build.gradle中添加ndk配置
defaultConfig {
applicationId "com.closedevice.fastapp"
minSdkVersion 14
targetSdkVersion 24
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//ndk配置
ndk{
moduleName "apkpatch"
abiFilters "armeabi", "armeabi-v7a","x86"
}
}
接下來,我們編譯試試(ndk環境的配置這里不做說明,自行配置即可),不出意外會遇到以下錯誤:
該問題的解決方法也非常簡單,注釋掉對應文件的main()方法即可。重新編譯,不出意外沒什么問題了。接下來,我們就需要在合適的地方合并差分包了。
3.合并差分包
上面的過程做完之后,就可以通過BsPatchUtil.patch()
來合并當前安裝包和差分包了。
這里,我們假設差分包已經從服務器下載到本地了。
首先來看如何獲取當前安裝包。我們安裝的應用通常在、data/app下,可以通過一下代碼獲取其路徑:
public static String getApkInstalledSrc(){
return BaseApplication.context().getApplicationInfo().sourceDir;
}
下面就可以通過BsPatchUtil.patch(String oldApkPath,String newApkPath,String pathPath)
來進行合并了。此處需要注意兩點:
- 合并的地方建議放在外置存儲(SDcard)當中
- 合并的過程比較耗時,需要放到子線程中進行。
4.安裝
任何更新包在下載完成后首先要做的就是進行MD5校驗,以便確認該更新包是正規途徑下載而來的。同樣,對于合并之后的更新包,首先要做的事情也是進行MD5校驗,校驗通過之后,再進行安裝:
public static void installAPK(Context context, File file) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
context.startActivity(intent);
}
到現在,增量更新已經完成。現在可以把增量包以及合并之后的安裝包進行刪除了。
增量更新的缺點
增量更新雖讓有效的解決了更新包過大的問題,但是存在以下幾點問題:
- 客戶端和服務端需要加入相應的支持。每次發布新版本,服務端都需要為以前所有的老版本生成對應的差分包,并根據客戶端端請求返回對應的更新包,維護過程將會變得相對復雜。客戶端需要對差分包做更為詳細的驗證,防止出錯,除此之外,客戶端應該可以根據服務端更新開關來確定當前是使用完整更新還是增量更新。
- apk包之間的差異過小時,比如2m以下,此時生成的差分包仍然有幾百k,此時使用增量更新得不償失,畢竟形成差分包和合并的過程都非常耗時。另外,但版本之間變化非常大的時候,通常是是大版本好變化的時候,比如從v 1.0.0到2.0.0,此時使用完整更新也不錯。
在師父說中已經添加主要代碼,可自行練習。沒事點個star,fork都是極好的