JNI
What is JNI
JNI是Java Native Interface的缩写,主要是提供了一系列API,让你能在其它语言中写Java。
What JNI can bring us
JNI最大的好处就是,额,Java你懂的,跑在JVM里面,虽然有着一处编译,到处运行的优势(,方便啊),但是它的效率。。。至少相对于c和C艹来说,比较低下,而且,正是由于这个能一处编译,到处运行的原因,Java极容易被反编译。Java中一般用的加密方式就是混淆了,然而其实并没有太大的作用。你还是开源吧。。。因为不开源也会被反编译的。。。
PS:并没有贬低Java的意思,个人还是挺喜欢用Java的
然后,相反的,JNI由于是用C或者C艹写,效率很高,可以用来处理一些底层的东西,比如解码或者TCP/IP有关的。编译过后跟C(艹)编译的结果是一样的,在Android里面是.so文件。然后,因为是C(艹),所以需要针对不同的平台,不同的处理器进行编译。所以,使用JNI,你需要在编译的时候生成许多个平台的版本,否则,Java跨平台这个优点相当于直接被废了。还有就是JNI的调试会非常蛋疼。
How to use JNI
Hello World
我用的Android Studio,有各种一键生成(x),要看手撸的话,网上应该能搜到,本文主要是介绍那些遇到的坑。
AS生成的main.cpp长这样:
#include <jni.h>
#include <string>
extern "C"
jstring
Java_com_helloworld_jnidemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
分析一下:
- 几个include,其中jni.h是JNI必需的,其他的可以添加C(艹)中的,比如
stdio.h
什么的 extern C
,这个我也不是特别理解,自我修养里面说是声明为C语言,然而删掉过后就炸了jstring
,返回值类型Java_com_helloworld_jnidemo_MainActivity_stringFromJNI
,Java_包名_类名_方法名,这是函数声明的规范JNIEnv *env, jobject /* this */
,JNIEnv里面有巨量的函数,后面就知道了,jobject就是thisstd::string hello = "Hello from C++";
,C艹env->NewStringUTF(hello.c_str())
,这儿就出现了env的其中一个函数,这个函数会经常在后面用到,char*转String,没错,他们不一样!
然后我自己写了一个HelloWordl和求和的函数:
extern "C"
jstring
Java_com_helloworld_jnidemo_MainActivity_helloworld(JNIEnv *env, jobject /* this */) {
return env->NewStringUTF("Hello World");
}
extern "C"
jint
Java_com_helloworld_jnidemo_MainActivity_sum(JNIEnv *env, jobject /* this */, jint a, jint b) {
return a + b;
}
Java中该这样写:
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
public native String helloworld();
其中,System.loadLibrary("native-lib");
这句是加载库,static语句块中的内容只会被执行一次。native-lib
为库的名称,声明方法时使用native
关键字。
CMakeLists.txt:
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp )
find_library(
log-lib
log )
target_link_libraries(
native-lib
${log-lib} )
其中,native-lib
可以随便改,对应System.loadLibrary("native-lib");
里面的。但是有个玄学问题,不能改成test。。。被坑了。。。
src/main/cpp/native-lib.cpp
里面的文件名可以随便改,只要与你写的文件对应。
好的,JNI入门了的样子。
Learn More
写出来了Hello World,该继续深入研究了。在继续之前,我们还应该了解一下jstring
,jint
这些是啥,这儿有个表,展示了JNI和Java里面的属性的关系:
- jint –> int
- jbyte –> byte
- jshort –> short
- jlong –> long
- jfloat –> float
- jdouble –> double
- jchar –> char
- jboolean –> boolean
- jclass –> java.lang.Class
- jstring –> java.lang.String
- jarray –> Array
- jxxxArray –> xxx[]
- jobject –> Object
- …
注意最后一个,一切皆为对象。
使用JNI,你应该实现Java的基本功能:
- new对象
- call方法
- 获取属性
学会了以上三个操作,就可以用JNI代替Java中70%以上的操作了。让我们一个一个来看。
new对象 & Call方法
没错,new对象就是通过调用构造方法实现的。
extern "C"
jobject
Java_com_helloworld_asdf_MainActivity_newObject(JNIEnv *env, jobject /* this */) {
jclass clazz = env->FindClass("java/lang/Object");
jmethodID init = env->GetMethodID(clazz, "<init>", "()V");
jobject result = env->NewObject(clazz, init);
return result;
}
步骤:
- 找到class,用/代替.,
FindClass
的参数为所在包名 - 找到对应构造方法
- 调用
newObject
,传入class和构造方法id。
再看看一般的方法调用:
extern "C"
jint
Java_com_helloworld_asdf_MainActivity_stringLen(JNIEnv *env, jobject /* this */, jstring str) {
jclass clazz = env->GetObjectClass(str);
jmethodID lenId = env->GetMethodID(clazz, "length", "()I");
jint result = env->CallIntMethod(str, lenId);
return result;
}
GetObjectClass
可以直接从object中拿到class。
调用方法用CallxxxMethod
,xxx为返回值类型。CallxxxMethod的第一个参数是jobject,不是jclass,这个与NewObject
不同。前面有jxxxArray,然而并没有CallxxxArrayMethod
哎,该怎么办呢?一切都是对象,用CallObjectMethod
再强转就可以了。
比如:
extern "C"
jstring
Java_com_helloworld_asdf_MainActivity_toString(JNIEnv *env, jobject /* this */, jobject object) {
jclass clazz = env->GetObjectClass(object);
jmethodID lenId = env->GetMethodID(clazz, "toString", "()Ljava/lang/String;");
jstring result = (jstring) env->CallObjectMethod(object, lenId);
return result;
}
方法签名:
简直有毒,反人类
- construction –>
- void –> V
- boolean –> Z
- byte –> B
- char –> C
- short –> S
- int –> I
- long –> J
- float –> F
- double –> D
- x[] –> [x
- x[][] –> [[x
- java.lang.String –> L/java/lang/String;
总结一下:
- 每个基本类型都有对应的签名,基本法
- 数组用[
- 构造方法规定为
- 其它类为L类;,注意:分号不能丢,分号不能丢,分号不能丢
获取Field
extern "C"
jint
Java_com_helloworld_asdf_MainActivity_getX(JNIEnv *env, jobject /* this */, jobject test) {
jclass clazz = env->GetObjectClass(test);
jfieldID lenId = env->GetFieldID(clazz, "x", "I");
jint result = env->GetIntField(test, lenId);
return result;
}
static
static
的属性和方法与普通的有一些区别,例如CallStaticObjectMethod
的第一个参数是jclass
。这些在熟悉了上面的操作过后都没有太大的问题了。
多维数组的处理
- 思路:一维一维处理
env->GetArrayLength()
拿到第一维数组长度- 在
for
循环中用env->GetByteArrayRegion()
将下一维的元素取出 - 如果是多维,重复操作
分享一点经验
- 一切都是object
- Java里的String和C(艹)里的是不一样的,要记得
NewStringUTF
,被坑过 L/java/lang/String;
java/util/List
和java/util/ArrayList
是不一样的。。。要看清方法的参数。。。