Android - JNI 开发你所需要知道的基础


从 JVM 角度,存在两种类型的代码:“Java”和“native”, native 一般指的是 c/c++,为了使 java 和 native 端能够进行交互,java 设计了 JNI(java native interface)。 JNI 允许java虚拟机(VM)内运行的java代码与C++、C++和汇编等其他编程语言编写的应用程序和库进行互操作。

虽然大部分情况下我们的软件完全可以由 java 来实现,但是某些场景下使用 native 代码更加适合,比如:

  • 代码效率:使用 native 代码的性能更高
  • 跨平台特性:标准Java类库不支持应用程序所需的依赖于平台的特性,或者希望用较低级别的语言(如汇编语言)实现一小部分时间关键型代码。

native 层使用 JNI 主要可以做到:

  • 创建、检查和更新Java对象(包括数组和字符串)。
  • 调用Java方法。
  • 加载类并获取类信息。

创建 android ndk 项目

使用 as 创建一个 native c++ 项目

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

文件结构如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到生成了一个 cpp 文件夹,里面有 CMakeLists.txt, native-lib.cpp,CMakeLists后面再讲,这里先来看一下 native-lib.cpp 和 java 代码。

public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary(“native-lib”);
}

public native String stringFromJNI();
}

#include <jni.h>
#include

extern “C” JNIEXPORT jstring JNICALL
Java_com_wangzhen_jnitutorial_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz) {
std::string hello = “Hello from C++”;
return env->NewStringUTF(hello.c_str());
}

可以看到在 MainActivity 中先定义了一个 native 方法,然后编译器在 cpp 文件中创建一个一个对应的方法Java_com_wangzhen_jnitutorial_MainActivity_stringFromJNI。 它的命名规则就是 Java_packageName_methodName。

接下来我们详细的解读一下 cpp 中的代码。

native 代码解读

extern “C”

在 c++ 中使用 c 代码

JNIEXPORT

宏定义:#define JNIEXPORT __attribute__ ((visibility ("default"))) 在 Linux/Unix/Mac os/Android 这种类 Unix 系统中,定义为__attribute__ ((visibility ("default")))

GCC 有个visibility属性, 该属性是说, 启用这个属性:

  • 当-fvisibility=hidden时,动态库中的函数默认是被隐藏的即 hidden。
  • 当-fvisibility=default时,动态库中的函数默认是可见的。

JNICALL

宏定义,在 Linux/Unix/Mac os/Android 这种类 Unix 系统中,它是个空的宏定义: #define JNICALL,所以在 android 上删除它也可以。 快捷生成 .h 代码

JNIEnv

  • JNIEnv类型实际上代表了Java环境,通过这个 JNIEnv* 指针,就可以对 Java 端的代码进行操作:
  • 调用 Java 函数
  • 操作 Java 对象
  • JNIEnv 的本质是一个与线程相关的结构体,里面存放了大量的 JNI 函数指针:

struct _JNIEnv {
/**

  • 定义了很多的函数指针
    */
    const struct JNINativeInterface
    functions;

#if defined(__cplusplus)
/// 通过类的名称(类的全名,这时候包名不是用.号,而是用/来区分的)来获取jclass
jclass FindClass(const char* name) { return functions->FindClass(this, name); }

}

JNIEnv 的结构图如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

JavaVM

  • JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个

  • JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv,同时 JNIEnv 具有线程相关性,也就是 B 线程无法使用 A 线程的 JNIEnv

JVM 的结构图如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

jobject thiz

这个 object 指向该 native 方法的 this 实例,比如我们在 MainActivity 调用的下面的 native 函数中打印一下 thiz 的 className:

#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR,“JNI”,VA_ARGS);

extern “C” JNIEXPORT jstring JNICALL
Java_com_wangzhen_jnitutorial_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
std::string hello = “Hello from C++”;
// 1. 获取 thiz 的 class,也就是 java 中的 Class 信息
jclass thisclazz = env->GetObjectClass(thiz);
// 2. 根据 Class 获取 getClass 方法的 methodID,第三个参数是签名(params)return
jmethodID mid_getClass = env->GetMethodID(thisclazz, “getClass”, “()Ljava/lang/Class;”);
// 3. 执行 getClass 方法,获得 Class 对象
jobject clazz_instance = env->CallObjectMethod(thiz, mid_getClass);
// 4. 获取 Class 实例
jclass clazz = env->GetObjectClass(clazz_instance);
// 5. 根据 class 的 methodID
jmethodID mid_getName = env->GetMethodID(clazz, “getName”, “()Ljava/lang/String;”);
// 6. 调用 getName 方法
jstring name = static_cast(env->CallObjectMethod(clazz_instance, mid_getName));
LOGE(“class name:%s”, env->GetStringUTFChars(name, 0));

return env->NewStringUTF(hello.c_str());
}

打印结果如下:

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值