目录
Android NDK开发两部曲(一)之初识篇(JNI通识与NDK配置)
Android NDK开发两部曲(二)之应用篇(增量更新也就那样)
前言
之前发过一篇在
Eclipse上关于JNI和NDK的博客Android JNI与Android NDK扫盲, 里面只是笼统地介绍了一下, 文章本身并没有深入学习的意思. 那么现在我们就开始在这篇文章的基础上, 在Android Studio上面去详细学习NDK开发吧.
本文参考鸿洋大神的Android 增量更新完全解析 是增量不是热修复, 学习过程中. 发现Android Studio对NDK的支持越来越友好, 并不需要像以前那样一个个地去新建文件.太多的原理就不重复了, 主要演示一下新版本下的操作变化
建议先看看鸿洋大神的博客再回来看这里
如果还没有NDK环境, 那么请参考Android NDK开发三部曲(一)之配置篇(AS对NDK友好集成)
新建项目
这里我的项目就叫
BadPatchDemo

同时勾选Include C++ support
如果没有安装
cmake(一个跨平台的C/C++项目编译配置工具), 那么AS会提示安装cmake, 按照提示完成安装就可以了.

NDK开发
我们发现
app/src/main/多了一个cpp的文件夹, 这里就是存放我们源文件的目录.
添加第三方代码
前提是你按照鸿洋大神的博客里面那样, 安装了
bsdiff. 因为生成差分包的工作应该是在服务器, 所以我们就只集成bspatch来对差分包跟旧安装包合并.
大家可以下载代码, 这里是筛选了某些源代码的
链接: https://pan.baidu.com/s/1dEHNhx3 密码: abnf
解压后我们直接把bspatch.c和bzip拷贝进去cpp文件夹, 然后切换到project视图编辑app/CMakeLists.txt
然后加入需要编译的代码
src/main/cpp/bzip2/blocksort.csrc/main/cpp/bzip2/bzip2.csrc/main/cpp/bzip2/bzip2recover.csrc/main/cpp/bzip2/bzlib.csrc/main/cpp/bzip2/compress.csrc/main/cpp/bzip2/crctable.csrc/main/cpp/bzip2/decompress.csrc/main/cpp/bzip2/huffman.csrc/main/cpp/bzip2/randtable.csrc/main/cpp/bspatch.c

最后点击同步按钮
暴露出接口
我们在cpp目录邮件下新建C/C++ Header File->bspatch.h, 加入下面代码
#ifndef BSDPATCHDEMO_BSPATCH_H
#define BSDPATCHDEMO_BSPATCH_H
#endif
//BSDPATCHDEMO_BSPATCH_Hint
doPatch(int argc, char *argv[]);
其中doPatch是bspatch.c的main函数改名而来, 因为我们在命令行下面执行命令的时候, 其实就是调用命令行工具的main函数. 后面的参数会作为字符串的形式传入到main函数中. 其中argc表示参数的个数,argv存着参数的具体内容
例如:
mv test /root/
argc = 3
argv[0] = mv
argv[1] = test
argv[2] = /root/
编写native代码
我们新建BsdUtil
package com.example.bsdpatchdemo;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import java.io.File;
/**
* Created by August on 2017/3/18.
*/
public class BsdUtil {
static {
System.loadLibrary("native-lib");
}
/**
* 原生打补丁方法
*
* @param oldApk
* @param patchFile
* @param newApk
*/
public static native void patch(String oldApk, String patchFile, String newApk);
/**
* 获取本身APK的路径
*
* @param context
* @return
*/
public static String getOriginalApk(Context context) {
return context.getApplicationInfo().sourceDir;
}
/**
* 安装指定apk
*
* @param context
* @param apk
*/
public static void install(Context context, String apk) {
Intent intent = new Intent();
intent.setDataAndType(Uri.fromFile(new File(apk)),
"application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
我们看到patch爆红, 这时候我们点一下提示, 然后让as帮我们创建方法. 然后我们就直接调到对应的C/C++文件里面了
现在我们需要引入的刚刚暴露方法的头文件bspatch.h
#include <jni.h>
#include <string>
#include "bspatch.h"
JNIEXPORT void JNICALL
Java_com_example_bsdpatchdemo_BsdUtil_patch(JNIEnv *env, jclass type, jstring oldApk_,
jstring patchFile_, jstring newApk_) {
const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
const char *patchFile = env->GetStringUTFChars(patchFile_, 0);
const char *newApk = env->GetStringUTFChars(newApk_, 0);
// TODO
char *argv[] = {(char *) "bspatch", (char *) oldApk, (char *) newApk, (char *) patchFile};
doPatch(4, argv);
env->ReleaseStringUTFChars(oldApk_, oldApk);
env->ReleaseStringUTFChars(patchFile_, patchFile);
env->ReleaseStringUTFChars(newApk_, newApk);
}
编写调用代码
activity_main
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="patch"
android:text="old app"
android:textSize="20sp" />
MainActivity
package com.example.bsdpatchdemo;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import java.io.File;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void patch(View v) {
String oldApk = BsdUtil.getOriginalApk(this);
String newApk = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "newApk.apk";
String patchFile = "/storage/sdcard0/p.patch";
BsdUtil.patch(oldApk, patchFile, newApk);
BsdUtil.install(this, newApk);
}
}
涉及到读写权限的, 不要忘了在AndroidManifest中加入
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
编译
Build->Build APK,编译的时候我这边爆了一个链接错误, 估计是C++跟C语言混编配置不当造成的.
解决方法
把
native-lib.cpp改成native-lib.c同时把
CMakeLists.txt里面的native-lib.cpp改成native-lib.c然后把
native-lib.c中面向对象的写法改回C语言面向过程的写法
#include <jni.h>
#include "bspatch.h"
JNIEXPORT void JNICALL
Java_com_example_bsdpatchdemo_BsdUtil_patch(JNIEnv *env, jclass type, jstring oldApk_,
jstring patchFile_, jstring newApk_) {
const char *oldApk = (*env)->GetStringUTFChars(env, oldApk_, 0);
const char *patchFile = (*env)->GetStringUTFChars(env, patchFile_, 0);
const char *newApk = (*env)->GetStringUTFChars(env, newApk_, 0);
char *argv[] = {(char *) "bspatch", (char *) oldApk, (char *) newApk, (char *) patchFile};
doPatch(4, argv);
(*env)->ReleaseStringUTFChars(env, oldApk_, oldApk);
(*env)->ReleaseStringUTFChars(env, patchFile_, patchFile);
(*env)->ReleaseStringUTFChars(env, newApk_, newApk);
}
再次编译APK
测试
现在我们得到的是旧的APK改名为
old.apk, 给手机安装上然后我们把activity_main中的
old app改成new app, 然后再build出新的apk, 改名为new.apk.有了两个apk我们可以生成差分包
yubindeMacBook-Pro:Desktop August$ bsdiff old.apk new.apk p.patch
yubindeMacBook-Pro:Desktop August$ adb push p.patch /storage/sdcard0/p.patch
[100%] /storage/sdcard0/p.patch

使用热修复虽然不需要安装, 但几乎市面上的热修复框架都是基于运行时修复的, 所以存在很大兼容性问题. 但是增量更新是通过合并差分包来更新的, 只要差分包是对的, 那么几乎不会存在兼容性问题. 很多时候应用只需要修改代码, 但是资源文件是不变的. 这时候用增量更新, 就能减去资源文件的大小, 从而达到省流量更新.
说在最后
其实本博文主要目的是说明新版的Android Studio的NDK开发方式, 增量更新只是作为其中一个应用分析. 其实这个过程也成为
移植. 大家也可以去找一些C库, 图片压缩 声音视频变换之类的, 然后移植到Android上, 也是挺有趣的.