Bootstrap

Android JNI——Java与C相互调用

实现功能描述:在java调用native方法,在native方法里面再回调java的方法

1.创建本地方法

public class HelloNDK {

    static {
        System.loadLibrary("HelloNDK");
    }

    private Context context;
    public HelloNDK(Context context){
        this.context = context;
    }
    //static method
    public static void helloFromJava(){
        System.out.println("jni:I am from java.");
    }

    public int getAdd(int x,int y){
        return x+y;
    }

    public void print(String s){
        System.out.println("jni:"+s);
    }

    public void toast(String s){
        Toast.makeText(context,s,Toast.LENGTH_SHORT).show();
    }

	/**
	*以下为native 方法,由c/c++实现
	*/
    public native static void helloWorld();
    //non-static method
    public native int add(int x,int y);
    public native static String  println(String name);
    public native  void  showToast();
    public native static int[]  arrInt(int[] arr);

}

2.MainActivity里面调用native方法

public class MainActivity extends AppCompatActivity {

    private HelloNDK ndk;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ndk = new HelloNDK(this);
    }

	/**
	*下面方法在4个点击事件完成
	*/
    public void helloWorld(View view) {
        HelloNDK.helloWorld();
    }

    public void add(View view) {
        //native的add方法
        int i = ndk.add(2,5);
        System.out.println("jni:i="+i);
    }

    public void print(View view) {
        HelloNDK.println("hello,jni");
    }

    public void showToast(View view) {
        ndk.showToast();
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="helloWorld"
        android:onClick="helloWorld"
        android:textAllCaps="false"/>
    <Button
        android:id="@+id/btn2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="add"
        android:onClick="add"
        android:textAllCaps="false"/>
    <Button
        android:id="@+id/btn3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="print(str)"
        android:onClick="print"
        android:textAllCaps="false"/>
    <Button
        android:id="@+id/btn4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="showToast"
        android:onClick="showToast"
        android:textAllCaps="false"/>

</LinearLayout>

3.JNI调用

3.1在app\src\main\java路径下执行命令

javah com.pfj.jnidemo.HelloNDK
生成com_pfj_jnidemo_HelloNDK.h文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_pfj_jnidemo_HelloNDK */
#ifndef _Included_com_pfj_jnidemo_HelloNDK
#define _Included_com_pfj_jnidemo_HelloNDK
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_pfj_jnidemo_HelloNDK
 * Method:    helloWorld
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT void JNICALL Java_com_pfj_jnidemo_HelloNDK_helloWorld(JNIEnv *, jclass);

JNIEXPORT jint JNICALL Java_com_pfj_jnidemo_HelloNDK_add(JNIEnv *, jobject , jint, jint);

JNIEXPORT jstring JNICALL Java_com_pfj_jnidemo_HelloNDK_println(JNIEnv *, jclass, jstring);

JNIEXPORT void JNICALL Java_com_pfj_jnidemo_HelloNDK_showToast(JNIEnv *, jobject);

JNIEXPORT jintArray JNICALL Java_com_pfj_jnidemo_HelloNDK_arrInt(JNIEnv *, jclass, jintArray);

#ifdef __cplusplus
}
#endif
#endif

并复制到jni下,同时创建HelloNDK.c和HelloNDK.h,如下所示:
在这里插入图片描述
app下CMakeLists.txt配置

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
           HelloNDK
           # Sets the library as a shared library.
           SHARED
           # Provides a relative path to your source file(s).
        src/main/jni/HelloNDK.c)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
#find_library(android-lib android)
find_library( # Sets the name of the path variable.
            log-lib
            # Specifies the name of the NDK library that
            # you want CMake to locate.
            log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                    HelloNDK
                     # Links the target library to the log library
                     # included in the NDK.
                     ${log-lib} )

重点看HelloNDK.c调用逻辑
先贴出完整代码,再对其分析

#include "HelloNDK.h"
#include "com_pfj_jnidemo_HelloNDK.h"
#include <stdlib.h>
#include <string.h>
#include <Android/log.h>

#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
//第一个方法
JNIEXPORT void JNICALL Java_com_pfj_jnidemo_HelloNDK_helloWorld(JNIEnv *env, jclass jclass1){
    jmethodID  jmethodID = (*env)->GetStaticMethodID(env,jclass1,"helloFromJava","()V");
    (*env)->CallStaticVoidMethod(env,jclass1,jmethodID);
}
//第二个方法
JNIEXPORT jint JNICALL Java_com_pfj_jnidemo_HelloNDK_add(JNIEnv *env, jobject this, jint jint1, jint jint2){
    jclass clazz = (*env)->GetObjectClass(env, this);//由jobject得到jclass
    //jclass  clazz = (*env)->FindClass(env,"com/pfj/jnidemo/HelloNDK");//由jobject得到jclass
    jmethodID  jmethodID = (*env)->GetMethodID(env,clazz,"getAdd","(II)I");
    int res = (*env)->CallIntMethod(env,this,jmethodID,3,4)+jint1+jint2;
    return res;
}
//jstring转成string
char* jstringTostring(JNIEnv *env, jstring jstr)
{
    char* rtn = NULL;
    //获取String.class字节码
    jclass clsstring = (*env)->FindClass(env, "java/lang/String");
    //utf-8转成jstring
    jstring strencode = (*env)->NewStringUTF(env, "utf-8");
    //反射,通过getBytes方法转byte数组
    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
    //获取byte数组在c中的jbyteArray
    jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode);
    //数组长度
    jsize alen = (*env)->GetArrayLength(env, barr);
    //获取数组首地址
    jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
    if (alen > 0)
    {
        rtn = (char*)malloc(alen + 1);//开辟堆内存 ,最后是"\0"
        memcpy(rtn,ba,alen);//复制
        rtn[alen] = 0;
    }
    //复制完释放资源
    (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
    return rtn;
}
//第三个方法
JNIEXPORT jstring JNICALL Java_com_pfj_jnidemo_HelloNDK_println(JNIEnv *env, jclass jclass1, jstring jstr){
   char* str = jstringTostring(env,jstr);//一定要先声明后调用;c是顺序执行的
   //    const char *str = env->GetStringUTFChars(jstr, 0);
   char* str2 = (*env)->GetStringUTFChars(env,jstr,NULL);//Jstring->char*(同上)
    jmethodID  jmethodID = (*env)->GetMethodID(env,jclass1,"print","(Ljava/lang/String;)V");
    jstring s = (*env)->NewStringUTF(env,str);
    //jobject jobject= (*env)->NewObject(env, jclass1, jmethodID,s);
    jobject jobject1 = (*env)->AllocObject(env,jclass1);
    (*env)->CallVoidMethod(env,jobject1,jmethodID,s);//调用非静态方法
    return (*env)->NewStringUTF(env,str);

}
//第四个方法
JNIEXPORT void JNICALL Java_com_pfj_jnidemo_HelloNDK_showToast(JNIEnv *env, jobject jobject1){
    jclass clazz = (*env)->GetObjectClass(env, jobject1);//由jobject得到jclass
    //jclass  clazz = (*env)->FindClass(env,"com/pfj/jnidemo/HelloNDK");//由jobject得到jclass
    jstring js=(*env)->NewStringUTF(env,"jni:hello man");
    jmethodID  jmethodID = (*env)->GetMethodID(env,clazz,"toast","(Ljava/lang/String;)V");
    (*env)->CallVoidMethod(env,jobject1,jmethodID,js);
    //另一种写法
    //(**env).CallVoidMethod(jobject1,jmethodID,js);

}

//处理数组
JNIEXPORT jintArray JNICALL Java_com_pfj_jnidemo_HelloNDK_arrInt(JNIEnv *env, jclass jclass1, jintArray jintArray1){
    int len = (*env)->GetArrayLength(env,jintArray1);//数组长度
    //获取数组首地址
    int* p = (*env)->GetIntArrayElements(env,jintArray1,NULL);//不关心是否创建副本
    int i;
    for (i=0;i<len;i++){
        *(p+i)+=10;//通过指针操作:每个值加10
    }
    return jintArray1;//地址操作,直接修改array
}

第一个方法分析

//第一个方法
JNIEXPORT void JNICALL Java_com_pfj_jnidemo_HelloNDK_helloWorld(JNIEnv *env, jclass jclass1){
    jmethodID  jmethodID = (*env)->GetStaticMethodID(env,jclass1,"helloFromJava","()V");
    (*env)->CallStaticVoidMethod(env,jclass1,jmethodID);
}

在Android端调HelloNDK.helloWorld();其中helloWorld是静态方法,在这个方法里面调java的静态方法helloFromJava()。由于helloWorld是静态方法,这里的方法参数是(JNIEnv *env, jclass jclass1),
参数 env代表了Java环境,通过env这个指针就可以对Java端的代码进行操作
jclass 是方法所在类的字节码对象,如果是非静态方法这里会是jobject

 jmethodID  jmethodID = (*env)->GetStaticMethodID(env,jclass1,"helloFromJava","()V");

这里类似java反射得到Method对象,这里是静态方法,参数解释:
env:java环境对象
jclass1:字节码文件对象
helloFromJava:调用的java的方法名
()V:指的是内部类型签名,括号里面是方法参数,V是返回值(Void)
获取类型签名的方法:
进入:项目名\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes路径下,执行javap -s com.pfj.jnidemo.HelloNDK就可以查看方法的类型签名。
在这里插入图片描述
方法调用

  (*env)->CallStaticVoidMethod(env,jclass1,jmethodID);

CallStaticVoidMethod解释:
Static是指调用静态方法;
Void:返回值是void

第二个方法分析

JNIEXPORT jint JNICALL Java_com_pfj_jnidemo_HelloNDK_add(JNIEnv *env, jobject this, jint jint1, jint jint2){
    jclass clazz = (*env)->GetObjectClass(env, this);//由jobject得到jclass
    //jclass  clazz = (*env)->FindClass(env,"com/pfj/jnidemo/HelloNDK");//由jobject得到jclass
    jmethodID  jmethodID = (*env)->GetMethodID(env,clazz,"getAdd","(II)I");
    int res = (*env)->CallIntMethod(env,this,jmethodID,3,4)+jint1+jint2;
    return res;
}

这个方法时非静态方法,所以这里参数是jobject, jint1和jint2是调用的native方法传入的参数。
在获取jmethodID 时需要字节码对象,这里通过jobject得到jclass,一般方法有两种:

方法1: jclass clazz = (*env)->GetObjectClass(env, this);
方法2: jclass clazz = (*env)->FindClass(env,“com/pfj/jnidemo/HelloNDK”);

CallIntMethod方法参数:
env:环境对象
this:java的类对象
jmethodID:method标识
3,4:是java方法传入的参数
第三个方法分析

JNIEXPORT jstring JNICALL Java_com_pfj_jnidemo_HelloNDK_println(JNIEnv *env, jclass jclass1, jstring jstr){
   //char* str = jstringTostring(env,jstr);//一定要先声明后调用;c是顺序执行的
   char* str = (*env)->GetStringUTFChars(env,jstr,NULL);//Jstring->char*(同上,Jstring转char*)
   jmethodID  jmethodID = (*env)->GetMethodID(env,jclass1,"print","(Ljava/lang/String;)V");
   jstring s = (*env)->NewStringUTF(env,str);//char*转jstring
   //jobject jobject= (*env)->NewObject(env, jclass1, jmethodID,s);//功能相当于下面两行
   jobject jobject1 = (*env)->AllocObject(env,jclass1);//由jclass得到jobject
   (*env)->CallVoidMethod(env,jobject1,jmethodID,s);//调用非静态方法
   return (*env)->NewStringUTF(env,str);//java方法返回值
}

需要关注的问题:
1.char*与jstring转化
2.由jclass得到jobject:

jobject jobject1 = (*env)->AllocObject(env,jclass1);
jobject jobject1 = (*env)->NewObject(env, jclass1, jmethodID,s)

第一种方法只得到jobject 对象,第二种方法得到jobject 对象同时还调用了java方法。

第四个方法分析

JNIEXPORT void JNICALL Java_com_pfj_jnidemo_HelloNDK_showToast(JNIEnv *env, jobject jobject1){
    jclass clazz = (*env)->GetObjectClass(env, jobject1);//由jobject得到jclass
    //jclass  clazz = (*env)->FindClass(env,"com/pfj/jnidemo/HelloNDK");//由jobject得到jclass
    jstring js=(*env)->NewStringUTF(env,"jni:hello man");
    jmethodID  jmethodID = (*env)->GetMethodID(env,clazz,"toast","(Ljava/lang/String;)V");
    (*env)->CallVoidMethod(env,jobject1,jmethodID,js);
    //上面另一种写法
    //(**env).CallVoidMethod(jobject1,jmethodID,js);
}

需要注意的问题:Java调showToast native方法,这个里面调用了android的Toast方法,要记得传递context对象,这里在构造里面进行传递。

总结:java与C调用方式,主要采用类似java反射的步骤流程
1.得到jclass文件对象:class&jclass
2.得到jmethodId:Method&jmethodId
3.得到jobject对象:object&jobject(静态方法不需要jobject)
4.方法调用:invoke&CallVoidMethod等

;