为什么使用JNI

我们知道Java语言是一个跨平台的语音,而C/C++这是一个对平台有依赖的语言,所以不是万不得已个人并不推荐在项目中引入项目中,一旦引入JNI那么我们将失去跨平台的特性,对特定的平台会产生依赖,并且NE是开发中最难解决的问题,一旦抛出Native 层的错误将会增加我们定位问题的难度。那么我们一般什么时候才需要使用到JNI技术呢?

  • 我们在编程中需要用到某个功能,这个功能之前已经用C/C++等本地语言进行编写了,这时候为了避免重复造轮子,所以使用JNI在Java层调用这些已经封装好的方法。
  • 有些功能需要非常严格的时效性,或者某些方法需要和特定的硬件进行交互,比如某些缓存的操作等。这种情况下就需要使用到JNI了。
  • 为了应用的安全性,会将一些复杂的逻辑和算法通过本地代码来实现,然后封装成so动态库文件,并提供Java接口供应用层调用,从而防止被反编译。

JNI在整个开发中所扮演的角色

JNI顾名思义Java Native Interface,是Java和Native层的一个接口,但是要注意的是这是一个双向接口,也就是说不但运行在Java层调用Native层,也允许在Native层调用Java层。

使用Android Studio开发JNI

在Android Studio中进行JNI开发环境的搭建,请查看我之前的一篇博客《Android 进阶之 NDK 开发》这里就不重复介绍了。

最简单的例子介绍

public class MainActivity extends AppCompatActivity {
// [1]
private native String getStringFromNative();
// [2]
static {
System.loadLibrary("testndk");
}

private TextView mTextView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.demojni);
mTextView.setText(getStringFromNative());
}
}
#include <jni.h>
JNIEXPORT jstring JNICALL
//[3]
Java_com_idealist_myapplication_MainActivity_getStringFromNative(JNIEnv *env, jobject instance) {
return (*env)->NewStringUTF(env, "Hello JNI");
}

下面将会上面标注的三点进行讲解,给大家一个简单的入门,这个例子中通过调用getStringFromNative这个native方法返回一个”Hello JNI”,并将其显示到TextView上。
[1]. 所有的Native方法在定义的时候都需要添加native标识。
[2]. 在Linux 环境下编译后会生成一个”lib”+模块名+“.so”的库文件,在这个例子中模块名为testndk所以会生成libtestndk.so,而在Window环境下会生成一个”lib”+模块名+“.dll”的库文件,在需要使用这些库文件里面的方法的时候需要使用loadLibrary将库引入。
[3]. Java_com_idealist_myapplication_MainActivity_getStringFromNative这个方法是自动生成的,它的生成规律是Java_包名将所有的分隔符由点换成下划线_native方法声明所处的类库_native方法名。
我们接下来看下这个jni方法的参数(JNIEnv *env, jobject instance) 第一个参数是一个方法指针指向JNI提供的方法,如果下图所示:

第二个参数要视Java层上声明的方法为静态方法还是非静态方法,如果是静态方法则传入的是jclass表示指向某个类,如果是动态方法者传入的是jobject类型参数,指向的是调用的对象。
JNIEXPORT 和 JNICALL 宏定义用于确保外部能够从native库中看到这个方法。

如果上面看不是很懂,没事可以在接下来的博客的例子中慢慢体会。

Contents
  1. 1. 为什么使用JNI
  2. 2. JNI在整个开发中所扮演的角色
  3. 3. 使用Android Studio开发JNI
  4. 4. 最简单的例子介绍