騰訊--增量升級爲什麼減少升級代價,增量升級原理

在前幾年,整體移動網絡環境相比現在差很多,加之流量費用又相對較高,因此每當我們發佈新版本的時候,一些用戶升級並不是很積極,這就造成了新版本的升級率並不高。而google爲了解決了這個問題,提出了Smart App Update,即增量更新(也叫做差分升級)。

儘管現在網絡環境有了很大的提升,但一個不爭的事實就是應用越做越大,因此,增量更新在目前的仍然是一種解決APP更新包過大的有效方案。今天,我們就來聊聊增量更新。

什麼是增量更新?

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

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

增量升級優勢在哪裏?

​ 3.差分的優勢

  • 大小非常小
  • 安全-必須是特定的節點才能進行升級
  • 相對於整包來說更容易控制

爲什麼會減少升級代價

例如舊版本的APK有5M,新版的有8M,更新的部分則可能只有3M左右(這裏需要說明的是,得到的差分包大小並不是簡單的相減,因爲其實需要包含一些上下文相關的東西),使用差分升級的好處顯而易見,

那麼你不需要下載完整的8M文件,只需要下載更新部分就可以,而更新部分可能只有3、4M,可以很大程度上減少流量的損失。

增量更新的原理

增量更新的原理非常簡單,簡單的說就是通過某種算法找出新版本和舊版本不一樣的地方(這個過程也叫做差分),然後將不一樣的地方抽取出來形成所謂的更新補丁(patch),也稱之爲差分包。客戶端在檢測到更新的時候,只需要下載差分包到本地,然後將差分包合併至本地的安裝包,形成新版本的安裝包,文件校驗通過後再執行安裝即可。本地的安裝包通過提取當前已安裝應用的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中實現相關的代碼了:

#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)來進行合併了。此處需要注意兩點:

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

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

到現在,增量更新已經完成。現在可以把增量包以及合併之後的安裝包進行刪除了。

大體代碼如下:

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

                //假設patch.patch文件已經下載到sdcard上,切已經校驗通過
                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("正在應用差分包");
                    }
                })
                .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);
                    }
                });


    }

增量更新的缺點

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

  1. 客戶端和服務端需要加入相應的支持。每次發佈新版本,服務端都需要爲以前所有的老版本生成對應的差分包,並根據客戶端端請求返回對應的更新包,維護過程將會變得相對複雜。客戶端需要對差分包做更爲詳細的驗證,防止出錯,除此之外,客戶端應該可以根據服務端更新開關來確定當前是使用完整更新還是增量更新。
  2. apk包之間的差異過小時,比如2m以下,此時生成的差分包仍然有幾百k,此時使用增量更新得不償失,畢竟形成差分包和合並的過程都非常耗時。另外,但版本之間變化非常大的時候,通常是是大版本好變化的時候,比如從v 1.0.0到2.0.0,此時使用完整更新也不錯。

在師父說中已經添加主要代碼,可自行練習。效果如下:

1.gif

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章