騰訊面試:增量升級為什么減少升級代價,增量升級原理

在前幾年,整體移動網(wǎng)絡(luò)環(huán)境相比現(xiàn)在差很多,加之流量費(fèi)用又相對較高,因此每當(dāng)我們發(fā)布新版本的時候,一些用戶升級并不是很積極,這就造成了新版本的升級率并不高。而google為了解決了這個問題,提出了Smart App Update,即增量更新(也叫做差分升級)。
盡管現(xiàn)在網(wǎng)絡(luò)環(huán)境有了很大的提升,但一個不爭的事實就是應(yīng)用越做越大,因此,增量更新在目前的仍然是一種解決APP更新包過大的有效方案。今天,我們就來聊聊增量更新。

1.什么是增量更新?

增量更新的關(guān)鍵在于如何理解增量一詞。來想想平時我們的開發(fā)過程,往往都是今天在昨天的基礎(chǔ)上修改一些代碼,app的更新也是類似的:往往都是在舊版本的app上進(jìn)行修改。這樣看來,增量更新就是原有app的基礎(chǔ)上只更新發(fā)生變化的地方,其余保持原樣。

與原來每次更新都要下載完整apk包的做法相比,這樣做的好處顯而易見:每次變化的地方總是比較少,因此更新包的體積就會小很多。比如“師父說”安裝包的體積在6m左右,如果不采用增量更新,用戶每次更新都需要下載大約6m左右的安裝包,而采用增量更新這種方案之后每次只需要下載2m左右的更新包即可,相比原來做法大大減少了用戶下載等待的時間。

2.增量升級優(yōu)勢在哪里?

差分的優(yōu)勢

  • 大小非常小
  • 安全-必須是特定的節(jié)點(diǎn)才能進(jìn)行升級
  • 相對于整包來說更容易控制

3.為什么會減少升級代價

例如舊版本的APK有5M,新版的有8M,更新的部分則可能只有3M左右(這里需要說明的是,得到的差分包大小并不是簡單的相減,因為其實需要包含一些上下文相關(guān)的東西),使用差分升級的好處顯而易見,
那么你不需要下載完整的8M文件,只需要下載更新部分就可以,而更新部分可能只有3、4M,可以很大程度上減少流量的損失。

4.增量更新的原理

增量更新的原理非常簡單,簡單的說就是通過某種算法找出新版本和舊版本不一樣的地方(這個過程也叫做差分),然后將不一樣的地方抽取出來形成所謂的更新補(bǔ)?。╬atch),也稱之為差分包??蛻舳嗽跈z測到更新的時候,只需要下載差分包到本地,然后將差分包合并至本地的安裝包,形成新版本的安裝包,文件校驗通過后再執(zhí)行安裝即可。本地的安裝包通過提取當(dāng)前已安裝應(yīng)用的apk得到。

演示:差分包的生成與合并
如下圖所示:

image

現(xiàn)在的問題在于如何生成差分包以及合并差分包。這里,我們借助開源庫bsdiff來解決以上兩個問題。首先我們先演示一下差分包的形成與合并。

下載bsdiff_win_exe.zip,解壓到本地。如下圖:

image

然后,我們先打出一個安裝包,假設(shè)為old.apk。對源碼做修改后,再打出一個新的安裝包new.apk。此處old.apk相當(dāng)于老版本的應(yīng)用,而new.apk相當(dāng)于新版本的應(yīng)用。接下來,我們利用bsdiff來生成差分包patch.patch。

5.生成差分包

將上面的old.apk和new.apk放入bsdiff解壓后的目錄,然后在控制臺中執(zhí)行命令bsdiff old.apk new.apk patch.patch,稍等一會便可以生成差分包patch.patch,如下
image

6.合并差分包

合并old.apk和patch.patch,生成新的安裝包new.apk。只要此處合并出來的new.apk和上面我們自己打出來的new.apk一樣,那么就可以認(rèn)為它就是我們需要的新版本安裝包。

我們來看看如何合并。將old.apk和patch.patch放入bsdiff文件夾,合并之前為

image

然后執(zhí)行命令bspatch old.apk new.apk patch.patch,稍等一會之后便可以看到合并出的new.apk.如下:

image

不出意外,合并而來的new.apk應(yīng)該和我們自己打出來的new.apk是一模一樣的,這可以通過驗證兩者的md5來認(rèn)定。
我們已經(jīng)弄明白增量更行是怎么一回事。下面,我們就以“師父說”為對象進(jìn)實踐一把。

7.實踐:讓師父說支持增量更新

客戶端支持增量更新總體和上面的演示差不多,唯一的區(qū)別在于客戶端要自行編譯bspatch.c來實現(xiàn)合并差分包,也就是所謂的ndk開發(fā),這里我們首先要下載bsdiff的源碼以及bszip的源碼,以便后面使用。在as中如何進(jìn)行ndk開發(fā)不是本文的重點(diǎn)。

7.1.編寫B(tài)sPatchUtil類 BsPatchUtil中只有一個natvie方法patch(String oldApkPath,String newApkPath,String patchPath)用于實現(xiàn)增量包的合并:

public class BsPatchUtil {
    static {
        System.loadLibrary("apkpatch");
}

    
public static  native int patch(String oldApkPath, String newApkPath, String patchPath);

7.2.編寫C代碼

在實現(xiàn)BsPatchUtil之前,我們需要將bspatch.c以及bzip的相關(guān)代碼拷貝到j(luò)ni目錄下(bzip只保留.h頭文件和.c文件)。并將bspatch.c中的main()方法名修改為executePatch(),并且修改其中bzip的引入頭為#include "bzip2/bzlib.h".目錄結(jié)構(gòu)如下:

image

注意:上圖當(dāng)中的em.c是一個空文件,用來避免在window下編譯產(chǎn)生的未知錯誤。

接下來我們就可以在bspatch_util.c中實現(xiàn)相關(guān)的代碼了:


#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;
}

至此,大部分工作已經(jīng)完成了。配置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環(huán)境的配置這里不做說明,自行配置即可),不出意外會遇到以下錯誤:

image

該問題的解決方法也非常簡單,注釋掉對應(yīng)文件的main()方法即可。重新編譯,不出意外沒什么問題了。接下來,我們就需要在合適的地方合并差分包了。

7.3.合并差分包

上面的過程做完之后,就可以通過BsPatchUtil.patch()來合并當(dāng)前安裝包和差分包了。

這里,我們假設(shè)差分包已經(jīng)從服務(wù)器下載到本地了。

首先來看如何獲取當(dāng)前安裝包。我們安裝的應(yīng)用通常在、data/app下,可以通過一下代碼獲取其路徑:

public static String getApkInstalledSrc(){
        return BaseApplication.context().getApplicationInfo().sourceDir;
    }

下面就可以通過BsPatchUtil.patch(String oldApkPath,String newApkPath,String pathPath)來進(jìn)行合并了。此處需要注意兩點(diǎn):

  1. 合并的地方建議放在外置存儲(SDcard)當(dāng)中
  2. 合并的過程比較耗時,需要放到子線程中進(jìn)行。

7.4.安裝

任何更新包在下載完成后首先要做的就是進(jìn)行MD5校驗,以便確認(rèn)該更新包是正規(guī)途徑下載而來的。同樣,對于合并之后的更新包,首先要做的事情也是進(jìn)行MD5校驗,校驗通過之后,再進(jìn)行安裝:

 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);
    }

到現(xiàn)在,增量更新已經(jīng)完成。現(xiàn)在可以把增量包以及合并之后的安裝包進(jìn)行刪除了。

大體代碼如下:

private void smartupdate() {
        Observable.create(new Observable.OnSubscribe<File>() {
            @Override
            public void call(Subscriber<? super File> subscriber) {
                //定義生成的新包
                File newApk = new File(Environment.getExternalStorageDirectory(), "newApk.apk");

                //假設(shè)patch.patch文件已經(jīng)下載到sdcard上,切已經(jīng)校驗通過
                File patch = new File(Environment.getExternalStorageDirectory(), "patch.patch");

                if(!patch.exists()){
                    subscriber.onError(new IOException("patch file not exist!"));
                    return;

                //合并差分包
                BsPatchUtil.patch(OSUtil.getApkInstalledSrc(), newApk.getAbsolutePath(), patch.getAbsolutePath());
                if (newApk.exists()) {
                    subscriber.onNext(newApk);
                    subscriber.onCompleted();
                    patch.delete();
                }else{
                    subscriber.onError(new IOException("bspatch failed,file not exist!"));
                }


            }
        }).subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(new Action0() {
                    @Override
                    public void call() {
                        showDialog("正在應(yīng)用差分包");
                    }
                })
                .subscribe(new Subscriber<File>() {
                    @Override
                    public void onCompleted() {
                        hideDialog();
                    }

                    @Override
                    public void onError(Throwable e) {
                        hideDialog();
                        LogUtils.d(e.getMessage());
                    }

                    @Override
                    public void onNext(File file) {
                        OSUtil.installAPK(getActivity(),file);
                    }
                });


    }

8.增量更新的缺點(diǎn)

增量更新雖讓有效的解決了更新包過大的問題,但是存在以下幾點(diǎn)問題:

8.1. 客戶端和服務(wù)端需要加入相應(yīng)的支持。每次發(fā)布新版本,服務(wù)端都需要為以前所有的老版本生成對應(yīng)的差分包,并根據(jù)客戶端端請求返回對應(yīng)的更新包,維護(hù)過程將會變得相對復(fù)雜??蛻舳诵枰獙Σ罘职龈鼮樵敿?xì)的驗證,防止出錯,除此之外,客戶端應(yīng)該可以根據(jù)服務(wù)端更新開關(guān)來確定當(dāng)前是使用完整更新還是增量更新。
8.2. apk包之間的差異過小時,比如2m以下,此時生成的差分包仍然有幾百k,此時使用增量更新得不償失,畢竟形成差分包和合并的過程都非常耗時。另外,但版本之間變化非常大的時候,通常是是大版本好變化的時候,比如從v 1.0.0到2.0.0,此時使用完整更新也不錯。

在師父說中已經(jīng)添加主要代碼,可自行練習(xí)。效果如下:

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在前幾年,整體移動網(wǎng)絡(luò)環(huán)境相比現(xiàn)在差很多,加之流量費(fèi)用又相對較高,因此每當(dāng)我們發(fā)布新版本的時候,一些用戶升級并不是...
    涅槃1992閱讀 5,574評論 2 39
  • @[增量更新,差分包,bsdiff/patch] 背景 隨著Android app的不斷迭代升級,功能越來越多,a...
    SunYo閱讀 15,336評論 2 7
  • kaws聯(lián)名,一陣風(fēng),過去了。 這是2019的六一節(jié),前后,kaws和優(yōu)衣庫的最后一次聯(lián)名。萬民瘋搶。主要針對人群...
    闌珊聽雨堂主人閱讀 333評論 0 2
  • 第【十】章 三個女人一臺戲 楊佳佳關(guān)上門,請湯姜張丹丹坐在客廳的沙發(fā)上,笑了一聲:“我去換件衣服?!本惋w快地跑到...
    馮俊龍閱讀 754評論 1 28
  • 阿塞大四學(xué)生李秋月(中文名)給我們說這邊有個去雪山的當(dāng)?shù)貓F(tuán),她和朋友一起前往,如果我們?nèi)サ脑?,她也幫我們一起報名?..
    心隨筆悟閱讀 604評論 3 16

友情鏈接更多精彩內(nèi)容