Android 實(shí)現(xiàn)增量更新

一、概述

增量更新相較于全量更新的好處不言而喻,利用差分算法獲得1.0版本到2.0版本的差分包,這樣在安裝了1.0的設(shè)備上只要下載這個(gè)差分包就能夠完成由1.0-2.0的更新。比如:

存在一個(gè)1.0版本的apk

apk1.png

然后需要升級(jí)到2.0版本,而2.0版本的apk為

apk2.png

這樣如果進(jìn)行全量更新則需要下載完整的76.6M大小的apk文件,進(jìn)行安裝。而如果使用增量更新則只需要下載如下 50.7M的差分包。

patch.png

下載數(shù)據(jù)減少了26M。這樣做的好處不僅僅在于對(duì)于流量的節(jié)省。對(duì)于用戶(hù)來(lái)說(shuō)現(xiàn)在流量可能并不值錢(qián),或者使用wifi再進(jìn)行更新,但是從下載時(shí)間能夠得到一個(gè)良好的優(yōu)化,同時(shí)也減小了服務(wù)器的壓力。

二、實(shí)現(xiàn)

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

bsdiff 下載地址:

http://www.daemonology.net/bsdiff/

bsdiff 依賴(lài)bzip2(zip壓縮庫(kù))

http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz

下載完成后解壓:

bsdiff源碼.png
  1. bsdiff: 比較兩個(gè)文件的二進(jìn)制數(shù)據(jù),生成差分包

  2. bspatch: 合并舊的文件與差分包,生成新文件

執(zhí)行make

很顯然,bspatch我們需要在A(yíng)ndroid環(huán)境下來(lái)執(zhí)行,而bsdiff 一般會(huì)在你的存儲(chǔ)服務(wù)器當(dāng)中執(zhí)行即電腦環(huán)境下執(zhí)行(win或linux)

切到解壓后的目錄,然后執(zhí)行make:

Makefile:13: *** missing separator. Stop.

這時(shí)會(huì)報(bào)錯(cuò),需要修改Makefile文件,將install:下面的if,endif添加一個(gè)縮進(jìn):

install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
#上面這段makefile片段顯然有問(wèn)題
#因此需要修改為:
install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
    .ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
    .endif
#也就是在 `.if` 和 `.endif` 前加一個(gè) tab

unknown type name 'u_char'; did you mean 'char'static off_t offtin(u_char *buf)

然后,重新執(zhí)行make:

錯(cuò)誤很明顯,找不到u_char等,因?yàn)槿鄙倭祟^文件

bspatch.c文件中加入

#include <sys/types.h>

再次make就好了

no file found bzlib.h之類(lèi)的錯(cuò)誤

如果出現(xiàn)找不到bzip2 no file found bzlib.h之類(lèi)的錯(cuò)誤,則需要先安裝bzip2:

Ubuntu:

apt install libbz2-dev

Centos:

yum -y install bzip2-devel.x86_64

Mac:

brew install bzip2

最后執(zhí)行make后沒(méi)有問(wèn)題了,就會(huì)生成兩個(gè)bsdiff和bspatch的可執(zhí)行文件

bsdiff和bspatch的工具的使用

首先我們準(zhǔn)備兩個(gè)apk,old.apk和new.apk,你可以自己隨便寫(xiě)個(gè)項(xiàng)目,先運(yùn)行一次拿到生成的apk作為old.apk;然后修改些代碼,或者加一些功能,再運(yùn)行一次生成new.apk;

  • 生成增量文件
./bsdiff old.apk new.apk patch

這樣就生成了一個(gè)增量文件patch

  • 增量文件和old.apk合并成新的apk
./bspatch old.apk new2.apk patch

這樣就生成一個(gè)new2.apk

我們可以使用md5來(lái)查看new.apk和new2.apk兩個(gè)文件的md5值,

md5值的比較

三、android代碼中實(shí)現(xiàn)bspatch合并

將bspatch.c文件考入到項(xiàng)目的cpp目錄下,因?yàn)槠溥€需要bzip2依賴(lài),所以將下載好的bzip2的一些源碼也考入到項(xiàng)目中,

從下載的bzip2里的Makefile中的OBJS可以看出需要7個(gè)源文件文件,因此將這對(duì)應(yīng)的源文件考入到抗美中,然后在將依賴(lài)的.h文件也考入項(xiàng)目中

最終要考入的文件如下:

在項(xiàng)目的CMakeLists.txt文件中把bspatch.c和bzip項(xiàng)目源文件加入其中

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

此時(shí)執(zhí)行AS的Build下的Make project,發(fā)現(xiàn)會(huì)報(bào)錯(cuò)

明明考入的有bzlib.h這個(gè)文件,為哈還是報(bào)錯(cuò)呢?,其實(shí)這個(gè)是因?yàn)閎spatch.c里是以#include <bzlib.h>這種形式引入bzlib.h的,我們可以將其引入方式改為#include "bzip/bzlib.h"就可以了,但在這里我們用一種不修改源碼的方式解決,就是在CMakeLists.txt中加入一句include_directories(src/main/cpp/bzip)就可以了。

下面是具體實(shí)現(xiàn)

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView version = (TextView) findViewById(R.id.tv_version);
        version.setText(BuildConfig.VERSION_NAME);
    }

   /**
     * native方法 使用路徑為oldapk的apk與路徑為patch的補(bǔ)丁包,合成新的apk,并存儲(chǔ)于output
     * @param oldapk 當(dāng)前運(yùn)行的apk
     * @param patch 差分包
     * @param output 合成后的新的apk
     */
    native void bspatch(String oldapk,String patch,String output);

    public void onUpdate(View view) {
        new MyAsyncTask().execute();
    }

    private class MyAsyncTask extends AsyncTask<Void,Void,File>{

        @Override
        protected File doInBackground(Void... voids) {
            //1、合成apk
            String old = getApplication().getApplicationInfo().sourceDir;

            bspatch(old,"/sdcard/patch","/sdcard/new.apk");
            return new File("/sdcard/new.apk");
        }

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

記得開(kāi)啟讀寫(xiě)SDCard權(quán)限。

native-lib.cpp

#include <jni.h>
#include <string>

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

extern "C"
JNIEXPORT void JNICALL
Java_com_example_zuo_bsdiff_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);


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

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

因?yàn)樵赼ndroid7.0以上調(diào)用安裝界面需要特殊處理:

在A(yíng)ndroidManifest.xml文件中添加provider,這里的provider介紹可以參考博客:android 7.0 因?yàn)閒ile://引起的FileUriExposedException異常

 <provider
    android:authorities="com.example.zuo.bsdiff.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>

新建一個(gè)file_paths.xml

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

四、打包

分別打一個(gè)1.0版本的apk包,在打一個(gè)2.0版本的apk包,然后使用./bsdiff app-1.apk app-2.apk patch生成個(gè)差分包,將這個(gè)差分包考到/sdcard下,安裝舊版本的apk后,更新就可以升級(jí)到2.0版本的apk

大致的效果圖如下:

增量更新效果圖

源碼

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

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

  • 最近就是在練習(xí)ndk開(kāi)發(fā),剛好遇到android增量更新的話(huà)題,主要是工具的運(yùn)用,略帶使用第三方so庫(kù)的流程~~~...
    紅黑軍團(tuán)號(hào)閱讀 668評(píng)論 1 3
  • 1、普通更新和增量更新 首先了解一下應(yīng)用普通更新的邏輯(這里指不通過(guò)應(yīng)用市場(chǎng)更新):新版本發(fā)布后將APK文件上傳到...
    AxeChen閱讀 1,061評(píng)論 0 9
  • 家鄉(xiāng) 我的家鄉(xiāng)彭村! 麥浪隨風(fēng)飄動(dòng)! 旭日東升高照! 鄰里鄉(xiāng)親和諧! 新農(nóng)村! 新氣象! 新憧憬! 老鄉(xiāng)情!...
    冰峰2018閱讀 206評(píng)論 0 1
  • 今天關(guān)先生心情大好的給我發(fā)來(lái)幾張照片,是年前我送他的兩盆多肉類(lèi)植物,他帶到他的辦公室里養(yǎng)。其中一盆黃金萬(wàn)年草是從我...
    劉小妹_7ea9閱讀 316評(píng)論 0 0
  • 昨晚十點(diǎn)多了,鄭老師趕到頤和,匆匆吃了一點(diǎn)面條后就到會(huì)議室給我們講課,為了這次生命成長(zhǎng)營(yíng)順利進(jìn)行,他給我們講了如何...
    胡寶琴閱讀 236評(píng)論 0 0

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