手把手玩Android 增量更新

前言:

增量更新已經(jīng)出來幾年了,而一些大的公司早就實現(xiàn)了增量更新。增量更新相較于全量更新的好處不言而喻,利用差分算法獲得1.0版本到2.0版本的差分包,這樣在安裝了1.0的設備上只要下載這個差分包就能夠完成由1.0-2.0的更新。

存在一個1.0版本的apk:
image.png
存在一個2.0版本的apk:
image.png

這樣如果進行全量更新則需要下載完整的25.9大小的apk文件,進行安裝。而如果使用增量更新則只需要下載如下 25.3的差分包。

image.png

下載數(shù)據(jù)減少了0.6M(這里只是為了做了功能,patch包和最新的安裝包大小差不多的情況:建議做全量更新)。這樣做的好處不僅僅在于對于流量的節(jié)省。對于用戶來說現(xiàn)在流量可能并不值錢,或者使用wifi再進行更新,但是從下載時間能夠得到一個良好的優(yōu)化,同時也減小了服務器的壓力。

先來看效果圖:
bianyi.gif
2323.gif
Step1:實現(xiàn)增量更新流程:
image.png
Step2:實現(xiàn)bsdiff、bspatch源文件的編譯

需要實現(xiàn)增量更新,現(xiàn)在有各種開源的制作與合并差分包的開源庫,比如:bsdiff、hdiff等等。因此我們只需要獲得源碼來使用即可。

bsdiff 下載地址: http://www.daemonology.net/bsdiff/
bsdiff 依賴bzip2(zip壓縮庫)
https://nchc.dl.sourceforge.net/project/gnuwin32/bzip2/1.0.5/bzip2-1.0.5-src.zip

下載完成后解壓:


image.png
其中:

Makefile(編譯bsdiff、bapatch)
bsdiff: 比較兩個文件的二進制數(shù)據(jù),生成差分包
bspatch: 合并舊的文件與差分包,生成新文件

很顯然,bspatch我們需要在Android環(huán)境下來執(zhí)行,而bsdiff 一般會在你的存儲服務器當中執(zhí)行即電腦環(huán)境下執(zhí)行(win或linux)

編譯:

對于windows,可以直接從 https://github.com/cnSchwarzer/bsdiff-win/releases 下載。
而Linux/Mac則可以自行編譯:
在Linux中的解壓目錄直接執(zhí)行:make 會產(chǎn)生錯誤。需要修改:

install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
#上面這段makefile片段顯然有問題(lsn9資料,指令必須以tab開頭)
#因此需要修改為:
install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
    .ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
    .endif
#也就是在 `.if` 和 `.endif` 前加一個 tab

修改后再來執(zhí)行make 如果出現(xiàn)找不到bzip2 no file found bzlib.h之類的錯誤,則需要先安裝bzip2:

Ubuntu:

apt install libbz2-dev

Centos:

yum -y install bzip2-devel.x86_64

Mac:

brew install bzip2
如果已經(jīng)安裝bzip2,執(zhí)行make還是出現(xiàn):

bsdiff.c:(.text.startup+0x2aa): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0xcfa): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xe37): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xf80): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0xfe1): undefined reference to `BZ2_bzWriteClose'
bsdiff.c:(.text.startup+0x1034): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0x105c): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0x1082): undefined reference to `BZ2_bzWriteClose'
bsdiff.c:(.text.startup+0x10d5): undefined reference to `BZ2_bzWriteOpen'
bsdiff.c:(.text.startup+0x1100): undefined reference to `BZ2_bzWrite'
bsdiff.c:(.text.startup+0x1126): undefined reference to `BZ2_bzWriteClose'

則修改Makefile為:

CFLAGS          +=      -O3 -lbz2

PREFIX          ?=      /usr/local
INSTALL_PROGRAM ?=      ${INSTALL} -c -s -m 555
INSTALL_MAN     ?=      ${INSTALL} -c -m 444

all:            bsdiff bspatch
bsdiff:         bsdiff.c
        cc bsdiff.c ${CFLAGS} -o bsdiff  #增加
bspatch:        bspatch.c
        cc bspatch.c ${CFLAGS} -o bspatch #增加

install:
        ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
        .ifndef WITHOUT_MAN
        ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
        .endif

cc bsdiff.c ${CFLAGS} -o bsdiff #增加
cc bspatch.c ${CFLAGS} -o bspatch #增加

猜測可能是編譯器的原因。

Step3:AS工程中去配置

1、創(chuàng)建一個AS(NDK)工程
2、在app目錄下的grade文件中增加編譯架構(gòu)類型

externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters 'armeabi-v7a'//加入
            }
        }

3、將下載好的bsdiff 解壓之后,將bspatch.c復制進cpp目錄下。同時也將bspatch.c依賴的bzip2庫的源文件和頭文件復制進cpp目錄下。


pe

4、配置CMakeLists.txt

 cmake_minimum_required(VERSION 3.4.1)


 file(GLOB bzip_source src/main/cpp/bzip/*.c)

 add_library(
              native-lib

              SHARED

              src/main/cpp/native-lib.cpp
              src/main/cpp/bspatch.c
              ${bzip_source}
              
               )
 include_directories(src/main/cpp/bzip)


 find_library( 
               log-lib
               log )


 target_link_libraries( 
                        native-lib
                        ${log-lib} )

5、配置好之后,寫一個native方法

    /**
     *
     * @param oldApk 當前運行的APK
     * @param patch 差分包
     * @param output 合成后新的APK輸出到
     */
    native void bspatch(String oldApk,String patch,String output);
extern "C" {
//bspatch.c中在執(zhí)行合成方法就是其中main(int argc,char * argv[])
//所以在這里需要引入該方法
extern int main(int argc,char * argv[]);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_ly_chen_incrementalupdates_MainActivity_bspatch(JNIEnv *env, jobject 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);


    char * argv[4] = {"", const_cast<char *>(oldApk), const_cast<char *>(output),
                      const_cast<char *>(patch)};
    main(4,argv);

    env->ReleaseStringUTFChars(oldApk_, oldApk);
    env->ReleaseStringUTFChars(patch_, patch);
    env->ReleaseStringUTFChars(output_, output);
}

6、接下我們做合成了安裝包了,不過的注意這個操作是一個耗時操作

//1、合成 apk
        //先從服務器下載到差分包
        new AsyncTask<Void,Void,File>(){
            @Override
            protected File doInBackground(Void... voids) {
                //拿到已經(jīng)安裝版本路徑
                String old = getApplication().getApplicationInfo().sourceDir;
                //合成新的安裝包(備注:記得路徑 等會放置 patch包時候需要)
                bspatch(old,"/sdcard/patch","/sdcard/new.apk");
                //返回新的APK文件
                return new File("/sdcard/new.apk");
            }

            @Override
            protected void onPostExecute(File file) {
                Intent intent = new Intent(Intent.ACTION_VIEW);
                //兼容7.0 
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                    intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
                } else {
                    // 聲明需要的臨時權限
                    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    // 第二個參數(shù),即第一步中配置的authorities
                    String packageName = getApplication().getPackageName();
                    Uri contentUri = FileProvider.getUriForFile(MainActivity.this, packageName + ".fileprovider", file);
                    intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
                }
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        }.execute();

7、兼容7.0和8.0
兼容7.0:在AndroidManifest.xml配置

 <!--四大組件之一 內(nèi)容提供者 可以跨進程使用,7.0安裝必須使用它-->
        <provider
            android:authorities="com.ly.chen.incrementalupdates.fileprovider"
            android:name="android.support.v4.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">

            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

新建一個xml文件


image.png

暴露手機SD卡的路徑

<?xml version="1.0" encoding="utf-8"?>
<resource>
    <paths>
        <external-path name="haah" path="" />
    </paths>
</resource>

適配8.0:加入未知來源權限。備注:只是做Demo演示,沒有動態(tài)申請權限,需手動開啟相關權限

<!--存儲-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--位未知來源權限,適配8.0-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

分別打包 V1.0.0版本 V2.0.0版本

Step3:生成差分包 這里我直接用的是Windows編譯的bsdiff.exe生成的差分包(對于windows,可以直接從 https://github.com/cnSchwarzer/bsdiff-win/releases 下載。)

image.png

上圖:
其中app1.apk:V1.0.0
其中app2.apk:V2.0.0
其中patch:差分包
下載好windows 版 解壓出現(xiàn):
bsdiff.exe:用于生產(chǎn)差分包
bspatch.exe:用于合成差分包玉低版本APK
在當前文件夾下輸入:cmd

bsdiff.exe app1.apk app2.apk patch
生成patch

將patch包放置進 手機SD卡

adb push patch /sdcard/

安裝低版本APK

adb install app1.apk

好了,安裝完成之后(注意手動開啟相關權限,不然會閃退),點擊更新按鈕,就開始更新了。。。不出意外的話,你會看到版本已經(jīng)變了。
github:https://github.com/MrRightChen/IncrementalUpdates

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

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

  • 增量更新在Android開發(fā)中是一種很常見的技術。 增量更新的原理 增量更新的原理非常簡單,就是將本地apk與服務...
    re冷星閱讀 1,677評論 3 3
  • 在前幾年,整體移動網(wǎng)絡環(huán)境相比現(xiàn)在差很多,加之流量費用又相對較高,因此每當我們發(fā)布新版本的時候,一些用戶升級并不是...
    涅槃1992閱讀 5,583評論 2 39
  • 1.概述 1.1.什么是應用增量更新 當我們要更新一個應用的時候,以前很多更新的做法是下載一個新版本去覆蓋一個舊版...
    揚靈閱讀 3,526評論 8 19
  • 沉香能夠供佛、能夠靜心、能夠去除穢氣,是大家都知道的。但是沉香作為佛法的象征,需要更深的感受,像有著堅實的...
    云漢藏閱讀 150評論 0 0
  • 十分鐘作文指導:《運動會一隅 指導老師:孫德玲 一,寫作思路和寫作素材 師: 同學們,前兩天,我們學校的運動會如火...
    簫檸檬閱讀 459評論 0 0

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