Android應(yīng)用增量更新/升級方案

@[增量更新,差分包,bsdiff/patch]

背景

隨著Android app的不斷迭代升級氧骤,功能越來越多叮贩,apk體積也越來越大,雖然當(dāng)前移動網(wǎng)絡(luò)環(huán)境較幾年前有巨大提升话侧,但流量資費依然不便宜栗精,因此每次發(fā)布新版時用戶升級并不是很積極,自從Android4.1開始掂摔,Google引入了應(yīng)用程序的Smart App Update术羔,即增量更新,增量更新提供了一個更好的方式將更新推送到設(shè)備乙漓,相對于全量更新而言前者只需要將變化的部分推送出去级历,這有助于用戶更快的下載更新、節(jié)省設(shè)備電量消耗叭披,最重要的是有效降低了應(yīng)用升級時消耗的網(wǎng)絡(luò)流量寥殖,國內(nèi)小米、360應(yīng)用市場已經(jīng)使用了該更新機制推出了省流量更新功能涩蜘。


官方說明

Smart app updates is a new feature of Google Play that introduces a better way of delivering app updates to devices. When developers publish an update, Google Play now delivers only the bits that have changed to devices, rather than the entire APK. This makes the updates much lighter-weight in most cases, so they are faster to download, save the device’s battery, and conserve bandwidth usage on users’ mobile data plan. On average, a smart app update is about 1/3 the sizeof a full APK update.
http://developer.android.com/about/versions/jelly-bean.html


實現(xiàn)原理

增量更新原理其實比較簡單嚼贡,就是通過差分算法將新舊版本進行對比將有差異的地方抽取出來生成更新補丁patch,也稱之為差分包同诫≡敛撸客戶端在檢測到更新的時候,只需要將差分包下載到本地误窖,然后通過合成算法將差分包與當(dāng)前應(yīng)用合并叮盘,生成最新安裝包秩贰,在文件校驗通過后執(zhí)行安裝即可。目前主流的差分比較算法是bsdiff/patch柔吼,來自http://www.daemonology.net/bsdiff/ 毒费,該算法是開源的,可根據(jù)平臺的不同在對應(yīng)平臺使用源代碼進行編譯集成愈魏。


編碼實現(xiàn)

準(zhǔn)備工具

  • bsdiff/patch源碼(點擊下載)
  • 由于bsdiff/patch依賴bzip2庫觅玻,因此還需要下載bzip2。(點擊下載)
  • Android studio配置NDK環(huán)境
  1. 打開Tools->Android->SDK Manager->SDK Tools選中LLDB和NDK培漏,點擊確認溪厘,軟件會自動安裝NDK。見下圖:


    enter image description here
  2. 配置環(huán)境變量牌柄,點擊File->Project Structure打開設(shè)置頁面桩匪,點擊SDK Location選項卡設(shè)置NDK路徑。


    image.png

生成差分包

  • 編譯bsdiff/patch友鼻,Mac環(huán)境編譯方法如下:
  1. 解壓下載的bsdiff-4.3.tar.gz
    tar -zxvf bsdiff-4.3.tar.gz
  2. 進入bsdiff-4.3目錄,在終端下執(zhí)行構(gòu)建
    cd bsdiff-4.3
    make
    Window/linux平臺可參考這篇文章 增量更新:bsdiff工具的安裝和使用
  • bsdiff命令:
  1. 生成差分包:
    命令:bsdiff old.file new.file add.patch ,即old.file是舊的文件闺骚,new.file是新更改變化的文件彩扔,add.patch是這兩個文件的差異文件(即差分包).
    生成差分包需要較多的內(nèi)存和時間,所幸這些操作只需要在服務(wù)器后端執(zhí)行僻爽。
  2. 舊文件和差分包合成新文件:
    命令:bspatch old.file createNew.file add.patch 其中createNew.file是合并后的新文件

合并差分包

  • 創(chuàng)建Native方法類
 public class PatchUtils {

    static PatchUtils instance;

    public static PatchUtils getInstance() {
        if (instance == null)
            instance = new PatchUtils();
        return instance;
    }

    static {
        System.loadLibrary("ApkPatchLibrary");
    }

    /**
     * native方法 使用路徑為oldApkPath的apk與路徑為patchPath的補丁包虫碉,合成新的apk,并存儲于newApkPath
     * 
     * 返回:0胸梆,說明操作成功
     * 
     * @param oldApkPath
     *            示例:/sdcard/old.apk
     * @param newApkPath
     *            示例:/sdcard/new.apk
     * @param patchPath
     *            示例:/sdcard/xx.patch
     * @return
     */
    public native int patch(String oldApkPath, String newApkPath, String patchPath);
}

編譯之后在工程build/intermediates/classes對應(yīng)路徑下生成PatchUtils.class文件敦捧,打開終端切換到該目錄,輸入命令行javah com.yyh.lib.bsdiff.PatchDroid(包名.類名),生成頭文件com_yyh_lib_bsdiff_PatchUtils.h碰镜。

  • 實現(xiàn)Native方法
    將上一個步驟生成的頭文件拷貝到工程jni目錄下兢卵,同時解壓bzip2包和bspatch源碼到該目錄下,將bspatch.c重命名為com_yyh_lib_bsdiff_PatchUtils.c(注意命名方式為包名.類名)绪颖,并在其中實現(xiàn)Java_com_yyh_lib_bsdiff_PatchUtils_patch方法秽荤,注意方法名一定要包含Native方法類所在的包名絕對路徑,包名可以自定義柠横。

JNIEXPORT jint JNICALL Java_com_yyh_lib_bsdiff_PatchUtils_patch
  (JNIEnv *env, jclass cls,
            jstring old, jstring new, jstring patch){
    int argc = 4;
    char * argv[argc];
    argv[0] = "bspatch";
    argv[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));
    argv[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));
    argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));

    printf("old apk = %s \n", argv[1]);
    printf("patch = %s \n", argv[3]);
    printf("new apk = %s \n", argv[2]);

    int ret = applypatch(argc, argv);

    printf("patch result = %d ", ret);

    (*env)->ReleaseStringUTFChars(env, old, argv[1]);
    (*env)->ReleaseStringUTFChars(env, new, argv[2]);
    (*env)->ReleaseStringUTFChars(env, patch, argv[3]);
    return ret;
}

編譯SO模塊

在jni目錄下創(chuàng)建Android.mk文件窃款,寫入以下代碼,其中LOCAL_MODULE表示SO模塊名稱牍氛,LOCAL_SRC_FILES表示源文件路徑晨继,用相對路徑即可,不必寫絕對路徑搬俊,具體語法可參考:http://www.cnblogs.com/wainiwann/p/3837936.html紊扬,這里一定要注意加上這句代碼APP_PLATFORM:=android-14,其中android-14與你工程的minSDKVersion一致即可蜒茄,否則運行在某些低版本設(shè)備上會出現(xiàn)java.lang.UnsatisfiedLinkError錯誤。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := ApkPatchLibrary
LOCAL_LDFLAGS := -Wl,--build-id
LOCAL_SRC_FILES := \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/com_yyh_lib_bsdiff_DiffUtils.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/com_yyh_lib_bsdiff_PatchUtils.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/blocksort.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/bzip2.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/bzip2recover.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/bzlib.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/compress.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/crctable.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/decompress.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/huffman.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/randtable.c \
    /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni/bzip2/readMe.txt \

LOCAL_C_INCLUDES += /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/main/jni
LOCAL_C_INCLUDES += /Users/xiayang075/Documents/項目/IncrementallyUpdate/app/src/debug/jni

include $(BUILD_SHARED_LIBRARY)
APP_PLATFORM:=android-14

在jni目錄下創(chuàng)建Application.mk文件珠月,復(fù)制以下代碼:

APP_MODULES := libApkPatchLibrary (lib+so文件名)
APP_ABI := all

修改app module下的build.gradle文件扩淀,如下:

    ndk{
        moduleName "ApkPatchLibrary"
    }
    sourceSets {
        main {
            jni.srcDirs = [] //禁用gradle編譯jni
            jniLibs.srcDirs = ['libs'] // libs為so文件所在包路徑
        }
    }

推薦參考以下文章編譯NDK,超級簡單的Android Studio jni 實現(xiàn)(無需命令行)

將差分包與當(dāng)前應(yīng)用合成新包啤挎,注意生產(chǎn)上要注意對差分包驻谆、本地包以及生成后的新包做MD5文件校驗,防止文件被篡改庆聘,確保最后生成新包的MD5值與全量包一致胜臊。

    private class PatchTask extends AsyncTask<String, Void, Integer> {

        @Override
        protected Integer doInBackground(String... params) {

            try {

                int result = PatchUtils.getInstance().patch(srcDir, destDir2, patchDir);
                if (result == 0) {
                    handler.obtainMessage(4).sendToTarget();
                    return WHAT_SUCCESS;
                } else {
                    handler.obtainMessage(5).sendToTarget();
                    return WHAT_FAIL_PATCH;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return WHAT_FAIL_PATCH;
        }

        @Override
        protected void onPostExecute(Integer integer) {
            super.onPostExecute(integer);
            loadding.setVisibility(View.GONE);
        }
    }

安裝新包

注意使用chmod命令修改權(quán)限,否則在高版本Android系統(tǒng)上可能會報錯伙判。

    private void install(String dir) {
        String command = "chmod 777 " + dir;
        Runtime runtime = Runtime.getRuntime();
        try {
            runtime.exec(command); // 可執(zhí)行權(quán)限
        } catch (IOException e) {
            e.printStackTrace();
        }

        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(Uri.parse("file://" + dir), "application/vnd.android.package-archive");
        startActivity(intent);
    }

結(jié)語:

使用增量更新方式可以解決往常使用全量更新時安裝包過大的問題象对,但其本身還有以下不足:

  • 多版本運營繁瑣,當(dāng)線上存在多個版本時宴抚,要給每個版本分別生成差分包勒魔;
  • 使用多渠道包時,要針對每個渠道包分別生成差分包菇曲,造成差分包非常多冠绢,難以維護;
  • patch依賴本地版本安裝包完整性常潮,如果本地文件損壞或者被篡改弟胀,就無法增量升級,只能下載全量包進行升級喊式;
  • 使用bs diff/patch算法生成的差分包體積依然比較大孵户,以同學(xué)會為例,新老包大小約為15M左右岔留,修改少量代碼并生成差分包體積達到了5M左右夏哭,與官方宣稱的差量包體積約為全量包體積的1/3一致,但上述差分算法還有待優(yōu)化的空間贸诚,如果需要對差分算法進行改進可參考HDiffPatch 和 rsync rolling等方庭。

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酱固,隨后出現(xiàn)的幾起案子械念,更是在濱河造成了極大的恐慌,老刑警劉巖运悲,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件西设,死亡現(xiàn)場離奇詭異级野,居然都是意外死亡哟玷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門烁巫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宠能,你說我怎么就攤上這事亚隙。” “怎么了违崇?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵阿弃,是天一觀的道長。 經(jīng)常有香客問我羞延,道長渣淳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任伴箩,我火速辦了婚禮入愧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗤谚。我一直安慰自己棺蛛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布巩步。 她就那樣靜靜地躺著鞠值,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渗钉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天钞钙,我揣著相機與錄音鳄橘,去河邊找鬼。 笑死芒炼,一個胖子當(dāng)著我的面吹牛瘫怜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播本刽,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼鲸湃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了子寓?” 一聲冷哼從身側(cè)響起暗挑,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎斜友,沒想到半個月后炸裆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡鲜屏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年烹看,在試婚紗的時候發(fā)現(xiàn)自己被綠了国拇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡惯殊,死狀恐怖酱吝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情土思,我是刑警寧澤务热,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站浪漠,受9級特大地震影響陕习,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜址愿,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一该镣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧响谓,春花似錦损合、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赖晶,卻和暖如春律适,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遏插。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工捂贿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胳嘲。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓厂僧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親了牛。 傳聞我的和親對象是個殘疾皇子颜屠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,509評論 25 707
  • 在前幾年,整體移動網(wǎng)絡(luò)環(huán)境相比現(xiàn)在差很多鹰祸,加之流量費用又相對較高甫窟,因此每當(dāng)我們發(fā)布新版本的時候,一些用戶升級并不是...
    涅槃1992閱讀 5,470評論 2 39
  • 1.概述 1.1.什么是應(yīng)用增量更新 當(dāng)我們要更新一個應(yīng)用的時候蛙婴,以前很多更新的做法是下載一個新版本去覆蓋一個舊版...
    揚靈閱讀 3,152評論 8 19
  • 風(fēng)在四處的游走 一直捕風(fēng)捉影般告白自己 我游走在城市鄉(xiāng)村山野 尋覓并且沉思 成長的痛是煉獄的魔鬼 直到體無完膚遍體...
    營州布衣閱讀 138評論 1 5
  • 消息中間件是目前互聯(lián)網(wǎng)服務(wù)常用的技術(shù)服務(wù)蕴坪。消息中間件為應(yīng)用系統(tǒng)提供高效、靈活的消息同步和異步傳輸處理、存儲轉(zhuǎn)發(fā)背传、可...
    王帥199207閱讀 511評論 0 2