android之JNI(C和Java互调)

本文主要记录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.MainActivity
3. 根据生成的 *.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'
        }
    }

android之JNI(C和Java互调)_第1张图片

你可能感兴趣的:(jni)