这是这个系列的第二篇,第一篇介绍了如何配置。这一篇介绍Java与C如何相互介绍。java
没有配置过的能够去看看Android JNI开发系列之配置android
首先介绍的就是Java如何调用C,而C调用Java核心使用的就是反射,下面会依次介绍。git
第一篇中有个简单的例子,就是使用Java调用C,调用一个无参的native函数,并返回一个String,下面接着说点更多的状况:github
由于Java和C的基本类型也有些许区别,而在这二者之间还有一个jni的类型做为桥梁链接转换类型,有一张图特别好,一看就清楚了,借了一下这位做者文章中的图,表示感谢。数组
下边对于数据的处理就是基于这些类型去处理的。bash
这个也是坑了我这个萌新很多,体会到其实Java的垃圾回收机制仍是很方便的。函数
其中在c中字符串的拼接主要就是使用strcat
方法,导入#include<string.h>
包。post
仍是老样子,先定义一个native方法,对于配置都是在上一篇的基础上的:学习
public class Hello {
static {
System.loadLibrary("Hello");
}
//传入一个字符串,拼接一段字符串后返回
public native String sayHello(String msg);
}
复制代码
接着在Hello.c文件中写这个方法,这里有两种方法去写这个方法,第一种是手动本身写,也有点技巧:ui
JNIEnv *env, jobject instance
,而后第三个参数开始就是在Java中定义的方法的参数,这里传入了一个String,在这里的就改成jstring msg
,方法以下:jstring Java_net_arvin_androidstudy_jni_Hello_sayHello(JNIEnv *env, jobject instance,
jstring msg) {
// implement code...
}
复制代码
还有一种方法就是使用javah命令,处理.java文件就能获得定义的.h文件;方法就是在该项目的java目录下,使用命令javah 类的彻底限定名
,在我这个项目里就是: javah net.arvin.androidstudy.jni.Hello
这样在java目录下就有一个net_arvin_androidstudy_jni_Hello.h
文件,打开能够看到这个方法:
JNIEXPORT jstring JNICALL Java_net_arvin_androidstudy_jni_Hello_sayHello
(JNIEnv *, jobject, jstring);
复制代码
其中JNIEXPORT和JNICALL关键字均可以去掉的,去掉后就和上边的方法同样了,而后本身去把参数的名字补充上便可。
最后对于字符串的拼接,没啥好说的,我这里提供一种方式:
jstring Java_net_arvin_androidstudy_jni_Hello_sayHello(JNIEnv *env, jobject instance,
jstring msg) {
char *fromJava = (char *) (*env)->GetStringUTFChars(env, msg, JNI_FALSE);
char *fromC = " add I am from C~";
char *result = (char *) malloc(strlen(fromJava) + strlen(fromC) + 1);
strcpy(result, fromJava);
strcat(result, fromC);
return (*env)->NewStringUTF(env, result);
}
复制代码
最后就是去调用,这就简单了。
Hello jni = new Hello();
String result = jni.sayHello("I am from Java");
Log.d(TAG, result);
复制代码
有了上文的介绍,这个比较就比较简单,核心就是使用strcmp
方法,Java代码以下:
public class Hello {
static {
System.loadLibrary("Hello");
}
//若是是c中要求的就返回200,不然就返回400
public native int checkStr(String str);
}
复制代码
c代码以下:
jint Java_net_arvin_androidstudy_jni_Hello_checkStr
(JNIEnv *env, jobject instance, jstring jstr) {
char *input = (char *) (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
char *real = "123456";
return strcmp(input, real) == 0 ? 200 : 400;
}
复制代码
这里就不接着介绍其余的处理方法了,须要时能够本身搜一下。
一样有了上文的基础,Java代码以下:
public class Hello {
static {
System.loadLibrary("Hello");
}
public native void increaseArray(int[] arr);
}
复制代码
C代码以下:
void Java_net_arvin_androidstudy_jni_Hello_increaseArray
(JNIEnv *env, jobject instance, jintArray arr) {
jsize length = (*env)->GetArrayLength(env, arr);
jint *elements = (*env)->GetIntArrayElements(env, arr, JNI_FALSE);
for (int i = 0; i < length; i++) {
elements[i] += 10;
}
(*env)->ReleaseIntArrayElements(env, arr, elements, 0);
}
复制代码
能够看到:
到这里Java调用C的介绍就到这里,方法基本介绍了,可是如何更好的运用还需努力实践。
上文中说到这个操做,主要是利用反射,这样就能调用Java代码了。
对于配置都不说了,也直接上代码,主要的细节都是在反射那里。
先来一个C调用Java无参无返回值的函数,Java代码以下:
public class CallJava {
static {
System.loadLibrary("Hello");
}
private static final String TAG = "CallJava";
//调用无参,无返回函数
public native void callVoid();
public void hello() {
Log.d(TAG, "Java的hello方法");
}
}
复制代码
能够看到这里换了一个类了,可是没有影响,以后会介绍这一块知识。
C代码:
//调用public void hello()方法
void Java_net_arvin_androidstudy_jni_CallJava_callVoid
(JNIEnv *env, jobject instance) {
jclass clazz = (*env)->FindClass(env, "net/arvin/androidstudy/jni/CallJava");
jmethodID method = (*env)->GetMethodID(env, clazz, "hello", "()V");
jobject object = (*env)->AllocObject(env, clazz);
(*env)->CallVoidMethod(env, object, method);
}
复制代码
这个就是四部曲:
第一步:使用FindClass
方法,第二个参数,就是要调用的函数的类的彻底限定名,可是须要把点换成/
第二步:使用GetMethodID
方法,第二个参数就是刚获得的类的class,第三个就是方法名,第四个就是该函数的签名,这里有个技巧,使用javap -s 类的彻底限定名
就能获得该函数的签名,可是须要在build->intermediates->classes->debug目录下,使用该命令,获得以下结果:
//else method...
public void hello();
descriptor: ()V
复制代码
descriptor:后边的就是该方法的签名
第三步:使用AllocObject
方法,使用clazz建立该class的实例。
第四步:使用CallVoidMethod
方法,能够看到这个就是调用返回为void的方法,第二个参数就是第三步中建立的实例,第三个参数就是上边建立的要调用的方法。
有了这个四部就能在C中吊起Java中的代码了。
而对于有参,有返回的方法,在这四部曲的基础上,只须要修改第二步获取方法的名字和签名,其中签名以及第四步的CallMethod方法,Type能够是int,string,boolean,float等等。
提示:对于基本类型又个技巧,括号内依次是参数的类型的缩写,括号右边是返回类型的缩写,用得多了就能够不用每次都去使用命令查询了,可是开始最好仍是都查一下,省得出错
可是对于静态方法的调用就应该使用GetStaticMethodID
和CallStaticVoidMethod
了,而对于静态方法就不须要实例化对象,相对来讲还少一步。
到这里,可能有使用过java的反射的同窗有疑问了,若是是去调用private的方法,会不会报错呢,这个能够告诉你,我试过了,也是能够调用起来的,没有问题,不用担忧啦。
到这里,Java调用C,C调用Java基本就算是完成了,这个代码我也会上传到github上,须要的同窗能够自行下载比对,有不足之处也请多多指教。地址在文末。
前文中说了,对于多文件的配置会在以后的文章中说到,果真,在第二篇中,想着方法太多了,我想放到别的文件中去处理,避免混乱了,因此就去了解了一下,在此告诉你们,其实很简答。
首先,在以前的配置基础上,再在cpp目录下建立一个文件,例如这里叫作Test.c,而后再到CMakeLists.txt文件中关联上就好了,关联方式以下:
cmake_minimum_required(VERSION 3.4.1)
add_library(Hello
SHARED
src/main/cpp/Hello.c
src/main/cpp/Test.c)
复制代码
对比以前的配置,对了一行src/main/cpp/Test.c
至关于把Test.c文件也关联到叫作Hello的这个lib中。
虽然如今c代码也能够调试debug了,可是仍是有打印日志才方便,printf
是没有用的,因此须要咱们手动去添加一个日志库,首先在CMakeLists.txt中添加成以下:
cmake_minimum_required(VERSION 3.4.1)
add_library(Hello
SHARED
src/main/cpp/Hello.c
src/main/cpp/Test.c)
find_library(log-lib log)
target_link_libraries(Hello ${log-lib})
复制代码
多了后两句代码。而后再须要用到的地方申明:
#include "android/log.h"
#define LOG_TAG "JNI_TEST"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
复制代码
这样就能在这个类中使用了:
这里就有个技巧了,定义一个Log.c文件,导入上文中的配置,而后在须要用日志的地方引入Log.c便可。
这样就不用在每一个文件开头都去申明这些东西了。
在这个项目中,java代码在包下的jni下,配置也可在相应位置查看。
部分代码来源尚硅谷Android视频《JNI》