Android 探索增量升級

一、介紹

Android 的增量升級,不同熱修復和熱更新,它只是通過和老的 apk 對比,識別出與新 apk 之間的二進制差異,從而生成的補丁包(差量包);
這樣的好處在於,不用全部下載所有的文件,比如一個遊戲 1個多G,如果每次更新,都下載1個多G,相信這個遊戲基本沒人下,但是使用差量包,則需要幾十或者幾百兆,這樣對用戶來說,相對能接受些。
通過這篇文章,你將看到:

  • 差量包的生成
  • cmake 實現 bsdiff 升級接口的過程
  • 生成 so 工其他工程使用

工程連接:https://github.com/LillteZheng/ZDiffUpdate

二、生成差量包

首先,我們需要藉助 bsdiff工具生成差量包。win 用戶下載這個:
在這裏插入圖片描述
接着解壓,文件如下:
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200610114125794.png在這裏插入圖片描述
接着,把老的 apk 和 新的 apk 放到相同目錄下,並使用 cmd 執行以下命令,生成 patch

bsdiff_win_exe>bsdiff.exe  v1.0.apk v1.1.apk patch.patch

然後再通過 patch 與 老版本的apk對比,生成 差量包:

bspatch.exe v1.0.apk new.apk patch.patch

最後的結果如下:
在這裏插入圖片描述
這樣,v1.1.apk 與 new.apk 是相同的,這樣,我們只需要把 new.apk 和 patch 上傳到服務器,Android下載更新即可

三、Android 增量更新(cmake)

bsdiff 工具爲 .c 的源代碼,需要配合 bszip。(有些鏈接已經失效,通過我的github工程加載)
導入源碼:
在這裏插入圖片描述
然後打開 CMakeLists.txt 配置源碼路徑:

# 配置路徑,CMAKE_SOURCE_DIR 爲CMakeLists.txt 的位置
file(GLOB bzip_source ${CMAKE_SOURCE_DIR}/bzip/*.c)
add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED
		#添加 bspatch 和 bzip 下的所有代碼
        bspatch.c
        ${bzip_source}

        # Provides a relative path to your source file(s).
        native-lib.cpp)

此時編譯,會出現 bspatch.c 報錯的問題,我們需要改變頭文件的引入路徑,註釋掉默認的 <bzlib.h>,修改爲:
在這裏插入圖片描述
然後,再修改它的 main()函數名爲 execute_update,方便後面我們的調用:
在這裏插入圖片描述

3.1 添加 Java 層調用方法

新建一個 UpdateJni.java 類,引用 jni 庫,並添加方法:

public class UpdateJni {
    static {
        System.loadLibrary("native-lib");
    }

    /**
     * 升級方法
     *
     * @param oldPath    老的apk 路徑
     * @param patch      對比生成的 patch 的路徑
     * @param newApkPath 新 apk 的路徑
     */
    public static native void diffUpdate(String oldPath, String patch, String newApkPath);
}

然後,編寫 native-lib.cpp ,添加 diffUpdate() 方法,如下:


extern "C"{
//引入bspatch.c裏的main方法
extern int execute_update(int argc,char * argv[]);
}

extern "C" JNIEXPORT void JNICALL
Java_com_zhengsr_zdiffupdate_UpdateJni_diffUpdate(JNIEnv *env, jclass instance, jstring oldapk_,
                                                 jstring patch_, jstring output_) {
    const char *oldapk = env->GetStringUTFChars(oldapk_, 0);
    const char *patch = env->GetStringUTFChars(patch_, 0);
    const char *output = env->GetStringUTFChars(output_, 0);


    int argc = 4;
    char *argv[4] ={"", const_cast<char *>(oldapk),const_cast<char *>(output),const_cast<char *>(patch)};
    execute_update(argc,argv);

    env->ReleaseStringUTFChars(oldapk_, oldapk);
    env->ReleaseStringUTFChars(patch_, patch);
    env->ReleaseStringUTFChars(output_, output);
}

這樣,我們的 C 層就編寫完了。

回到 activity,在點擊事件中,更新差量包:

   public void test(View view) {
        /**
         * 使用請參考以下步驟
         * 1、請先安裝 v1.0.apk 看看效果,然後不要點擊它的button,adb 命令參考:adb install -r v1.0.apk
         * 2、接着把new.apk 和 patch.patch push 到 sdcard,adb 命令參考:adb  push patch.patch /sdcard/.
         * 3、運行軟件,點擊更新,即可看到 v1.0.apk 的背景被替換了
         */
        new UpdateTask().execute();
    }

    class UpdateTask extends AsyncTask<Void,Void,File> {

        @Override
        protected File doInBackground(Void... voids) {
            //自己的apk 可以用這個
           // String sourceDir = getApplicationInfo().sourceDir;
            String sourceDir = "/data/app/com.zhengsr.diffupdate-1/base.apk";

            String patch = Environment.getExternalStorageDirectory().getAbsolutePath()+"/patch.patch";

            String newApk = Environment.getExternalStorageDirectory().getAbsolutePath()+"/new.apk";

            File file1 = new File(patch);
            File file2 = new File(newApk);

           
            long time = System.currentTimeMillis();
 			//差分包建議在子線程中運行,防止阻塞主線程
            UpdateJni.diffUpdate(sourceDir,patch,newApk);


            return new File(newApk);
        }

        @Override
        protected void onPostExecute(File file) {
            super.onPostExecute(file);
            //2、安裝
            Intent i = new Intent(Intent.ACTION_VIEW);
            if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
                i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                i.setDataAndType(Uri.fromFile(file),
                        "application/vnd.android.package-archive");
            }else {
                i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                String packageName = getApplication().getPackageName();
                Uri contentUri = FileProvider.getUriForFile(MainActivity.this, packageName+ ".fileProvider", file);
                i.setDataAndType(contentUri,"application/vnd.android.package-archive");
            }
            startActivity(i);
        }
    }

由於解析差量包的過程是好事的,所以,我們放在 asynctask 中去更新。 效果如下:
在這裏插入圖片描述

四、生成 so 工其他工程使用

既然功能已經完成了,下個工程還得重新搞一遍有點得不償失,所以,從 debug 中,拿到已經有完整功能的 so:
在這裏插入圖片描述
新建一個cmake或ndk工程,把它放到 libs 中,並把 libnative-lib.so 命名成自己喜歡的,比如 libdiffUpdate.so:
在這裏插入圖片描述
然後再 build.gradle 中配置so庫的位置:
在這裏插入圖片描述
接着,新建一個和 so 相同的包名,並把 UpdateJni 複製過去:
在這裏插入圖片描述
然後在 activity 中調用即可:

    class UpdateTask extends AsyncTask<Void,Void, File> {

        @Override
        protected File doInBackground(Void... voids) {
            //自己的apk 可以用這個
            // String sourceDir = getApplicationInfo().sourceDir;
            String sourceDir = "/data/app/com.zhengsr.diffupdate-1/base.apk";

            String patch = Environment.getExternalStorageDirectory().getAbsolutePath()+"/patch.patch";

            String newApk = Environment.getExternalStorageDirectory().getAbsolutePath()+"/new.apk";

            File file1 = new File(patch);
            File file2 = new File(newApk);

            //差分包建議在子線程中運行,防止阻塞主線程,這裏是測試,所以沒關係
            long time = System.currentTimeMillis();

            UpdateJni.diffUpdate(sourceDir,patch,newApk);

            return new File(newApk);
        }

        @Override
        protected void onPostExecute(File file) {
            super.onPostExecute(file);
            //2、安裝
            Intent i = new Intent(Intent.ACTION_VIEW);
            if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
                i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                i.setDataAndType(Uri.fromFile(file),
                        "application/vnd.android.package-archive");
            }else {
                i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                String packageName = getApplication().getPackageName();
                Uri contentUri = FileProvider.getUriForFile(MainActivity.this, packageName+ ".fileProvider", file);
                i.setDataAndType(contentUri,"application/vnd.android.package-archive");
            }
            startActivity(i);
        }
    }

參考:Android增量更新

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