增量更新

現(xiàn)在的應(yīng)用市場(chǎng),很多都有省流量更新,到底是使用什么技術(shù)實(shí)現(xiàn)的呢宽档?了解一番后,原來(lái)翩活,是使用到了增量更新,而且實(shí)現(xiàn)的手段便贵,其實(shí)也挺簡(jiǎn)單的菠镇。

基本流程就是這樣的:用戶手機(jī)上已經(jīng)安裝的應(yīng)用,比如版本為1.0承璃,下載新版本2.0與1.0的增量包(也可以稱為差分包)利耍,然后1.0與增量包合并為新的2.0包,重新安裝盔粹。

結(jié)合上面的流程隘梨,基本技術(shù)點(diǎn)就是生成和合并增量包:

  1. 生成增量包
  2. 合并增量包

生成bsdiff和bspatch可執(zhí)行文件

下載

增量包的生成,目前已經(jīng)有很多開源庫(kù)了舷嗡,可以直接使用bsdiff轴猎,進(jìn)去bsdiff下載頁(yè)面,點(diǎn)擊here就可以下載并解壓进萄∧聿保可以看到有以下文件

?  bsdiff-4.3 ls
Makefile  bsdiff.1  bsdiff.c  bspatch.1 bspatch.c

bsdiff:比較兩個(gè)文件的二進(jìn)制數(shù)據(jù),生成差分包中鼠。一般會(huì)在你的存儲(chǔ)服務(wù)器當(dāng)中執(zhí)行郎仆,接下來(lái)以Mac系統(tǒng)環(huán)境下演示

bspatch:合并舊的文件與差分包,生成新文件兜蠕。一般在Android環(huán)境中執(zhí)行,也就是會(huì)集成到Android項(xiàng)目中抛寝。

編譯

# 執(zhí)行make命令
?  bsdiff-4.3 make
Makefile:13: *** missing separator.  Stop.

出現(xiàn)錯(cuò)誤提示的原因是:在makefile中熊杨,命令行要以tab鍵開頭,所以我們要對(duì)MakeFile進(jìn)行編輯

# 原來(lái)的代碼
install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif

# 添加tab開頭后的代碼
install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
    .ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
    .endif

修改完成后盗舰,重新執(zhí)行make命令

?  bsdiff-4.3 make
cc -O3 -lbz2    bsdiff.c   -o bsdiff
cc -O3 -lbz2    bspatch.c   -o bspatch
bspatch.c:39:21: error: unknown type name 'u_char'; did you mean 'char'?
static off_t offtin(u_char *buf)
                    ^~~~~~
                    char

成功生成了bsdiff可執(zhí)行文件晶府,但是在生成bspathc的時(shí)候,卻出現(xiàn)錯(cuò)誤提示钻趋,原因是:找不到u_char的聲明川陆,所以需要引入#include <sys/types.h>bspatch.c文件中,再次執(zhí)行make蛮位。

?  bsdiff-4.3 make
cc -O3 -lbz2    bspatch.c   -o bspatch
?  bsdiff-4.3 ls
Makefile  bsdiff    bsdiff.1  bsdiff.c  bspatch   bspatch.1 bspatch.c

可以發(fā)現(xiàn)多出了bsdiffbspatch文件

合并增量文件

生成差分包的步驟较沪,就先放在后面鳞绕,先介紹怎樣在項(xiàng)目中實(shí)現(xiàn)合并增量包,然后再編譯不同的包進(jìn)行增量更新尸曼。

創(chuàng)建項(xiàng)目

創(chuàng)建NDK項(xiàng)目们何,最簡(jiǎn)單的就是在創(chuàng)建時(shí),勾選Include C++ Support控轿,拷貝bspatch.csrc/main/cpp文件中冤竹,修改CMakeList.txt文件。

add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp
             # 新增
             src/main/cpp/bspatch.c) 

嘗試編譯運(yùn)行茬射,提示出錯(cuò)如下鹦蠕,找不到bzlib.h

fatal error: 'bzlib.h' file not found
#include <bzlib.h>
         ^~~~~~~~~

因?yàn)閎spathc依賴于bzip2(zip壓縮庫(kù)),所以需要導(dǎo)入bzip2庫(kù)文件在抛,bizp2下載地址钟病,直接解壓,然后在cpp文件下創(chuàng)建bzip文件(文件名可以隨便人ā)档悠,把其中的.c.h文件拷貝進(jìn)去。

如果不想全部拷貝進(jìn)去望浩,可以分析MakeFile文件辖所,生成bzip2的庫(kù),需要下列這些文件磨德,只需要對(duì)應(yīng)找其中的.c文件缘回,執(zhí)行編譯就通過(guò)錯(cuò)誤提示不斷導(dǎo)入需要的文件。

OBJS= blocksort.o  \
      huffman.o    \
      crctable.o   \
      randtable.o  \
      compress.o   \
      decompress.o \
      bzlib.o

并且需要修改CMakeList.txt文件

# 新增
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}) 

導(dǎo)入bzlib.h文件后典挑,編譯運(yùn)行仍然出現(xiàn)如下

fatal error: 'bzlib.h' file not found
#include <bzlib.h>
         ^~~~~~~~~

再修改CMakeList.txt文件酥宴,不斷添加文件直到編譯成功不報(bào)錯(cuò)

include_directories(src/main/cpp/bzip) 

編寫native方法

導(dǎo)入bzlib庫(kù)成功后,新建一個(gè)native方法您觉,用于執(zhí)行合并拆分包

/**
* 合并差分包
* @param oldApk 當(dāng)前項(xiàng)目路徑
* @param newApk 差分包路徑
* @param patchFile 合并后新包路徑
*/
private native void native_bspatch(String oldApk,String newApk,String patchFile);

修改native-lib.cpp文件

#include <jni.h>

// 執(zhí)行合并差分包拙寡,實(shí)際上就是bspatch.c中的main()方法
// 由于native-lib.cpp為c++,bspatch.c為c琳水,所以需要使用extern
extern "C" {
extern int main(int argc,char * argv[]);
}

extern "C"
JNIEXPORT void JNICALL
Java_cn_guidongyuan_studypatch_MainActivity_native_1bspatch(JNIEnv *env, jobject instance,
                                                            jstring oldApk_, jstring newApk_,
                                                            jstring patchFile_) {
    const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
    const char *newApk = env->GetStringUTFChars(newApk_, 0);
    const char *pathFile = env->GetStringUTFChars(patchFile_, 0);

    char * argv[4] = {"", const_cast<char *>(oldApk), const_cast<char *>(newApk),
                      const_cast<char *>(pathFile)};

    main(4, argv);

    env->ReleaseStringUTFChars(oldApk_, oldApk);
    env->ReleaseStringUTFChars(newApk_, newApk);
    env->ReleaseStringUTFChars(patchFile_, pathFile);
}

在MainActivity中肆糕,點(diǎn)擊按鈕,執(zhí)行更新

/**
* 模擬下載在孝,合并應(yīng)用包诚啃,跳轉(zhuǎn)安裝
*/
public void downLoadApkPatch(View view) {
    new AsyncTask<Void,Void,File>() {

        @Override
        protected File doInBackground(Void... voids) {
            // 此處模擬用戶去服務(wù)器下載增量包,實(shí)際項(xiàng)目中私沮,一般會(huì)通過(guò)發(fā)送當(dāng)前用戶的版本號(hào)向服務(wù)器請(qǐng)求差分包始赎,并且服務(wù)器會(huì)返回合成新包后的md5值作為驗(yàn)證,此處就省略
            // 獲取當(dāng)前應(yīng)用的路徑
            String oldPath = getApplication().getApplicationInfo().sourceDir;
            String newPath = Environment.getExternalStorageDirectory().getPath() + File.separator + "new.apk";
            String patchPath = Environment.getExternalStorageDirectory().getPath() + File.separator + "patch.diff";

            if (new File(patchPath).exists()) {
                native_bspatch(oldPath,newPath,patchPath);
                return new File(newPath);
            }else {
                Log.w(TAG, "patch file is not exists");
                return null;
            }
        }

        @Override
        protected void onPostExecute(File file) {
            if (file != null) {
                installNewApk(file);
            }
        }
    }.execute();
}

差分包下載后就需要執(zhí)行安裝,不同版本的執(zhí)行流程不一樣需要額外處理

/**
* 跳轉(zhuǎn)到安裝頁(yè)面
* @param file 新包路徑
*/
private void installNewApk(File file) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }else {
        // 聲明需要的臨時(shí)權(quán)限
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        String packageName = getApplication().getPackageName();
        Uri contentUri = FileProvider.getUriForFile(MainActivity.this, packageName + ".fileProvider", file);
        intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
    }
    startActivity(intent);
}

另外造垛,還要需要修改AndroidManifest.xml魔招,開啟必要的權(quán)限

// 讀寫SDCard權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

// 7.0 執(zhí)行安裝聲明
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="cn.guidongyuan.studypatch.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">

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

創(chuàng)建xml目錄下的文件file_paths.xml

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

生成增量包

編譯以上項(xiàng)目,生成old.apk筋搏,修改版本號(hào)為2.0仆百,再次編譯生成new.apk。都拷貝到與bsdiff同一個(gè)目錄下奔脐,執(zhí)行

# ./bsdiff 舊版本apk 新版本apk 差分包(文件名和格式可以隨便取)
?  bsdiff-4.3 ./bsdiff old.apk new.apk patch.diff

拷貝patch.diff到手機(jī)根目錄中俄周,執(zhí)行舊項(xiàng)目代碼,就可以看到跳轉(zhuǎn)到應(yīng)用安裝界面

image

完整項(xiàng)目

完整項(xiàng)目demo代碼髓迎,可以自行到github

錯(cuò)誤

  • 編譯的時(shí)候峦朗,出現(xiàn)錯(cuò)誤提示如下,應(yīng)該是提示鏈接庫(kù)失敗

    Build command failed.
    Error while executing process ‘...省略忘記先復(fù)制完整錯(cuò)誤信息了....bzip2.c.o', missing and no known rule to make it
    

    修改方法:Build -> Refresh Linked C++ Projects

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末波势,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子橄维,更是在濱河造成了極大的恐慌尺铣,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件争舞,死亡現(xiàn)場(chǎng)離奇詭異凛忿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)竞川,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門店溢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人委乌,你說(shuō)我怎么就攤上這事床牧。” “怎么了遭贸?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵戈咳,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我壕吹,道長(zhǎng)著蛙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任算利,我火速辦了婚禮,結(jié)果婚禮上泳姐,老公的妹妹穿的比我還像新娘效拭。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布缎患。 她就那樣靜靜地躺著慕的,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挤渔。 梳的紋絲不亂的頭發(fā)上肮街,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音判导,去河邊找鬼嫉父。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眼刃,可吹牛的內(nèi)容都是我干的绕辖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼擂红,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼仪际!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起昵骤,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤树碱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后变秦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體成榜,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年伴栓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伦连。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钳垮,死狀恐怖惑淳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饺窿,我是刑警寧澤歧焦,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站肚医,受9級(jí)特大地震影響绢馍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肠套,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一舰涌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧你稚,春花似錦瓷耙、人聲如沸朱躺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)长搀。三九已至,卻和暖如春鸡典,著一層夾襖步出監(jiān)牢的瞬間源请,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工彻况, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谁尸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓疗垛,卻偏偏與公主長(zhǎng)得像症汹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贷腕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359