实现功能描述:在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等