android jni bspatch

[TOC]

1. 下载并配置NDK:

https://developer.android.com/ndk/downloads/index.html

2. 编译Bsdiff

2.1 编写java类,创建native方法

package com.commons.utils;
	
public class Bspatch {
	
    static {
        System.loadLibrary("bspatch");
    }
	
    public static native int applyPatch(String oldFile, String newFile, String patchFile);
	
}

2.2 生成.h文件

  1. 使用javac编写Bspatch或者在ide里面编译,这里我在android studio中编写并执行gradle的compileDebugJavaWithJavac任务进行编译,编译完成后,切换到classes目录。

  2. 使用javah生成.h文件

    // 切换到编译好的类目录
    cd app/build/intermediates/classes
    // 生成.h文件
    javah -d h -classpath debug com.commons.utils.Bspatch
    	
    

2.3 编译c/c++代码.

  1. 复制生成的.h文件到c/c++文件目录.
  2. 在存放c/c++代码的目录中添加Android.mk、Application.mk文件。 其中Android.mk用于指定要编译的类,模块名称等; Application.mk用于指定要生成特定abi类型的so文件,如:x86,armeabi等。

目录结构如下:

  • app
    • build
    • jni
      • bspatch
      • bzip2
      • Android.mk
      • Application.mk
      • com_commons_utils_Bspatch.c
      • com_commons_utils_Bspatch.h
    • libs
    • src
    • build.gradle
    • proguard-rules.pro
  1. Android.mk内容如下:

    LOCAL_PATH := $(call my-dir)
    	
    include $(CLEAR_VARS)
    	
    # 指定模块名称
    LOCAL_MODULE    := bspatch
    # 添加源码
    LOCAL_SRC_FILES := com_commons_utils_BsPatch.c
    	
    # for logging
    LOCAL_LDLIBS    := -lz -llog
    	
    include $(BUILD_SHARED_LIBRARY)
    	
    
  2. Application.mk

    #表示生成全部abi的so
    APP_ABI := all
    	
    
  3. 切换到jni目录,手动编译native代码。

    $ cd app/jni
    $ ndk-build
    

3. 修改bspatch代码

  1. 为了调用简单,这里直接修改了bspatch的main函数为patch函数,然后在jni函数中直接调用patch函数进行合并。

    	
    #include <android/log.h>
    	
    #include "bspatch/bspatch.c"
    	
    #include "com_commons_utils_Bspatch.h"
    	
    	
    	
    /*
    * Class:     com_commons_utils_Bspatch
    * Method:    applyPatch
    * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
    */
    JNIEXPORT jint JNICALL Java_com_commons_utils_Bspatch_applyPatch
        (JNIEnv *env, jobject obj, jstring oldFile, jstring newFile, jstring patchFile) {
    	
    int ret;
    char *ch[4];
    ch[0] = "bspatch";
    ch[1] = (char *) ((*env)->GetStringUTFChars(env, oldFile, 0));
    ch[2] = (char *) ((*env)->GetStringUTFChars(env, newFile, 0));
    ch[3] = (char *) ((*env)->GetStringUTFChars(env, patchFile, 0));
    	
    __android_log_print(ANDROID_LOG_INFO, "bspatch", "oldFile = %s ", ch[1]);
    __android_log_print(ANDROID_LOG_INFO, "bspatch", "newFile = %s ", ch[2]);
    __android_log_print(ANDROID_LOG_INFO, "bspatch", "patchFile = %s ", ch[3]);
    	
    ret = patch(4, ch);
    __android_log_print(ANDROID_LOG_INFO, "bspatch", "applypatch result = %d ", ret);
    	
    (*env)->ReleaseStringUTFChars(env, oldFile, ch[1]);
    (*env)->ReleaseStringUTFChars(env, newFile, ch[2]);
    (*env)->ReleaseStringUTFChars(env, patchFile, ch[3]);
    return ret;
    }
    	
    
  2. 另由于在bspatch的代码中使用了err、errx函数,会导致出错时直接退出进程。为了在app中调用bspatch时不出现这种情况,所以使用自定义的err、errx函数替换系统err、errx函数,同时以返回值确定是否合并成功。

    	
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdarg.h>
    // #include <err.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <android/log.h>
    #include <jni.h>
    	
    #include "../bzip2/bzlib.c"
    #include "../bzip2/crctable.c"
    #include "../bzip2/compress.c"
    #include "../bzip2/decompress.c"
    #include "../bzip2/randtable.c"
    #include "../bzip2/blocksort.c"
    #include "../bzip2/huffman.c"
    	
    #include <android/log.h>
    	
    // #define vprintf(...) __android_log_print(ANDROID_LOG_DEBUG, "bspatch", __VA_ARGS__);
    	
    static int err(int retVal, char* fmt, ...) {
    va_list arglist;
    va_start(arglist, fmt);
    // vprintf(fmt, arglist);
    __android_log_print(ANDROID_LOG_DEBUG, "bspatch", fmt, arglist);
    va_end(arglist);
    return retVal;
    }
    	
    static int errx(int retVal, char* fmt, ...) {
    va_list arglist;
    va_start(arglist, fmt);
    // vprintf(fmt, arglist);
    __android_log_print(ANDROID_LOG_DEBUG, "bspatch", fmt, arglist);
    va_end(arglist);
    return retVal;
    }
    	
    static void cleanup(FILE* f, FILE*  cpf, FILE*  dpf, FILE*  epf, 
    BZFILE* cpfbz2, BZFILE* dpfbz2, BZFILE* epfbz2, 
    u_char* old, u_char* new) {
        
    // close file and then free;
    if (f != NULL) {
        fclose(f);
        free(f);
    }
    	
    if (cpf != NULL) {
        fclose(cpf);
        free(cpf);
    }
    	
    if (dpf != NULL) {
        fclose(dpf);
        free(dpf);
    }
    	
    if (epf != NULL) {
        fclose(epf);
        free(epf);
    }
    	
    if (cpfbz2 != NULL) {
        BZ2_bzReadClose(NULL, cpfbz2);
    }
        
    if (dpfbz2 != NULL) {
        BZ2_bzReadClose(NULL, dpfbz2);
    }
        
    if (epfbz2 != NULL) {
        BZ2_bzReadClose(NULL, epfbz2);
    }
    	
    if (old != NULL) {
        free(old);
    }
    	
    if (new != NULL) {
        free(new);
    }
    }
    	
    static off_t offtin(u_char *buf) {
    off_t y;
    	
    y = buf[7] & 0x7F;
    y = y * 256;
    y += buf[6];
    y = y * 256;
    y += buf[5];
    y = y * 256;
    y += buf[4];
    y = y * 256;
    y += buf[3];
    y = y * 256;
    y += buf[2];
    y = y * 256;
    y += buf[1];
    y = y * 256;
    y += buf[0];
    	
    if (buf[7] & 0x80)
        y = -y;
    	
    return y;
    }
    	
    static int patch(int argc, char *argv[]) {
    FILE *f, *cpf, *dpf, *epf;
    BZFILE *cpfbz2, *dpfbz2, *epfbz2;
    int cbz2err, dbz2err, ebz2err;
    int fd;
    ssize_t oldsize, newsize;
    ssize_t bzctrllen, bzdatalen;
    u_char header[32], buf[8];
    u_char *old, *new;
    off_t oldpos, newpos;
    off_t ctrl[3];
    off_t lenread;
    off_t i;
    	
    if (argc != 4) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return errx(1, "usage: %s oldfile newfile patchfile\n", argv[0]);
    }
    	
    /* Open patch file */
    if ((f = fopen(argv[3], "r")) == NULL) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fopen(%s)", argv[3]);
    }
            
    	
    /*
     File format:
     0	8	"BSDIFF40"
     8	8	X
     16	8	Y
     24	8	sizeof(newfile)
     32	X	bzip2(control block)
     32+X	Y	bzip2(diff block)
     32+X+Y	???	bzip2(extra block)
     with control block a set of triples (x,y,z) meaning "add x bytes
     from oldfile to x bytes from the diff block; copy y bytes from the
     extra block; seek forwards in oldfile by z bytes".
     */
    	
    /* Read header */
    if (fread(header, 1, 32, f) < 32) {
        if (feof(f)) {
            errx(1, "Corrupt patch\n");
        }
    	
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fread(%s)", argv[3]);
    }
    	
    /* Check for appropriate magic */
    if (memcmp(header, "BSDIFF40", 8) != 0) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return errx(1, "Corrupt patch\n");
    }
    	
    /* Read lengths from header */
    bzctrllen = offtin(header + 8);
    bzdatalen = offtin(header + 16);
    newsize = offtin(header + 24);
    if ((bzctrllen < 0) || (bzdatalen < 0) || (newsize < 0)) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return errx(1, "Corrupt patch\n");
    }
    	
    /* Close patch file and re-open it via libbzip2 at the right places */
    if (fclose(f)) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fclose(%s)", argv[3]);
    }
            
    if ((cpf = fopen(argv[3], "r")) == NULL) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fopen(%s)", argv[3]);
    }
    	
    if (fseeko(cpf, 32, SEEK_SET)) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fseeko(%s, %lld)", argv[3], (long long) 32);
    }
    	
    if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
    }
    	
    if ((dpf = fopen(argv[3], "r")) == NULL) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fopen(%s)", argv[3]);
    }
    if (fseeko(dpf, 32 + bzctrllen, SEEK_SET)) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fseeko(%s, %lld)", argv[3], (long long) (32 + bzctrllen));
    }
    if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
    }
    if ((epf = fopen(argv[3], "r")) == NULL) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fopen(%s)", argv[3]);
    }
    if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET)) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fseeko(%s, %lld)", argv[3],
            (long long) (32 + bzctrllen + bzdatalen));
    }
    if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
    }
    	
    if (((fd = open(argv[1], O_RDONLY, 0)) < 0)
        || ((oldsize = lseek(fd, 0, SEEK_END)) == -1)
        || ((old = malloc(oldsize + 1)) == NULL)
        || (lseek(fd, 0, SEEK_SET) != 0)
        || (read(fd, old, oldsize) != oldsize) || (close(fd) == -1)) {
            
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "%s", argv[1]);
    }
            
    if ((new = malloc(newsize + 1)) == NULL) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, NULL);
    }
    	
    oldpos = 0;
    newpos = 0;
    while (newpos < newsize) {
        /* Read control data */
        for (i = 0; i <= 2; i++) {
            lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
            if ((lenread < 8)
                || ((cbz2err != BZ_OK) && (cbz2err != BZ_STREAM_END))) {
                    cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
                    return errx(1, "Corrupt patch\n");
                }
            ctrl[i] = offtin(buf);
        };
    	
        /* Sanity-check */
        if (newpos + ctrl[0] > newsize) {
            cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
            return errx(1, "Corrupt patch\n");
        }
    	
        /* Read diff string */
        lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]);
        if ((lenread < ctrl[0])
            || ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END))) {
            cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
            return errx(1, "Corrupt patch\n");
        }
    	
        /* Add old data to diff string */
        for (i = 0; i < ctrl[0]; i++)
            if ((oldpos + i >= 0) && (oldpos + i < oldsize))
                new[newpos + i] += old[oldpos + i];
    	
        /* Adjust pointers */
        newpos += ctrl[0];
        oldpos += ctrl[0];
    	
        /* Sanity-check */
        if (newpos + ctrl[1] > newsize) {
            cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
            return errx(1, "Corrupt patch\n");
        }
    	
        /* Read extra string */
        lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]);
        if ((lenread < ctrl[1])
            || ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END))) {
            cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
            return errx(1, "Corrupt patch\n");
        }
    	
        /* Adjust pointers */
        newpos += ctrl[1];
        oldpos += ctrl[2];
    };
    	
    /* Clean up the bzip2 reads */
    BZ2_bzReadClose(&cbz2err, cpfbz2);
    BZ2_bzReadClose(&dbz2err, dpfbz2);
    BZ2_bzReadClose(&ebz2err, epfbz2);
    if (fclose(cpf) || fclose(dpf) || fclose(epf)) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "fclose(%s)", argv[3]);
    }
    	
    /* Write the new file */
    if (((fd = open(argv[2], O_CREAT | O_TRUNC | O_WRONLY, 0666)) < 0)
        || (write(fd, new, newsize) != newsize) || (close(fd) == -1)) {
        cleanup(f, cpf, dpf, epf, cpfbz2, dpfbz2, epfbz2, old, new);
        return err(1, "%s", argv[2]);
    }
    	
    free(new);
    free(old);
    	
    return 0;
    }