博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
android studio 3.0 Ndk 开发- 利用增量更新进行 apk的覆盖安装
阅读量:5924 次
发布时间:2019-06-19

本文共 18335 字,大约阅读时间需要 61 分钟。

学习目的

  • 在android studio 3.0的 基础上同时生成多个so包,cmake的编写
  • 开发安卓程序进行ndk开发 自动生成 增量包 patch
  • 生成 合并的so 库进行patch文件的合并
  • 解决 引用三方so库 方法报红 的问题

演示

增量更新就是,app自动更新的时候不会把整个新版本的apk包下载下来 进行覆盖安装,而是将一个 新版本和老版本 进行比较 生成一个 patch包,把patch包下载下来 和当前版本进行合并。然后在进行覆盖安装。

为了演示 我们先创建一个项目。将相同包名 的两个不同版本的apk 放到assert 目录下。然后将这两个包拷贝到 指定文件夹下。在生成一个patch 包。

下过如下

就这样 我们把assets目录下的两个apk。拷贝到了文件夹下,并生成了一个patch包。

这个patch包就是 app-new 和app-old 中的区别的部分。

然后我们可以吧这个patch包放到服务器。这样用户在使用app的时候 只用下载patch包,然后和当前app进行合并。就可以覆盖安装了。

下面我们在看一下 合并包的效果。我们还在该手机上。直接安装目录下的app-old.apk,然后点击合成按钮。会在线程里合成一个新的apk。然后进行覆盖安装。

我们看到了。我们的app 进行了覆盖安装。

  • 注意:
    • 这个叫oldtonew.apk 并不是这个文件夹下的 patch 和 app-old.apk合成的。因为用户使用的时候并不一定手机里会有 老版本的apk安装包。
    • 高于7.0 版本 的系统 覆盖安装会有一些坑。可以查看这篇文章。 https://juejin.im/post/5a7163866fb9a01ca3259710

包的拆分

需要的工具

  • bzip2 : http://www.bzip.org/downloads.html 下载放一边

  • bsdiff : https://github.com/mendsley/bsdiff clone 一波

    有用的就两个文件 。ok了。下载完这些 准备完事儿了。开始写代码了。

如何生成多个so文件(先了解一下)

如果androidstudio 3.0 上还不会使用ndk 开发 请看这篇文章 https://juejin.im/post/5a72bc53f265da3e364197c0 看完这个基本了解个大概了。androidstudio 已经不用 编写 .mk 文件了。

我们想创建一个项目。生成一个 diff.so 和patch.so 两个包。怎么做呢

这样一目了然了。再看看 这三个CmakeList.txt 怎么写的呢

diff的 CmakeList.txtcmake_minimum_required(VERSION 3.4.1)add_library( # Specifies the name of the library.# 这里是你so的名字。刚才在 MainActivity里面要引用的             diff             # Sets the library as a shared library.             SHARED             # Provides a relative path to your source file(s).              #这里是刚才 创建的c++ 代码的名字             Diff.c            )find_library( # Sets the name of the path variable.              log-lib              # Specifies the name of the NDK library that              # you want CMake to locate.              log )target_link_libraries( # Specifies the target library.                       diff                       # Links the target library to the log library                       # included in the NDK.                       ${log-lib} )patch 的 CmakeList.txtcmake_minimum_required(VERSION 3.4.1)# include_directories(#             "${PROJECT_SOURCE_DIR}/..")add_library( # Specifies the name of the library.# 这里是你so的名字。刚才在 MainActivity里面要引用的             patch             # Sets the library as a shared library.             SHARED             # Provides a relative path to your source file(s).              #这里是刚才 创建的c++ 代码的名字             PatchUtils.c            )find_library( # Sets the name of the path variable.              log-lib              # Specifies the name of the NDK library that              # you want CMake to locate.              log )target_link_libraries( # Specifies the target library.                        patch                       # Links the target library to the log library                       # included in the NDK.                       ${log-lib} )被gradle 引用的 CmakeList.txtcmake_minimum_required(VERSION 3.4.1)#添加子目录,将会调用子目录中的CMakeLists.txtADD_SUBDIRECTORY(diff)ADD_SUBDIRECTORY(patch)复制代码

在看一下 我们的gradle文件

请注意一下路径。 如果我们的代码写的没有问题。直接 build ->make project

可以看到 我们生成了 diff.so 和 patch.so 文件夹。 现在如果我们在创建什么项目 直接使用这两个so文件。就可以进行 拆分 和合并了。

拆分,合并代码编写。

拆分 和 合并 要使用 刚才 下载的 bsdiff 文件。bsdiff 文件又依赖于 bzip2 的库

因为拆分 与 合并 基本类似。就展示 拆分的代码了。我会把所有代码放到github上去。因为这两个库在使用的时候有特别多的坑。

先看一下目录结构吧

把刚才下载的bzip2 包放到 cpp目录下。
注意 不是全部都放过来。

在diff目录下新建一些 拆分需要的代码。

注意 如果你不知道 sven_com_apkpatchserver_DiffUtils.h 这个文件是如何生成的,你需要先看这篇文章,不然下面的看不懂了https://juejin.im/post/5a72bc53f265da3e364197c0

新建一个 DiffUtils 的工具类

public class DiffUtils {    static {//导入libhello.so 文件。 这里面只写hello就可以        System.loadLibrary("diff");    }    /**     * native方法 比较路径为oldPath的apk与newPath的apk之间差异,并生成patch包,存储于patchPath     * 

* 返回:0,说明操作成功 * * @param oldApkPath 示例:/sdcard/old.apk * @param newApkPath 示例:/sdcard/new.apk * @param patchPath 示例:/sdcard/xx.patch * @return */ public static native String genDiff(String oldApkPath, String newApkPath, String patchPath);}复制代码

生成 他的 native 头文件 sven_com_apkpatchserver_DiffUtils.h

/* DO NOT EDIT THIS FILE - it is machine generated */#include 
/* Header for class sven_com_apkpatchserver_DiffUtils */#ifndef _Included_sven_com_apkpatchserver_DiffUtils#define _Included_sven_com_apkpatchserver_DiffUtils#ifdef __cplusplusextern "C" {#endif/* * Class: sven_com_apkpatchserver_DiffUtils * Method: genDiff * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I */JNIEXPORT jstring JNICALL Java_sven_com_apkpatchserver_DiffUtils_genDiff (JNIEnv *, jclass, jstring, jstring, jstring);#ifdef __cplusplus}#endif#endif复制代码

还记得 上文提到的github 中 有一个 bsdiff.c 的文件么 全部拷贝 放到diff文件夹下,这里更名为 diff.c 在这个代码的基础上进行编写 代码如下,特别多。请小心。下面全是c 语言的代码了。如果看不懂可以先从jni学起。

//// Created by mypro on 2018/1/28.///*- * Copyright 2003-2005 Colin Percival * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright *    notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright *    notice, this list of conditions and the following disclaimer in the *    documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */#include "sven_com_apkpatchserver_DiffUtils.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 
#include
#include
#include
#include
#include
#include
#define MIN(x, y) (((x)<(y)) ? (x) : (y))static void split(off_t *I, off_t *V, off_t start, off_t len, off_t h) { off_t i, j, k, x, tmp, jj, kk; if (len < 16) { for (k = start; k < start + len; k += j) { j = 1; x = V[I[k] + h]; for (i = 1; k + i < start + len; i++) { if (V[I[k + i] + h] < x) { x = V[I[k + i] + h]; j = 0; }; if (V[I[k + i] + h] == x) { tmp = I[k + j]; I[k + j] = I[k + i]; I[k + i] = tmp; j++; }; }; for (i = 0; i < j; i++) V[I[k + i]] = k + j - 1; if (j == 1) I[k] = -1; }; return; }; x = V[I[start + len / 2] + h]; jj = 0; kk = 0; for (i = start; i < start + len; i++) { if (V[I[i] + h] < x) jj++; if (V[I[i] + h] == x) kk++; }; jj += start; kk += jj; i = start; j = 0; k = 0; while (i < jj) { if (V[I[i] + h] < x) { i++; } else if (V[I[i] + h] == x) { tmp = I[i]; I[i] = I[jj + j]; I[jj + j] = tmp; j++; } else { tmp = I[i]; I[i] = I[kk + k]; I[kk + k] = tmp; k++; }; }; while (jj + j < kk) { if (V[I[jj + j] + h] == x) { j++; } else { tmp = I[jj + j]; I[jj + j] = I[kk + k]; I[kk + k] = tmp; k++; }; }; if (jj > start) split(I, V, start, jj - start, h); for (i = 0; i < kk - jj; i++) V[I[jj + i]] = kk - 1; if (jj == kk - 1) I[jj] = -1; if (start + len > kk) split(I, V, kk, start + len - kk, h);}static void qsufsort(off_t *I, off_t *V, u_char *old, off_t oldsize) { off_t buckets[256]; off_t i, h, len; for (i = 0; i < 256; i++) buckets[i] = 0; for (i = 0; i < oldsize; i++) buckets[old[i]]++; for (i = 1; i < 256; i++) buckets[i] += buckets[i - 1]; for (i = 255; i > 0; i--) buckets[i] = buckets[i - 1]; buckets[0] = 0; for (i = 0; i < oldsize; i++) I[++buckets[old[i]]] = i; I[0] = oldsize; for (i = 0; i < oldsize; i++) V[i] = buckets[old[i]]; V[oldsize] = 0; for (i = 1; i < 256; i++) if (buckets[i] == buckets[i - 1] + 1) I[buckets[i]] = -1; I[0] = -1; for (h = 1; I[0] != -(oldsize + 1); h += h) { len = 0; for (i = 0; i < oldsize + 1;) { if (I[i] < 0) { len -= I[i]; i -= I[i]; } else { if (len) I[i - len] = -len; len = V[I[i]] + 1 - i; split(I, V, i, len, h); i += len; len = 0; }; }; if (len) I[i - len] = -len; }; for (i = 0; i < oldsize + 1; i++) I[V[i]] = i;}static off_t matchlen(u_char *old, off_t oldsize, u_char *new, off_t newsize) { off_t i; for (i = 0; (i < oldsize) && (i < newsize); i++) if (old[i] != new[i]) break; return i;}static off_t search(off_t *I, u_char *old, off_t oldsize, u_char *new, off_t newsize, off_t st, off_t en, off_t *pos) { off_t x, y; if (en - st < 2) { x = matchlen(old + I[st], oldsize - I[st], new, newsize); y = matchlen(old + I[en], oldsize - I[en], new, newsize); if (x > y) { *pos = I[st]; return x; } else { *pos = I[en]; return y; } }; x = st + (en - st) / 2; if (memcmp(old + I[x], new, MIN(oldsize - I[x], newsize)) < 0) { return search(I, old, oldsize, new, newsize, x, en, pos); } else { return search(I, old, oldsize, new, newsize, st, x, pos); };}static void offtout(off_t x, u_char *buf) { off_t y; if (x < 0) y = -x; else y = x; buf[0] = y % 256; y -= buf[0]; y = y / 256; buf[1] = y % 256; y -= buf[1]; y = y / 256; buf[2] = y % 256; y -= buf[2]; y = y / 256; buf[3] = y % 256; y -= buf[3]; y = y / 256; buf[4] = y % 256; y -= buf[4]; y = y / 256; buf[5] = y % 256; y -= buf[5]; y = y / 256; buf[6] = y % 256; y -= buf[6]; y = y / 256; buf[7] = y % 256; if (x < 0) buf[7] |= 0x80;}int bsdiff_main(int argc, char *argv[]) { int fd; u_char *old, *new; off_t oldsize, newsize; off_t *I, *V; off_t scan, pos, len; off_t lastscan, lastpos, lastoffset; off_t oldscore, scsc; off_t s, Sf, lenf, Sb, lenb; off_t overlap, Ss, lens; off_t i; off_t dblen, eblen; u_char *db, *eb; u_char buf[8]; u_char header[32]; FILE *pf; BZFILE *pfbz2; int bz2err; if (argc != 4) errx(1, "usage: %s oldfile newfile patchfile\n", argv[0]); /* Allocate oldsize+1 bytes instead of oldsize bytes to ensure that we never try to malloc(0) and get a NULL pointer */ 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)) err(1, "%s", argv[1]); if (((I = malloc((oldsize + 1) * sizeof(off_t))) == NULL) || ((V = malloc((oldsize + 1) * sizeof(off_t))) == NULL)) err(1, NULL); qsufsort(I, V, old, oldsize); free(V); /* Allocate newsize+1 bytes instead of newsize bytes to ensure that we never try to malloc(0) and get a NULL pointer */ if (((fd = open(argv[2], O_RDONLY, 0)) < 0) || ((newsize = lseek(fd, 0, SEEK_END)) == -1) || ((new = malloc(newsize + 1)) == NULL) || (lseek(fd, 0, SEEK_SET) != 0) || (read(fd, new, newsize) != newsize) || (close(fd) == -1)) err(1, "%s", argv[2]); if (((db = malloc(newsize + 1)) == NULL) || ((eb = malloc(newsize + 1)) == NULL)) err(1, NULL); dblen = 0; eblen = 0; /* Create the patch file */ if ((pf = fopen(argv[3], "w")) == NULL) err(1, "%s", argv[3]); /* Header is 0 8 "BSDIFF40" 8 8 length of bzip2ed ctrl block 16 8 length of bzip2ed diff block 24 8 length of new file */ /* File is 0 32 Header 32 ?? Bzip2ed ctrl block ?? ?? Bzip2ed diff block ?? ?? Bzip2ed extra block */ memcpy(header, "BSDIFF40", 8); offtout(0, header + 8); offtout(0, header + 16); offtout(newsize, header + 24); if (fwrite(header, 32, 1, pf) != 1) err(1, "fwrite(%s)", argv[3]); /* Compute the differences, writing ctrl as we go */ if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); scan = 0; len = 0; lastscan = 0; lastpos = 0; lastoffset = 0; while (scan < newsize) { oldscore = 0; for (scsc = scan += len; scan < newsize; scan++) { len = search(I, old, oldsize, new + scan, newsize - scan, 0, oldsize, &pos); for (; scsc < scan + len; scsc++) if ((scsc + lastoffset < oldsize) && (old[scsc + lastoffset] == new[scsc])) oldscore++; if (((len == oldscore) && (len != 0)) || (len > oldscore + 8)) break; if ((scan + lastoffset < oldsize) && (old[scan + lastoffset] == new[scan])) oldscore--; }; if ((len != oldscore) || (scan == newsize)) { s = 0; Sf = 0; lenf = 0; for (i = 0; (lastscan + i < scan) && (lastpos + i < oldsize);) { if (old[lastpos + i] == new[lastscan + i]) s++; i++; if (s * 2 - i > Sf * 2 - lenf) { Sf = s; lenf = i; }; }; lenb = 0; if (scan < newsize) { s = 0; Sb = 0; for (i = 1; (scan >= lastscan + i) && (pos >= i); i++) { if (old[pos - i] == new[scan - i]) s++; if (s * 2 - i > Sb * 2 - lenb) { Sb = s; lenb = i; }; }; }; if (lastscan + lenf > scan - lenb) { overlap = (lastscan + lenf) - (scan - lenb); s = 0; Ss = 0; lens = 0; for (i = 0; i < overlap; i++) { if (new[lastscan + lenf - overlap + i] == old[lastpos + lenf - overlap + i]) s++; if (new[scan - lenb + i] == old[pos - lenb + i]) s--; if (s > Ss) { Ss = s; lens = i + 1; }; }; lenf += lens - overlap; lenb -= lens; }; for (i = 0; i < lenf; i++) db[dblen + i] = new[lastscan + i] - old[lastpos + i]; for (i = 0; i < (scan - lenb) - (lastscan + lenf); i++) eb[eblen + i] = new[lastscan + lenf + i]; dblen += lenf; eblen += (scan - lenb) - (lastscan + lenf); offtout(lenf, buf); BZ2_bzWrite(&bz2err, pfbz2, buf, 8); if (bz2err != BZ_OK) errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); offtout((scan - lenb) - (lastscan + lenf), buf); BZ2_bzWrite(&bz2err, pfbz2, buf, 8); if (bz2err != BZ_OK) errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); offtout((pos - lenb) - (lastpos + lenf), buf); BZ2_bzWrite(&bz2err, pfbz2, buf, 8); if (bz2err != BZ_OK) errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); lastscan = scan - lenb; lastpos = pos - lenb; lastoffset = pos - scan; }; }; BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); if (bz2err != BZ_OK) errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); /* Compute size of compressed ctrl data */ if ((len = ftello(pf)) == -1) err(1, "ftello"); offtout(len - 32, header + 8); /* Write compressed diff data */ if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); BZ2_bzWrite(&bz2err, pfbz2, db, dblen); if (bz2err != BZ_OK) errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); if (bz2err != BZ_OK) errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); /* Compute size of compressed diff data */ if ((newsize = ftello(pf)) == -1) err(1, "ftello"); offtout(newsize - len, header + 16); /* Write compressed extra data */ if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); BZ2_bzWrite(&bz2err, pfbz2, eb, eblen); if (bz2err != BZ_OK) errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); if (bz2err != BZ_OK) errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); /* Seek to the beginning, write the header, and close the file */ if (fseeko(pf, 0, SEEK_SET)) err(1, "fseeko"); if (fwrite(header, 32, 1, pf) != 1) err(1, "fwrite(%s)", argv[3]); if (fclose(pf)) err(1, "fclose"); /* Free the memory we used */ free(db); free(eb); free(I); free(old); free(new); return 0;}//JNI 调用JNIEXPORT jstring JNICALL Java_sven_com_apkpatchserver_DiffUtils_genDiff (JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr) { int argc = 4; char *oldfile = (char *) (*env)->GetStringUTFChars(env, oldfile_jstr, NULL); char *newfile = (char *) (*env)->GetStringUTFChars(env, newfile_jstr, NULL); char *patchfile = (char *) (*env)->GetStringUTFChars(env, patchfile_jstr, NULL);//参数(第一个参数无效) char *argv[4]; argv[0] = "bsdiff"; argv[1] = oldfile; argv[2] = newfile; argv[3] = patchfile;//这个方法是和新方法,他会根据 old.apk new.apk 进行拆分,然后生成 patch 放到指定文件夹下 bsdiff_main(argc, argv); (*env)->ReleaseStringUTFChars(env, oldfile_jstr, oldfile); (*env)->ReleaseStringUTFChars(env, newfile_jstr, newfile); (*env)->ReleaseStringUTFChars(env, patchfile_jstr, patchfile); return (*env)->NewStringUTF(env, "hello hello");}//void main(int argc, char **argv) {////}复制代码

到这里 拆分就完成了。 只需要在java代码里执行如下代码就行

findViewById(R.id.diff).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String s = DiffUtils.genDiff(Constants.OLD_APK_PATH, Constants.NEW_APK_PATH, Constants.PATCH_FILE);            }        });Constants.OLD_APK_PATH: old.apk 路径Constants.NEW_APK_PATH: new.apk 路径Constants.PATCH_FILE:   生成的patch 路径复制代码

到这里 拆分 就都讲完了。合并 的代码 大同小异这里就不赘述了。我会把代码放到github上去。

使用patch.so 进行apk 与patch的合并。

新建一个项目,假设这个项目就是我们公司的app了。把刚才生成好的 patch.so文件 拿过来 复制到这里

然后我们开始编写 PatchUtils的代码了

注意

  • 我们如果想完美的使用so文件。我们带么的 名字 和路径 都要和 生成so 项目的代码的路径一致
    不然的话 代码 引用上就会有问题。 这就是为什么我们引用三方so文件 无法运行或者 写的代码报红了

比如这样

引用成功的话。基本就完成了。 我们只需要在代码里 简单调用就可以了

class ApkUpdateTask extends AsyncTask
{ @Override protected Boolean doInBackground(Void... params) { try { //1.下载差分包 String oldfile = ApkUtils.getSourceApkPath(MainActivity.this, getPackageName()); //2.合并得到最新版本的APK文件 String newfile = Constants.OLD_2_NEW_APK; String patchfile = Constants.PATCH_FILE; PatchUtils.patch(oldfile, newfile, patchfile); } catch (Exception e) { e.printStackTrace(); return false; } return true; } @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); //3.安装 if (result) { Toast.makeText(MainActivity.this, "您正在进行无流量更新", Toast.LENGTH_SHORT).show(); ApkUtils.installApk(MainActivity.this, Constants.OLD_2_NEW_APK); } } }复制代码

到这里 我们就完成了apk 的 增量更新

覆盖安装的一些坑

  • 增量更新,在手机上安装的 apk。一定要是 release key 打包的,并且 v1 v2 对勾都要选中
  • 覆盖安装 要适配7.0 https://juejin.im/post/5a72bc53f265da3e364197c0
  • 覆盖安装不要忘记 这句代码
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);复制代码
  • 如果怎么都不行,可以查看 你的apk包是否有读写权限 可以这样
String[] command = {
"chmod", "777", Constants.OLD_2_NEW_APK}; ProcessBuilder builder = new ProcessBuilder(command); try { builder.start(); } catch (IOException e) { e.printStackTrace(); }复制代码

不懂得可以查看代码,最直观 https://github.com/wanghao200906/NdkBsDiffPractise

转载地址:http://loavx.baihongyu.com/

你可能感兴趣的文章
记一次JavaScript API练习题
查看>>
Node.js中的事件循环(Event Loop),计时器(Timers)以及process.nextTick()
查看>>
nodejs配置微信小程序本地服务器(二):利用ws模块创建基于ssl证书的WebSocket服务器...
查看>>
创建git项目(vue),使用webstorm上传
查看>>
nginx的web-server的基本使用(二)
查看>>
基于Helm和Operator的K8S应用管理
查看>>
android精美时钟界面、游戏新闻客户端、美食APP、音乐助手等源码
查看>>
浅谈Vue模板的那些事儿
查看>>
[翻译] Async/Await 使你的代码更简洁
查看>>
NPM酷库:commander,命令行参数处理框架
查看>>
ES6时代,你真的会克隆对象吗?
查看>>
使用PHPExcel读写excel
查看>>
spring security系列二:过滤器机制
查看>>
Flask-restful 用法及自定义参数错误信息
查看>>
10个Python面试常问的问题
查看>>
AI重新定义Web安全
查看>>
C语言学习入门01
查看>>
证书类型原理及转换方式
查看>>
2017-09-17 前端日报
查看>>
面向对象编程
查看>>