NDK開發—增量更新

AxeChen發表於2018-01-23

1、普通更新和增量更新

首先了解一下應用普通更新的邏輯:
新版本釋出後將APK檔案上傳到伺服器。然後由安卓客戶端下載新的APK檔案並安裝。

但是如果APK過大,普通更新的弊端就出現了。
比如:一個遊戲的APK,老版本有480M。新版本新增了一個模組APK增加到500M。按照普通更新的邏輯,使用者需要下載500M的APK,很顯然比較費流量!這個並不是只針對使用者,對伺服器也是如此,伺服器也需要節省頻寬。

假如有一種機制:
只需要下載新版APK與老版APK多出的不同的部分,然後再由安卓客戶端將老版的APK和下載的部分合併成新版APK,再進行安裝更新。

例如這個例子,使用者只需要下載20M左右的更新包就能進行更新操作,就能非常愉快的解決流量的這個問題了。

以上的這個例子就是增量更新的基本邏輯,用圖形來表示:

增量更新

根據增量更新的邏輯,可以知道增量更新重要的幾個知識點。

  • 伺服器根據新舊APK生成的增量包。(本文重點)
  • 客戶端下載差分包(本文不做介紹)。
  • 客戶端根據舊APK和下載的差分包生成新的APK。(本文重點,需要一點點NDK的基礎)
  • 客戶端安裝新的APK。

2、增量更新的依賴

增量更新需要差分APK和合並APK,這裡並不需要應用的開發者去寫演算法,直接可用三方提供的工具。

官方地址: www.daemonology.net/bsdiff/

不過Windows無法下載,可能被牆的緣故。我從第三方弄到的工具:

連結:拆分合並工具百度雲地址

資源
這裡集合了Windows平臺和Linux平臺使用的差分工具,以及客戶端需要用到的合併工具。

3、差分APK

差分APK的操作有多種,我這裡介紹Windows平臺和Linux平臺兩種。

3.1、Windows下的差分

將下載的工具中Windows平臺下的檔案bsdiff4.3-win32-src.zip解壓。

bsdiff for windows
然後在Release資料夾下可以看到兩個.exe檔案。需要使用Windows差分檔案需要用到。bsdiff.exe.
bsdiff
接下來將舊版的APK和新版的APK複製到這個資料夾下,執行以下命令:

bsdiff.exe  舊APK地址  新APK地址 增量包地址
複製程式碼

執行拆分命令
然後增量包就生成了:
生成增量包
以上差分增量包的方法應該是非常簡單了。

差分的效果:

差分的效果
通過這個圖可以看到,增量包要比新版本的安裝包要小很多的。不過APK太小,效果並不是非常明顯,如果新版apk比較大,效果則明顯。

3.2、Linux下的差分

Linux系統同樣是提供差分支援的。我這邊是裝了一個Ubuntu的虛擬機器。由於Linux不太熟練,我這裡只展示Linux差分的核心部分。

將bsdiff-4.3.tar.gz和bzip2-1.0.6.tar.gz解壓。

將bsdiff-4.3.tar.gz中的bsdiff.c和bzip2-1.0.6.tar.gz中所有的.c和.h檔案全部複製到一個目錄中。(我這裡是用windows解壓的,所以放的是windows的截圖)

bsdiff.c
bzip2的所有.h和.c
在Linux中新建一個資料夾存放這些檔案:
在Linux的檔案

用linux的編譯命令gcc將這些檔案編譯成Linux中可執行的檔案。(gcc是什麼命令我就不介紹了)。接下來會踩幾個坑。 執行:

 gcc -g -o axe_bsdiff  blocksort.c decompress.c bsdiff.c  randtable.c  bzip2.c huffman.c compress.c bzlib.c crctable.c 
複製程式碼

裡面的axe_bsdiff是Linux端可執行檔案的檔名,可修改。執行這段命令就會遇到以下問題:

第一個坑
用vim進入bsdiff.c,找include bzlib.h的地方:
vim bsdiff.c
問題一目瞭然了,只有引用系統提供的標頭檔案才能用"< >",那就把這裡改成 include "bzlib.h" 然後儲存!

解決這個問題後繼續執行gcc命令。

第二個坑
以上就是第二個坑了,main方法被多次定義。這個問題也很清楚,每個程式只有一個main方法。那就把其他地方定義的main去掉就行了。
將不需要的main方法修改成其他方法
這裡需要保留bsdiff.c 中的main方法。 我這裡用windows端的notepad++修改的。名字是隨便改的,只要符合規範就行了。

客戶端合併也需要這些修改後的原始檔,已上傳git。最後有連結

修改完成之後,繼續執行gcc編譯命令。生成了axe_bsdiff的可執行檔案!

gcc成功

接下來的步驟就和windows端差分增量包操作差不多了。執行命令:

./axe_bsdiff 舊APK路徑 新APK路徑 增量包路徑
複製程式碼

將新舊APK都複製到了這個資料夾,然後執行命令:

執行差分命令
成功生成增量包:
成功生成增量包

總體來說Linux差分增量包要比Windows差分增量包麻煩些。還可能會遇到許可權不夠的問題,這個時候需要chmod命令等等。

4、客戶端的合併操作

這裡還是需要用到bsdiff-4.3.tar.gz和bzip2-1.0.6.tar.gz的解壓檔案。這裡需要用到bsdiff-4.3中的bspatch.c還有bzip2-1.0.6所有的.c和.h。上面講到的Linux中差分增量包的檔案,這裡的可以直接使用。

4.1、新建NDK專案 (這個就不講解了)
4.2、將所需要的檔案匯入並且配置好CMakeLists.txt。

檔案匯入

CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

# 載入指定資料夾的所有檔案
file(GLOB bzip2 src/main/cpp/bzip2/*.c)

find_library( log-lib
              log )
              
# 配置AxeBsPatch動態庫
add_library( AxeBsPatch
             SHARED
             src/main/cpp/bspatch.c
             ${bzip2}
             )
             
# 連結AxeBsPatch動態庫
target_link_libraries( AxeBsPatch
                       ${log-lib} 
複製程式碼

我這邊就稍微截個圖。

4.3、新建native方法。

這邊生成的native方法需要傳入三個引數,老的APK地址、新的APK地址、下載的增量包的地址。

public class BsPatch {
    public native static int patch(String oldFile,String newFile,String patchFile);
    static {
        System.loadLibrary("AxeBsPatch");
    }
}
複製程式碼

然後用javah命令生成.h檔案。

4.4、實現.h檔案中的方法

在bspatch.c中include生成的標頭檔案。並實現.h中的方法。

#include "com_mg_axechen_bspatch_BsPatch.h"
// ... ... 省略若干程式碼

/*
 * Class:     com_mg_axechen_bspatch_BsPatch
 * Method:    patch
 * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
 * params: old_path:The path for old APK.
 *         new_path:The path for new APK.
 *         patch_path:The path for patch package.
 */
JNIEXPORT jint JNICALL Java_com_mg_axechen_bspatch_BsPatch_patch
        (JNIEnv *env, jclass jclz, jstring old_path, jstring new_path, jstring patch_path) {

    // 先將jstring轉化為char
    char *oldPath = (char *) (*env)->GetStringUTFChars(env, old_path, NULL);
    char *newPath = (char *) (*env)->GetStringUTFChars(env, new_path, NULL);
    char *patchPath = (char *) (*env)->GetStringUTFChars(env, patch_path, NULL);
    char *argv[4];

    int ret = -1;

    // 第一個引數隨便填寫
    argv[0] = "AXE_BSPATCH";
    argv[1] = oldPath;
    argv[2] = newPath;
    argv[3] = patchPath;

    // 第一個引數必須為4.
    ret = main(4, argv);

    // 回收資源
    (*env)->ReleaseStringUTFChars(env, old_path, oldPath);
    (*env)->ReleaseStringUTFChars(env, new_path, newPath);
    (*env)->ReleaseStringUTFChars(env, patch_path, patchPath);

    return ret;
}
複製程式碼

這邊也比較簡單,將傳入的引數放入陣列中,然後呼叫bspatch中的main方法即可。

4.5、更新的邏輯判斷

為了模擬得更像更新的邏輯,我加入了簡單的VersionCode的判斷。 老版APK的VersionCode是1 ,新版的版本VersionCode是2。如果是老版才更新。

因為更新耗時,裡面包含了下載和合並。所以放線上程中。 如果合併成功,就通過Hanlder傳送通知去安裝新的APK。 我這裡沒有下載的邏輯,所以先將增量包放在了安裝客戶端sdcard的指定路徑!

/**
     * 更新操作
     *
     * @param view
     */
    public void upload(View view) {
        if (APKUtils.getVersionCode(MainActivity.this, getPackageName()) >= 2) {
            //
            new AlertDialog.Builder(MainActivity.this)
                    .setMessage("已經是最新的版本了,無需更新!")
                    .setNegativeButton("確定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).create().show();
            return;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 下載就省略。 由於我沒有伺服器進行下載

                // 1、拿到老的APK
                String oldFile = APKUtils.getSourceApkPath(MainActivity.this, getPackageName());

                // 2、拿增量包的路徑
                String patchFile = Contants.PATCH_FILE_PATH;

                Log.i("AXE", patchFile);
                // 3、開始執行合併操作
                int ret = BsPatch.patch(oldFile, Contants.NEW_APK_PATH, patchFile);

                // 4、合併的成功和失敗
                if (ret == 0) {
                    Log.i("AXE", "合併成功");
                    handler.sendEmptyMessage(0);
                } else {
                    Log.i("AXE", "合併失敗");
                }
            }
        }).start();
    }
複製程式碼
4.6、安裝APK

7.0後的APK安裝有很大的改變。看下官方的解釋吧。

看“在應用間共享檔案”這部分

具體實現我也不貼出來了,這裡直接貼出其他人的部落格連結。
blog.csdn.net/czhpxl007/a…

5、增量更新的優缺點

優點:

  • 很明顯,能節約流量,節省伺服器開支。

缺點:

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

以上缺點引用CSDN部落格:

http://blog.csdn.net/dd864140130/article/details/52928419
作者:江湖人稱小白哥

6、最後

本文講解了增量更新的基本邏輯、差分操作(Windows/Linux)、合併的操作(客戶端)、7.0後安裝APK的改變。

客戶端合併的原始碼: 原始碼傳送門

參考部落格:
blog.csdn.net/lmj62356579…

blog.csdn.net/dd864140130…

相關文章