增量更新服務端與客戶端的處理方案

分類:技術 時間:2016-10-25

前言

如果要對一個 APP 進行更新,你會怎么做呢,或許你的宿主 App 有若干功能,每一個功能都是一個插件,每次在功能更新的時候,直接更新插件就好,但是這種方式也有局限,它不能進行宿主程序更新,對于非常大的 apk ,增量更新還是有必要的,可以節省流量。增量更新的難點在于不同市場,不同版本都需要生成對應的差分包。差分包是將手機上已安裝 apk 與服務器端最新 apk 進行二進制對比得到的,用戶更新程序時,只需要下載差分包,并在本地使用差分包與已安裝 apk,合成新版 apk。假設現在已經有三個版本version1.apk, version2.apk ,version3.apk ,最新版本是 version4.apk,那么需要在服務器端與最新版本version4.apk 相比較生成 version1.patch, version2.patch , version3.patch 三個差分包。假設客戶端現在是 version3.apk 這個版本,那么就需要下載 version3.patch 差分包,下載之后在客戶端完成合并操作并且提示用戶進行安裝。那么我們現在至少有兩個問題要解決,如何生成差分包?如何對差分包進行合并?

差量更新算法的核心思想是,盡可能多的利用old文件中已有的內容,盡可能少的加入新的內容來構建new文件。通常的做法是對old文件和new文件做子字符串匹配或使用hash技術,提取公共部分,將new文件中剩余的部分打包成patch包 apk文件的差分、合成,可以通過開源的二進制比較工具 bsdiff( https://github.com/mendsley/bsdiff) 來實現,又因為bsdiff依賴bzip2,所以我們還需要用到 bzip2,在bsdiff中bsdiff.c 用于生成差分包,bspatch.c 用于合成差分包。傳送門( http://www.daemonology.net/bsdiff/ )

一、在服務端生成差分包

1、下載bsdiff文件

如圖,把里面所有的C/C 文件和.h文件全部添加到Visual Studio中(bspatch.cpp文件可以不添加,這個是用于客戶端合并的,bsdiff.cpp才是文件差分)。

圖1-bsdiff文件內容

2、新建web工程,定義jni函數。

web項目結構.png

jni函數是用于調用bsdiff.cpp中的main函數的,bsdiff.cpp文件main函數是實現生成差分包的入口,通過分析代碼可以知道,main函數中需要傳遞四個參數,第一個參數沒用到,可以隨便指定,第二個參數是舊版本apk的路徑,第三個參數是新版本apk的路徑,第四個參數是生成差分包所在的路徑。所以jin函數定義成這樣:

/*
 * Class:     com_wangjing_patch_BsDiff
 * Method:    bsdiff
 * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_wangjing_patch_BsDiff_bsdiff
  (JNIEnv *, jclass, jstring, jstring, jstring);

3、在bsdiff.cpp實現bsdiff函數。

將main函數修改成bsdiffmain,在bsdiff函數中調用。

JNIEXPORT void JNICALL Java_com_wangjing_patch_BsDiff_bsdiff(JNIEnv * env, jclass cla, jstring old_apk_path_str, jstring new_apk_path_str, jstring diff_packet_file_path_str){

    char * old_apk_path = (char*)env-gt;GetStringUTFChars(old_apk_path_str,NULL);
    char * new_apk_path = (char*)env-gt;GetStringUTFChars(new_apk_path_str, NULL);
    char * diff_packet_file_path = (char*)env-gt;GetStringUTFChars(diff_packet_file_path_str, NULL);

    char *argv[4];

    argv[0] = quot;bisdiffquot;;
    argv[1] = old_apk_path;
    argv[2] = new_apk_path;
    argv[3] = diff_packet_file_path;


    bsdiffmain(4,argv);
    env-gt;ReleaseStringUTFChars(old_apk_path_str,old_apk_path);
    env-gt;ReleaseStringUTFChars(new_apk_path_str, new_apk_path);
    env-gt;ReleaseStringUTFChars(diff_packet_file_path_str, diff_packet_file_path);

}

4、編譯成dll動態庫

這個過程遇到很多錯誤,比如這個開源庫用到了一些不安全的函數,在文件中添加#define _CRT_SECURE_NO_WARNINGS。還有指針未初始化的等等,編譯成功后,我們將生成的dll文件放到服務器目錄下。

5、加載動態庫

public class BiDiffTest {
    public static void main(String[] args) {
        BsDiff.bsdiff(Constant.old_apk_path, Constant.new_apk_path, Constant.diff_file_path);
    }
}
public class BsDiff {
    /**
     * 
     * @param oldApkPath
     * @param newApkPath
     * @param pacthPacketPath
     */
    public static native void bsdiff(String oldApkPath,String newApkPath,String pacthPacketPath);

    static {
        System.loadLibrary(quot;PatchDiffquot;);
    }
}

二、在客戶端合并差分包

1、項目配置

1.1 新建一個android項目,最好將androidStudio升級到2.2,這樣寫C代碼時候更加方便。然后將下載的bizip2文件復制到cpp目錄下以及將bsdiff中bspatch.cpp復制到cpp目錄下。

android項目新建.png

1.2. 配置gralde

gradle配置.png

1.3配置CMakeLists.txt

將項目根目錄的CMakeLists.txt移動到cpp目錄下,編輯打開添加下面兩行代碼

set(bzip2_src_DIR ${CMAKE_SOURCE_DIR})
add_subdirectory(${bzip2_src_DIR}/bzip2)

在bizp2文件中新建一個CMakeLists.txt文件,內容是:PROJECT(bzip2)

2、 定義jni函數

調用 bspatch.cpp 中的 main 函數,這一步與上面的調用 bsdiff.cpp 步驟類似。

public class BsPatch {
    public native static void patch(String oldfile, String newfile, String patchfile);
      static{
          System.loadLibrary(quot;bspatchquot;);
        }
    }

3、在bspatch.cpp中實現jni函數

首先將bspatch.cpp中的main函數重命名成bspatch_main,在下面調用

JNIEXPORT void JNICALL Java_apk_wangjing_com_apkpatch_utils_BsPatch_patch
  (JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr){
    int argc = 4;
    char* oldfile = (char*)(*env)-gt;GetStringUTFChars(env,oldfile_jstr, NULL);
    char* newfile = (char*)(*env)-gt;GetStringUTFChars(env,newfile_jstr, NULL);
    char* patchfile = (char*)(*env)-gt;GetStringUTFChars(env,patchfile_jstr, NULL);
    //參數(第一個參數無效)
    char *argv[4];
    argv[0] = quot;bspatchquot;;
    argv[1] = oldfile;
    argv[2] = newfile;
    argv[3] = patchfile;
    __android_log_print(ANDROID_LOG_DEBUG,quot;bspatchquot;,quot;合并開始quot;);
    bspatch_main(argc,argv);

    (*env)-gt;ReleaseStringUTFChars(env,oldfile_jstr, oldfile);
    (*env)-gt;ReleaseStringUTFChars(env,newfile_jstr, newfile);
    (*env)-gt;ReleaseStringUTFChars(env,patchfile_jstr, patchfile);

}

4、下載/合并差分包,并且提示用戶安裝

public class DownLoaderTask extends AsyncTasklt;Void, Void, Booleangt; {
        @Override
        protected Boolean doInBackground(Void... params) {
            try {
                //1、下載服務器中的差分包
                File patchFile = DownloadUtils.download(Constants.URL_PATCH_DOWNLOAD);
                if (patchFile != null) {
                    //2、獲取本地(data/dada/app目錄)apk文件 
                    String oldFilePath = ApkUtils.getSourceApkPath(MainActivity.this, getPackageName());
                    String newFilePath = Constants.NEW_APK_PATH;
                    //3、將本地apk文件與差分包進行合并
                    BsPatch.patch(oldFilePath, newFilePath, patchFile.getAbsolutePath());
                }
                return true;
            } catch (Exception e) {
                return  false;
            }
        }
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if(aBoolean){
                Toast.makeText(MainActivity.this, quot;開始更新quot;, Toast.LENGTH_SHORT).show();
                ApkUtils.installApk(MainActivity.this, Constants.NEW_APK_PATH);
            }else {
                Toast.makeText(MainActivity.this, quot;更新失敗quot;, Toast.LENGTH_SHORT).show();
            }
        }
    }
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mApkCodeTv = (TextView) findViewById(R.id.apk_code);
        ApkUtils.Apk apkInfo = ApkUtils.getAPPVersionCodeFromAPP(this);
        mApkCodeTv.setText(apkInfo.toString());
        new DownLoaderTask().execute();
    }

到此 ,如果順利的話,apk就跟新成功了。最后附上一個開源項目,也是用bsdiff實現的增量更新---- Smart App Updates( https://github.com/cundong/SmartAppUpdates)


Tags: JNI C 安卓開發

文章來源:http://www.jianshu.com/p/2551a01916df


ads
ads

相關文章
ads

相關文章

ad