本文主要记录JNI里C和Java的相互调用
JNI的API:https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
测试源码 github: https://github.com/CL-window/JNI-c-call-java-
测试环境 :ubuntu, android studio,ndk10e
主要分为以下几个方面实现代码:
1. java 调用 c
2. c 调用 java的无参数的static方法
3.c 调用 java的无参数的非static方法
4.c 调用 java的有参数的非static方法
5.c 获取以及更改 java里非static变量的值
下面一条一条实现,android studio 配置NDK 环境还不会的请参考:android之NDK环境小试牛刀
主要就是下载好NDK,然后安装好,项目的 gradle.properties 文件里新增 android.useDeprecatedNdk=true 就OK了。
1.先把 native 方法写出来
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.e("slack",getDataFromCCode()); //JniData.getTime(); putDataToJavaCode(); putDataToJavaCodeWithParams("slack"); putDataToJavaCodeWithParamsAndReturn("slacking"); } static{ System.loadLibrary("jnidemo"); } // java 调用 c public native String getDataFromCCode(); // c 调用 java 这里是先java调用c(可以直接c函数里调用javaset ,但是需要c代码先跑起来),再c调用java public native void putDataToJavaCode(); // c 调用 java 有参数,无返回值 public native void putDataToJavaCodeWithParams(String data); //c 调用 java 有参数,无返回值 get set 方法改变java里的值 public native void putDataToJavaCodeWithParamsAndReturn(String data); }2. 生成 *.h 文件
root@chenling-VirtualBox:/home/chenling/java/androidstudio/JNIDemo/app/src/main# cd java/ root@chenling-VirtualBox:/home/chenling/java/androidstudio/JNIDemo/app/src/main/java# javah -d ../jni com.example.root.jnidemo.MainActivity3. 根据生成的 *.h 文件新建 *.c 文件,在 *.c 里实现代码
3.1 java 调用 c
需要对无参数时的函数参数做一个说明:
JNIEnv类型代表Java环境。通过这个JNIEnv*指针,就可以对Java端的代码进行操作。
如,创建Java类得对象,调用Java对象的方法,获取Java对象的属性等。
JNIEnv的指针会被JNI传送到本地方法的实现函数中来对Java端的代码进行操作
jobject instance 代表当前类对象,即调用 native 方法的那个类
JNIEXPORT jstring JNICALL Java_com_example_root_jnidemo_MainActivity_getDataFromCCode(JNIEnv *env, jobject instance) { // 日志输出 __android_log_write(ANDROID_LOG_INFO,"slack","This is a Test..."); //返回 activity return (*env)->NewStringUTF(env,"I'm comes from to Native Function!"); }3.2 c 调用 java的无参数的static方法
我写了一个类,用于提供方法给 c 调用
import android.util.Log; import java.text.SimpleDateFormat; /** * Created by chenling on 16-4-25. */ public class JniData { // 非static c调用时会新建一个对象,无所谓 权限修饰符 private String param = "slack form java"; // static 必须是 public public static void getStaticTime(){ Log.i("slack", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()) ); } // 非static c调用时会新建一个对象,无所谓 权限修饰符 public void getTime(){ Log.i("slack", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()) ); } // 非static c调用时会新建一个对象,无所谓 权限修饰符 private void getTime(String data){ Log.i("slack", data+"..." ); } }接着看看c代码
// 调用java 无参数的static 无返回 void __func__useStaticMethod(JNIEnv *env, jobject instance) { // java 反射 //1 .找到java代码的 class文件 //static methods jclass native_clazz = (*env)->FindClass(env,"com/example/root/jnidemo/JniData"); if(native_clazz == 0){ __android_log_write(ANDROID_LOG_ERROR,"slack","find class error ..."); return; } //2 .寻找class里面的方法 //static methods // 最后一个参数 签名是对函数参数和返回值的描述 签名参照 .h 文件 jmethodID methodid = (*env)->GetStaticMethodID(env,native_clazz,"getStaticTime","()V"); if(methodid == 0){ __android_log_write(ANDROID_LOG_ERROR,"slack"," find method error ..."); return; } //3 .调用这个方法 // void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); (*env)->CallStaticVoidMethod(env,native_clazz,methodid); }3.3 c 调用 java的无参数的非static方法
// 调用java 无参数的非static 无返回 需要类似 java里的 new一个对象 void __func__useMethod(JNIEnv *env, jobject instance) { //1 .找到java代码的 class文件 java 反射 //static methods FindClass 根据类名来查找一个类,完整类名 jclass native_clazz = (*env)->FindClass(env,"com/example/root/jnidemo/JniData"); if(native_clazz == 0){ __android_log_write(ANDROID_LOG_ERROR,"slack","find class error ..."); return; } //2 .C中新建java对象 通过NewObject来创建对象 //默认构造函数,不传参数 jmethodID construction_id = (*env)->GetMethodID(env, native_clazz,"<init>", "()V"); jobject mJavaObject = (*env)->NewObject(env, native_clazz,construction_id); //3 .寻找class里面的方法 // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodid = (*env)->GetMethodID(env,native_clazz,"getTime","()V"); if(methodid == 0){ __android_log_write(ANDROID_LOG_ERROR,"slack"," find method error ..."); return; } //3 .调用这个方法 jobject 是新建出来的那个对象 // void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); (*env)->CallVoidMethod(env,mJavaObject,methodid); }3.4 c 调用 java的有参数的非static方法
这里对参数需要做一个处理,要不会出现乱码
/* java 调用 c 时传参数 关键是这里的参数的类型转换 * c 调用 java 里有参数无返回值的非静态方法 * 在java中由于是unicode编码,无论是英文字母还是汉字每个字符都是占用2个字节。但是在jni中的字符是utf-8编码,每个字符不是等长的 * */ void __func__userMethodWithParams(JNIEnv * env, jobject instance, jstring data) { // jstring --> char * // const char * GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy); char * ch = (char*)((*env)->GetStringUTFChars(env,data,0)); __android_log_write(ANDROID_LOG_INFO,"slack",ch); // c 调用 java 里有参数无返回值的非静态方法 __android_log_write(ANDROID_LOG_INFO,"slack","1 ..."); //1 .找到java代码的 class文件 java 反射 //static methods FindClass 根据类名来查找一个类,完整类名 jclass native_clazz = (*env)->FindClass(env,"com/example/root/jnidemo/JniData"); __android_log_write(ANDROID_LOG_INFO,"slack","2 ..."); if(native_clazz == 0){ __android_log_write(ANDROID_LOG_ERROR,"slack","find class error ..."); return; } //2 .C中新建java对象 通过NewObject来创建对象 //默认构造函数,不传参数 jmethodID construction_id = (*env)->GetMethodID(env, native_clazz,"<init>", "()V"); jobject mJavaObject = (*env)->NewObject(env, native_clazz,construction_id); //3 .寻找class里面的方法 // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); 有参数 参照 .h 文件 jmethodID methodid = (*env)->GetMethodID(env,native_clazz,"getTime","(Ljava/lang/String;)V"); __android_log_write(ANDROID_LOG_INFO,"slack","3 ..."); if(methodid == 0){ __android_log_write(ANDROID_LOG_ERROR,"slack"," find method error ..."); return; } __android_log_write(ANDROID_LOG_INFO,"slack","4 ..."); // char * --> jstring 传java函数里的参数 // jstring NewStringUTF(JNIEnv *env, const char *bytes); jstring datas = (*env)->NewStringUTF(env,ch); //4 .调用这个方法 jobject 是新建出来的那个对象 // void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); (*env)->CallVoidMethod(env,mJavaObject,methodid,datas); }3.5 c 获取以及更改 java里非static变量的值
这里测试的是String类型,其他Int,long,char等是类似的,JNI里支持的类型可以去上面提供的官方API里查找
https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp9502
/* java 调用 c 时传参数 * c 调用 java 里有参数有返回值的非静态方法 即java端运算完成后返回给 c 端 * 这里通过 GetIntField() SetIntField() 来实现 * */ void __func__userMethodWithParamsAndReturn(JNIEnv * env, jobject instance, jstring data) { // jstring --> char * // const char * GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy); char * ch = (char*)((*env)->GetStringUTFChars(env,data,0)); __android_log_write(ANDROID_LOG_INFO,"slack",ch); // c 调用 java 里有参数有返回值的非静态方法 __android_log_write(ANDROID_LOG_INFO,"slack","1 ..."); //1 .找到java代码的 class文件 java 反射 //static methods FindClass 根据类名来查找一个类,完整类名 jclass native_clazz = (*env)->FindClass(env,"com/example/root/jnidemo/JniData"); __android_log_write(ANDROID_LOG_INFO,"slack","2 ..."); if(native_clazz == 0){ __android_log_write(ANDROID_LOG_ERROR,"slack","find class error ..."); return; } // .C中新建java对象 通过NewObject来创建对象 //默认构造函数,不传参数 jmethodID construction_id = (*env)->GetMethodID(env, native_clazz,"<init>", "()V"); jobject mJavaObject = (*env)->NewObject(env, native_clazz,construction_id); __android_log_write(ANDROID_LOG_INFO,"slack","3 ..."); // .得到jfieldID // jfieldID GetFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig); jfieldID fieldID_param = (*env)->GetFieldID(env,native_clazz,"param","Ljava/lang/String;"); //获得属性句柄 if(fieldID_param == 0){ __android_log_write(ANDROID_LOG_ERROR,"slack","find jfieldID error ..."); return; } __android_log_write(ANDROID_LOG_INFO,"slack","4..."); // .得到param // NativeType Get<type>Field(JNIEnv *env, jobject obj,jfieldID fieldID); jstring param= (jstring )(*env)->GetObjectField(env,mJavaObject,fieldID_param); __android_log_write(ANDROID_LOG_INFO,"slack", (char*)((*env)->GetStringUTFChars(env,param,0))); // .修改param属性的值 char * c_param = "slack come from Native" ; param = (*env)->NewStringUTF(env,c_param);//构造一个jstring对象 // void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,NativeType value); (*env)->SetObjectField(env , mJavaObject, fieldID_param, param); // 设置该字段的值 param= (jstring )(*env)->GetObjectField(env,mJavaObject,fieldID_param); __android_log_write(ANDROID_LOG_INFO,"slack", (char*)((*env)->GetStringUTFChars(env,param,0))); }
Rebuid project后,*.so文件在 app/build/intermediates/ndk/debug/lib 下
把 生成的 各平台下的 so 文件拷贝到 app/lib下,*.c以及*.h就可以删除了,在app的 build.gradle里配置一下就可以直接使用 so 文件了
//指定动态库路径 so文件 sourceSets{ main{ jni.srcDirs = [] jniLibs.srcDir 'libs' } }