提供一种Java字节码调用C/C++的解决方案,JNI描述的是一种技术 java
Android NDK 是一组容许您将 C 或 C++(“原生代码”)嵌入到 Android 应用中的工具,NDK描述的是工具集。 可以在 Android 应用中使用原生代码对于想执行如下一项或多项操做的开发者特别有用:android
这里已有现成的文章就引用一下别人的了 ,讲的很仔细 关于JNI环境配置c++
当Java层调用navtie函数时,会在JNI库中根据函数名查找对应的JNI函数。若是没找到,会报错。若是找到了,则会在native函数与JNI函数之间创建关联关系,其实就是保存JNI函数的函数指针。下次再调用native函数,就能够直接使用这个函数指针。git
JNI函数名格式(包名里面的”.”须要改成”_”): Java_ + 包名(com_example_auto_jnitest)+ 类名(_MainActivity) + 函数名(_stringFromJNI)github
静态注册缺点:数组
静态注册例子markdown
类 JNITest 包名:com.hqk.jnitestone架构
package com.hqk.jnitestone;
public class JNITest {
static {
System.loadLibrary("native-lib");
}
public static native String sayHello();
}
复制代码
对应c++代码 ,cpp 类名为:native-lib.cpp函数
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL Java_com_hqk_jnitestone_JNITest_sayHello(JNIEnv *env, jclass clazz) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
复制代码
注:工具
static {
System.loadLibrary("native-lib");
}
复制代码
经过提供一个函数映射表,注册给JVM虚拟机,这样JVM就能够用函数映射表来调用相应的函数,就没必要经过函数名来查找须要调用的函数。
Java与JNI经过JNINativeMethod的结构来创建函数映射表,它在jni.h头文件中定义,其结构内容以下:
typedef struct {
const char* name; // 对应交互java 类对应的方法名
const char* signature; //对应交互方法的函数签名 (参考本文1.4.3)
void* fnPtr; //对应交互cpp方法的 指针函数 (指向对应函数)
} JNINativeMethod;
复制代码
一、建立映射表后,调用RegisterNatives函数将映射表注册给JVM; 二、当Java层经过System.loadLibrary加载JNI库时,会在库中查JNI_OnLoad函数。可将JNI_OnLoad视为JNI库的入口函数,须要在这里完成全部函数映射和动态注册工做,及其余一些初始化工做。
概念说完了,实际操做一下
JAVA类 JNITest 包名:com.hqk.jnitestone
package com.hqk.jnitestone;
public class JNITest {
static {
System.loadLibrary("native-lib");
}
public static native String sayHello();
public static native String sayHello2();
}
复制代码
注意:这里多了一个 sayHello2 方法,方法返回值为: String
CPP类名 native-lib.cpp 对应代码
#include <jni.h>
#include <string>
#include <android/log.h>
extern "C"
JNIEXPORT jstring JNICALL Java_com_hqk_jnitestone_JNITest_sayHello(JNIEnv *env, jclass clazz) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
//cpp 交互方法
jstring sayHello2(JNIEnv *env, jobject thiz) {
std::string hello = "Hello,我是动态注册成功的!";
return env->NewStringUTF(hello.c_str());
}
// 动态注册 函数 结构数组
static const JNINativeMethod gMethods[] = {
{"sayHello2", //对应java交互类的方法名
"()Ljava/lang/String;", //对应方法名的函数签名 (参考本文1.4.3)
(jstring *) sayHello2 //对应 cpp交互类的指针函数
}
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
__android_log_print(ANDROID_LOG_INFO, "native", "Jni_OnLoad");
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) //从JavaVM获取JNIEnv,通常使用1.4的版本
return -1;
//注意:这里 FindClass 必需要和交互类的 包名对应上,并换成[/]符号
jclass clazz = env->FindClass("com/hqk/jnitestone/JNITest");
if (!clazz) {
__android_log_print(ANDROID_LOG_INFO, "native",
"cannot get class: com/hqk/jnitestone/JNITest");
return -1;
}
if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
__android_log_print(ANDROID_LOG_INFO, "native", "register native method failed!\n");
return -1;
}
return JNI_VERSION_1_4;
}
复制代码
从上面代码能够看出
一、CPP类里面 建立了 JNI_OnLoad 方法--》 该方法是在 初始化的时候执行,相似于 activity 的onCreate, 核心代码,就是经过Find找到对应Java类,最后经过RegisterNatives 实现动态注册 二、CPP类里面 新增了JNINativeMethod 类型的 结构数组 ; 三、CPP类里面 新增了 sayHello2 交互方法 四、Java类里面通用新增了 sayHello2方法
除了Class、String、Throwable和基本数据类型的数组外,其他全部Java对象的数据类型在JNI中都用jobject表示。Java中的String也是引用类型,可是因为使用频率较高,因此在JNI中单首创建了一个jstring类型。
例如,二维整型数组就是指向一位数组的数组,其声明使用方式以下:
//得到一维数组的类引用,即jintArray类型
jclass intArrayClass = env->FindClass("[I");
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
jobjectArray obejctIntArray = env->NewObjectArray(length ,intArrayClass , NULL);
复制代码
因为Java支持函数重载,所以仅仅根据函数名是无法找到对应的JNI函数。为了解决这个问题,JNI将参数类型和返回值类型做为函数的签名信息。
(参数1类型字符…)返回值类型字符
3.JNI经常使用的数据类型及对应字符:
注意: 在C++建立的子线程中获取JNIEnv,要经过调用JavaVM的AttachCurrentThread函数得到。在子线程退出时,要调用JavaVM的DetachCurrentThread函数来释放对应的资源,不然会出错。
JNIEnv 做用:
CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,而后再调用底层的编译, 在Android Studio 2.2 以后支持Cmake编译。
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
复制代码
语法:add_library(libname SHARED | STATIC | MODULE [source])
将一组源文件 source 编译出一个库文件,并保存为 libname.so (lib 前缀是生成文件时 CMake自动添加上去的)。其中有三种库文件类型,不写的话,默认为 STATIC;
#将compress.c 编译成 libcompress.so 的共享库
add_library(compress SHARED compress.c)
复制代码
语法:target_link_libraries(target library <debug | optimized> library2…)
这个指令能够用来为 target 添加须要的连接的共享库,一样也能够用于为本身编写的共享库添加共享库连接。如:
#指定 compress 工程须要用到 libjpeg 库和 log 库
target_link_libraries(compress libjpeg ${log-lib})
复制代码
语法:find_library( name1 path1 path2 ...)
VAR 变量表示找到的库全路径,包含库文件名 。例如:
find_library(libX X11 /usr/lib)
find_library(log-lib log) #路径为空,应该是查找系统环境变量路径
复制代码
参考文献:更多关于Cmake的详细使用
ABI(Application binary interface)应用程序二进制接口。不一样的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,因此一样的程序代码为了兼容多个不一样的CPU,须要为不一样的 ABI 构建不一样的库文件。固然对于CPU来讲,不一样的架构并不意味着必定互不兼容。
根据以上的兼容总结,咱们还能够获得一些规律:
gitHub地址:点击下载