Android NDK的使用實例——增量更新實戰(zhàn)

image

1、概述

Android很多應(yīng)用沒有使用到NDK開發(fā),但想要開發(fā)更高級的應(yīng)用,NDK的學習是必然之路。NDK的好處不多說,這里也應(yīng)該說是JNI的好處,其中之一就是可以方便使用到C/C++世界里面的優(yōu)秀開源庫,這里要實戰(zhàn)的是增量更新,其中用到的是bsdiff開源代碼,而bsdiff又依賴bzip2開源代碼。

一開始自己做過一些硬件開發(fā),也使用過一些so庫,使用的話按照文檔指示一般沒什么問題,但實際上對NDK開發(fā)流程總有一些疑問。比如說

  • 如何創(chuàng)建so庫,so庫是怎么生成的?
  • 假如我生成了so庫,如何給其他應(yīng)用調(diào)用這個so庫?
  • jni目錄、cpp目錄又是什么?為什么有些項目使用的不一樣
  • Android.mk、Application.mk是什么?
  • NDK開發(fā)一定要使用到j(luò)avah、ndk-build命令嗎?
  • CMake、CMakeLists.txt又是什么?

對這些問題同樣存在疑問的,可以參考網(wǎng)上的一些博客,寫的非常詳細
Android NDK 開發(fā)從 0 到 1
Android NDK開發(fā)掃盲及最新CMake的編譯使用
AndroidStudio中使用JNI/NDK示例

產(chǎn)生這些問題的原因在于平時只看書、博客是會忽略掉很多細節(jié),從而產(chǎn)生這樣的疑問,所以NDK的學習之路,必然需要動手操作。

增量更新不同于熱更新,增量更新可以應(yīng)用于app市場,避免用戶用過多的流量去升級app,只需要下載差分部分的patch補丁就可以,更快速、節(jié)省流量的實現(xiàn)了app的更新。

2、在mac上實現(xiàn)增量更新

我用的是mac系統(tǒng),win應(yīng)該也差不多,先下載文件

Binary diff/patch utility
bzip2

2.1、使用make命令生成bsdiff、bspatch可執(zhí)行文件
lexdeMacBook-Pro:bsdiff-4.3 lex$ make

遇到問題

Mac下xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

解決方法

xcode-select --install

重新安裝xcode-select就可以
執(zhí)行make命令后還是出現(xiàn)以下問題

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

參考網(wǎng)上說對Makefile文件中 if/endif 做了縮進,沒效果。這里是一個坑,我用AndroidStudio去編輯無效,后來使用vim來編輯就可以了。

image

這時候生成了bsdiff可執(zhí)行文件,接著出現(xiàn)以下錯誤,無法生成bspatch可執(zhí)行文件,雖然說合并這部分在Android上做就可以了。

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
bspatch.c:65:8: error: expected ';' after expression
        u_char header[32],buf[8];
              ^
              ;
bspatch.c:65:2: error: use of undeclared identifier 'u_char'; did you mean
      'putchar'?
        u_char header[32],buf[8];
        ^~~~~~
        putchar
/usr/include/stdio.h:261:6: note: 'putchar' declared here
int      putchar(int);
         ^
bspatch.c:65:9: error: use of undeclared identifier 'header'
        u_char header[32],buf[8];
               ^
bspatch.c:65:20: error: use of undeclared identifier 'buf'
        u_char header[32],buf[8];
        
...
   

缺少頭文件,編輯bspatch.c加入頭文件可以解決

#include <sys/types.h>

該文件是存在于

/usr/include/sys/types.h

再次執(zhí)行make命令,成功的生成了兩個可執(zhí)行文件bsdiff、bspatch,前者用于差分生成補丁包,后者用于合并補丁生成新的apk包。

2.2、增量文件的生成與合并

使用AndroidStudio簡單生成兩個apk,old.apk、new.apk
注意:每次生成apk最好clean一次工程

使用以下命令./bsdiff old.apk new.apk patch.patch

lexdeMacBook-Pro:bsdiff-4.3 lex$ ./bsdiff old.apk new.apk patch.patch

得到了patch.patch補丁
然后使用以下命令./bspatch old.apk new2.apk patch.patch

lexdeMacBook-Pro:bsdiff-4.3 lex$ ./bspatch old.apk new2.apk patch.patch

得到了新的apk,new2.apk

這個時候可以安裝查看,也可以使用MD5來校驗new.apk、new2.apk是否一致

lexdeMacBook-Pro:bsdiff-4.3 lex$ md5 new.apk
MD5 (new.apk) = 322b5a702bc1507e547c24f05109d812
lexdeMacBook-Pro:bsdiff-4.3 lex$ md5 new2.apk
MD5 (new2.apk) = 322b5a702bc1507e547c24f05109d812

事實上已經(jīng)可以說明兩個文件幾乎一致了,說明這個操作是可行的了。

3、Android上實現(xiàn)增量更新

但這個怎么在Android上使用呢?這時候就要使用到NDK的知識了。

流程應(yīng)該是這樣的:
① 服務(wù)器端生成patch補丁
② 客戶端通過對應(yīng)的版本號獲取相應(yīng)的patch補丁
③ 客戶端將本地apk跟patch補丁合并,生成新的apk
④ 安裝新的apk從而實現(xiàn)了增量更新

這里作為demo的話,就只要實現(xiàn)將本地apk跟patch補丁合并生成新apk,然后安裝看看是否成功就可以了。

3.1、新建一個C++ support工程

具體步驟不贅述,AndroidStudio默認使用CMake的形式
不要忘記添加SD卡讀寫權(quán)限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
3.2、NDK相關(guān)代碼的配置

copy相關(guān)文件到工程jni目錄下
復(fù)制bspatch.c到j(luò)ni目錄下
復(fù)制bzip2文件夾到j(luò)ni目錄下,只留下頭文件.h和源文件.c

image

修改CMakeLists.txt,主要是添加了需要編譯的.c源碼和.h頭文件,這里要全部都編譯,所以全部都添加到編譯列表里面去

cmake_minimum_required(VERSION 3.4.1)

add_library( 
             SHARED
             
             src/main/jni/bspatch.c
             src/main/jni/bzip2/blocksort.c
             src/main/jni/bzip2/bzip2.c
             src/main/jni/bzip2/bzip2recover.c
             src/main/jni/bzip2/bzlib.c
             src/main/jni/bzip2/bzlib.h
             src/main/jni/bzip2/bzlib_private.h
             src/main/jni/bzip2/compress.c
             src/main/jni/bzip2/crctable.c
             src/main/jni/bzip2/decompress.c
             src/main/jni/bzip2/dlltest.c
             src/main/jni/bzip2/huffman.c
             src/main/jni/bzip2/mk251.c
             src/main/jni/bzip2/randtable.c
             src/main/jni/bzip2/spewG.c
             src/main/jni/bzip2/unzcrash.c )

find_library(
              log-lib

              log )

target_link_libraries(
                       bspatch

                       ${log-lib} )

修改bspatch.c里面的include,以及include jni

#include "bzip2/bzlib.h"
#include <jni.h>

編寫增量更新工具類BsPatchUtil.java,寫native方法

/**
 * 增量更新工具類
 * Created by lex.
 */

public class BsPatchUtil {
    static {
        System.loadLibrary("bspatch");
    }
    public static native int bspatch(String oldApk, String newApk, String patch);
}

選中bspatch方法,按alt + enter,生成源代碼方法,編寫jni代碼,傳入?yún)?shù)。patchMethod()就是bspatch.c中的main()方法

JNIEXPORT jint JNICALL
Java_com_example_lex_bsdiff_BsPatchUtil_bspatch(JNIEnv *env, jclass type, jstring oldApk_, jstring newApk_, jstring patch_) {
    const char *oldApk = (*env)->GetStringUTFChars(env, oldApk_, 0);
    const char *newApk = (*env)->GetStringUTFChars(env, newApk_, 0);
    const char *patch = (*env)->GetStringUTFChars(env, patch_, 0);

    // TODO
    int argc = 4;
    char *argv[argc];
    argv[0] = "bspatch";
    argv[1] = (char *) oldApk;
    argv[2] = (char *) newApk;
    argv[3] = (char *) patch;
    int ret = patchMethod(argc, argv);

    (*env)->ReleaseStringUTFChars(env, oldApk_, oldApk);
    (*env)->ReleaseStringUTFChars(env, newApk_, newApk);
    (*env)->ReleaseStringUTFChars(env, patch_, patch);

    return ret;
}

編譯后會發(fā)現(xiàn),報main方法重復(fù)的錯誤

Error:(70) multiple definition of `main'

找到各個對應(yīng)文件,找到main方法,注釋掉就可以編譯成功了。

3.3、編寫Android代碼實現(xiàn)增量更新
/**
 * 點擊按鈕觸發(fā)本地apk與補丁的合并
 */
public void patch(View btn) {
    String oldApk = getSourceDir();
    String newApk = getNewApkPath();
    String patch = getPatchPath();
    // 注意文件判空,否則崩潰
    BsPatchUtil.bspatch(oldApk, newApk, patch);
    // 如果成功了,調(diào)用intent去自動去安裝apk
    // ...
}

/**
 * 獲取本地apk的路徑
 */
public String getSourceDir() {
    String sourceDir = getApplicationInfo().sourceDir;
    Log.i(TAG, "sourceDir = " + sourceDir);

    return sourceDir;
}

/**
 * 生成新apk的路徑
 */
public String getNewApkPath() {
    String newApkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "newApk.apk";
    Log.i(TAG, "newApkPath = " + newApkPath);
    return newApkPath;
}

/**
 * 獲取補丁patch的路徑
 */
public String getPatchPath() {
    String patch = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "patch.patch";
    Log.i(TAG, "patch = " + patch);
    return patch;
}

安裝新的apk,成功!

源碼已上傳到 github
感謝鴻洋_大神的 Android 增量更新完全解析 是增量不是熱修復(fù)

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

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

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