1.初识JNI,第一个JNI小程序
什么是JNI?
JNI java本地开发接口(java native interface)
JNI 是一个协议
这个协议用来沟通java代码和外部的本地代码(c/c++).
通过这个协议,java代码就可以调用外部的c/c++代码
外部的c/c++代码也可以调用java代码
为什么用JNI?
java 一次编译 到处执行,不能写驱动,c是底层语言可以写驱动,要想在java中调用C语言的代码就必须使用jni,JNI扩展了java 虚拟机的能力,
怎么用JNI
1.C/C++语言,参考我之前的博客:c学习之路
2.NDK (native develop kits ) :下载
eclipse NDK配置:
如果有如下错误:
解决办法:在解压的第一层子目录添加一个空的ndk-build文件
如下:
**注意:**谷歌改良了ndk的开发流程,对于Windows环境下NDK的开发,如果使用的NDK是r7之前的版本,必须要安装Cygwin才能使用NDK。而在NDKr7开始,Google的Windows版的NDK提供了一个ndk-build.cmd的脚本,这样,就可以直接利用这个脚本编译,而不需要使用Cygwin了。只需要为Eclipse Android工程添加一个Builders,而为Eclipse配置的builder,其实就是在执行Cygwin,然后传递ndk-build作为参数,这样就能让Eclipse自动编译NDK。
4.掌握java jni流程
-
创建一个android工程
-
为工程新建一个jni的Folder
-
为工程添加一个Builder
1.右键工程找到Properties点击 2.找到Builder:
3.在弹出的【Choose configuration type】对话框,选择【Program】,点击【OK】
4.在弹出的【Edit Configuration】对话框中,配置选项卡【Main】。
在“Name“中输入新builders的名称(这个名字可以任意取)。
在“Location”中输入nkd-build.cmd的路径(这个是下载完ndk后解压后的路径,这个建议放在根目录下面,路径不能有空格和中文)。根据各自的ndk路径设置,也可以点击“Browser File System…”来选取这个路径。
在“Working Diretcoty”中输入Android工程位置(也可以点击“Browse Workspace”来选取TestNdk目录)。如图:
5、继续在这个【Edit Configuration】对话框中,配置选项卡【Refresh】。如下图 勾选“Refresh
resources upon completion”, 勾选“The entire workspace”,
勾选“Recuresively include sub-folders”。
6、继续在【Edit Configuration】对话框中,配置选项卡【Build options】。 勾选“After a“Clean””,(勾选这个操作后,如果你想编译ndk的时候,只需要clean一下项目 就开始交叉编译) 勾选“During manual builds”, 勾选“During auto builds”, 勾选“Specify working set of relevant
resources”。如下图
- JAVA代码中写声明native 方法 public native String helloFromJNI();
- 利用cmd生成一个.h的样式文件,为什么我叫它样式文件,因为它里面的内容就是我们接下在用c代码中需要实现native的方法,只不过.h文件没有具体操作而已,方法名和参数之类的都已经帮我们写好了,我们只需要直接赋值它里面的内容,然后实现具体的操作方法即可,这就是.h文件的好处:
在cmd来到你的Android工程目录的src目录下
使用javah -jni 包名.类名即可,如图:
这是一个下图所示的.h文件
将生成的.h文件复制到你的Android工程的jni目录:
- 编写.c的c语言文件
我们刚才说了.h里面已经帮我们定义好了,我们只要具体实现方法就好了
首先在jni目录下创建一个.c文件,文件名随便取,代码:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <stdio.h>
#include <jni.h>
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: helloFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_helloFromJNI
(JNIEnv* env, jobject obj){
//表示返回一个String
return (*env)->NewStringUTF(env,"hello from c");
}
#ifdef __cplusplus
}
#endif
#endif
以上代码只是在.h文件的基础上添加了一个#include <stdio.h>和实现了方法
- 编写Android.mk文件:c/c++与java交互的规则
首先在jni目录下创建一个Android.mk文件,这个文件不能随便取,只能是Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE :=Hello
LOCAL_SRC_FILES :=Hello.c
#liblog.so libGLESv2.so
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
关于Android.mk介绍:
LOCAL_PATH:=$(call my-dir)
LOCAL_PATH是定义源文件在哪个目录用的.
my-dir 是个定义的宏方法, $(call my-dir)就是调用这个叫 my-dir的宏方法,这个方法返回值就是
Android.mk文件所在的目录include $(CLEAR_VARS)
CLEAR_BARS 变量是build system里面的一个变量 这个变量指向了所有的类似 LOCAL_XXX的变量, 执行完这一句话,
这个编译系统就把 所有的类似
LOCAL_MODULE,_SRC_FILELOCALS,LOCAL_STATIC_LIBRARIES,…这样的变量都清除掉
但是不会清除掉 LOCAL_PATHLOCAL_MODULE 就是你要生成的库的名字,名字要是唯一的这个.不能有空格. 编译后系统会自动在前面加上lib的头,
比如说我们的Hello 就编译成了libHello.so还有个特点就是如果你起名叫libHello 编译后ndk就不会给你的module名字前加上lib了
但是你最后调用的时候 还是调用Hello这个库
LOCAL_SRC_FILES = :Hello.c 这个是指定你要编译哪些文件 不需要指定头文件 ,引用哪些依赖,
因为编译器会自动找到这些依赖 自动编译include $(BUILD_SHARED_LIBRARY) BUILD_STATIC_LIBRARY .so
编译后生成的库的类型,如果是静态库.a 配置include $(BUILD_STATIC_LIBRARY)
别的参数
LOCAL_CPP_EXTENSION := cc //指定c++文件的扩展名 LOCAL_MODULE := ndkfoo
LOCAL_SRC_FILES := ndkfoo.ccLOCAL_LDLIBS += -llog -lvmsagent -lmpnet -lmpxml -lH264Android
//指定需要加载一些别的什么库.
成功的话生成一系列的.so文件 如下图:
这些.so文件在我们的libs目录下
- .so 文件生成以后我们就可以在Java代码load 动态库.调用native代码,如下:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btn_jni(View view){
Toast.makeText(this, helloFromJNI(), Toast.LENGTH_SHORT).show();
}
public native String helloFromJNI();
static {
System.loadLibrary("Hello");
}
}
最后我们在原来的代码的基础上添加一个Button用于Toast出jni出来的String字符串,然后加了下面一串代码:
static {
System.loadLibrary("Hello");
}
注意:静态代码块优先加载内存,loadLibrary表示加载我们的动态库,而Hello并不是我乱写的,Hello对应我们的Androd.mk文件中的LOCAL_MODULE所对应的值
最后运行点击按钮,如图:
我们的jni第一个小程序就运行成功了
2.JNI之LOG调试代码
java中我们想打印Log那是很简单的只要在我们想打印的地方Log.i,log.e…,
ndk中为我们提供了一个log库,在jni里面我们想打印Log只要把Log库给引用进来就能用了。
库的位置:ndk目录下的platforms-随便一个API-arch-mips\usr\lib\liblog.so
1.C代码中增加:
#include <stdio.h>
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
首先第一行代码我们将ndk中的log.so头文件给引用进来了,
接下来我们看到了库中的Log打印函数__android_log_print(),此方法接收三个参数
- log的级别,在java中我们已经知道log有5个等级的info,error,verbose…,jni中也是一样
- TAG标识,在java中我们的Log.i()第一个参数也是TAG,这里也是一样的意思
- 我们想要Log的String字符串
#######define表示c/c++语言中的宏定义,定义完不用写‘;’号,简单明了的意思就是使用一种你自己定义的一种简单的语法代替c/c++中比较复杂的语法,例如:
我们方法中的TAG参数:
我们可以看到第二行: #define LOG_TAG “System.out.c”
这里就表示使用LOG_TAG代替 “System.out.c”
以此类推: #define LOGD(…) ___android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, VA_ARGS) 表示使用LOGD(…) 代替我们的Log.d(),
在使用时我们只要在你需要LOG的地方使用LOGD(“你需要显示的LOG信息即可”)
注意:打印的Log信息不能是中文,只能是英文
2.在Android.mk中增加:
前面我们已经说过Androd.mk文件是我们定义jni使用的一些规则,那么既然需要使用ndk的Log库,当然也要告诉我们的Android.mk文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE :=Hello
LOCAL_SRC_FILES :=Hello.c
#liblog.so
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
可以看到 LOCAL_LDLIBS += -llog 就表示引用了log库,
我们可以试着举一反三,如果我们还需要引用libGLESv2.so,即加上代码LOCAL_LDLIBS += -lGLESv2
记住前面lib和后面的.so是不用加入进去的
3.在需要的地方调用:LOGD(“开始调用吧”);
再次注意:打印的Log信息不能是中文,只能是英文
LOG篇结束。
3.JNI之中文乱码
在第一部分我们已经成功点击按钮弹出了一个Hello from c,现在我们将Hello from c修改成中文:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <stdio.h>
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: helloFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_helloFromJNI
(JNIEnv* env, jobject obj){
//表示返回一个String
LOGD("log start");
return (*env)->NewStringUTF(env,"你好 中国");
}
#ifdef __cplusplus
}
#endif
#endif
再次运行点击按钮发现程序崩掉了,错误如下:
根据错误信息:NewStringUTF输入的不是UTF-8类型的字符串,表示我们的”你好中国“不是UTF-8类型的
解决办法:
1.找到我们的.c文件右键找到Properties->
2.再次运行,成功:
中文乱码结束
4、java传递数据给c语言
首先我们来回顾一下jni的实现步骤:
- 创建一个Android工程
- 在工程中创建一个jni的Floder
- 编写native代码
- 使用javah -jni命令生成头文件
- 将头文件复制到jni文件下
- 添加Builder
- 编写.c代码
- 编写Android.mk文件
- 在java中调用navtive代码
需求一:通过java传递两个int类型的数据给c语言,然后c将两个数相加的和传递回去
在第一节中我们已经实现了我们的第一个JNI程序,为了节省配置,所以我继续在此项目上继续增加代码以减少配置
所以我们直接来到了第三步,代码如下:
mport android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btn_jni(View view){
Toast.makeText(this, helloFromJNI(), Toast.LENGTH_SHORT).show();
}
public native String helloFromJNI();
/**
* 把两个java中的int传递给c语言, c语言处理完毕后,把相加的结果返回给java
* @param x
* @param y
* @return
*/
public native int add(int x,int y);
static {
System.loadLibrary("Hello");
}
}
3、可以看到我们只是简单的在第一步中添加了一行
public native int add(int x,int y);
4、接下生成头文件,使用javah -jni
5、找到生成的头文件复制到jni目录下,因为我们之前已经存在一个,所以直接覆盖就好了,头文件代码如下
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: helloFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_helloFromJNI
(JNIEnv *, jobject);
/*
* Class: com_example_jnidemo_MainActivity
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_add
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
6、因为之前项目已经添加了Builder,所以第6步我们可以省略
7、编写c代码,找到我们的之前编写的Hello.c,如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <stdio.h>
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: helloFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_helloFromJNI
(JNIEnv* env, jobject obj){
LOGD("log start");
return (*env)->NewStringUTF(env,"你好 中国");
}
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_add
(JNIEnv *env, jobject obj, jint x, jint y){
LOGI("x=%d",x);
LOGI("y=%d",y);
return x+y;
}
#ifdef __cplusplus
}
#endif
#endif
代码中我们添加了一个方法,并返回了x+y的值
8、因为Android.mk文件我们已经写好了,所以可以忽略第8步
9、在java中调用:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btn_jni(View view){
Toast.makeText(this, helloFromJNI(), Toast.LENGTH_SHORT).show();
}
public void btn_add(View view){
int sum=add(3, 7);
Toast.makeText(this, "相加为:"+sum, Toast.LENGTH_SHORT).show();
}
public native String helloFromJNI();
public native int add(int x,int y);
static {
System.loadLibrary("Hello");
}
}
从代码中我们可以看到我只是简单添加了一个按钮,然后调用navtive函数,使用Toast出add的和
效果图:
C打印的LOG:
log图中可以看到java的两个数剧传递给了c
需求二:把java中的string传递给c语言, c语言获取到java中的string之后 ,在string后面添加 一个hello 字符串
之前我们已经讲解了两个jni的实例,我相信如果你仔细观看了并手打了代码我相信你已经掌握了jni的流程和基本用法,只需要进一步巩固,所以我们下面的例子将不会详细讲解,只给出代码,毕竟都是纯手工的打字
1.native代码:
package com.example.jnidemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btn_jni(View view){
Toast.makeText(this, helloFromJNI(), Toast.LENGTH_SHORT).show();
}
public void btn_add(View view){
int sum=add(3, 7);
Toast.makeText(this, "相加为:"+sum, Toast.LENGTH_SHORT).show();
}
public native String helloFromJNI();
/**
* 把两个java中的int传递给c语言, c语言处理完毕后,把相加的结果返回给java
* @param x
* @param y
* @return
*/
public native int add(int x,int y);
/**
* 把java中的string传递给c语言, c语言获取到java中的string之后 ,在string后面添加 一个hello 字符串
* @param s
* @return
*/
public native String javaToC(String str);
static {
System.loadLibrary("Hello");
}
}
生成的头文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: helloFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_helloFromJNI
(JNIEnv *, jobject);
/*
* Class: com_example_jnidemo_MainActivity
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_add
(JNIEnv *, jobject, jint, jint);
/*
* Class: com_example_jnidemo_MainActivity
* Method: javaToC
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_javaToC
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
.c代码
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <stdio.h>
#include <jni.h>
#include<malloc.h>
#include<string.h>
#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/**
* 返回值 char* 这个代表char数组的首地址
* Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串
*/
char* Jstring2CStr(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String"); //String
jstring strencode = (*env)->NewStringUTF(env,"GB2312"); // 得到一个java字符串 "GB2312"
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env,barr); // byte数组的长度
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;
}
/*
* Class: com_example_jnidemo_MainActivity
* Method: helloFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_helloFromJNI
(JNIEnv* env, jobject obj){
LOGD("log start");
return (*env)->NewStringUTF(env,"你好 中国");
}
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_add
(JNIEnv *env, jobject obj, jint x, jint y){
LOGI("x=%d",x);
LOGI("y=%d",y);
return x+y;
}
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_javaToC
(JNIEnv *env, jobject obj, jstring str){
char* cStr=Jstring2CStr(env,str);
LOGI("cStr=%s",cStr);
char* addStr="hello";
//表示将addStr添加到cStr末尾
strcat(cStr,addStr);
return (*env)->NewStringUTF(env,cStr);
}
#ifdef __cplusplus
}
#endif
#endif
首先我们根据需求知道我们要在c里面得到java中的字符串并在后面添加一个hello,经过c语言的学习我们知道c语言中没有String类型,用char表示字符串,所以我们必须用java中的字符串转换成c中的char,所以就有了我们.c代码中的Jstring2CStr()方法,此方法就是将java中的String转换成c中的char*,这个方法暂时看不懂不要紧,我们现在只要它使用干嘛的就行,后面会讲解
然后大家还有一个疑惑就是strcat()方法,此方法是表示将第二个参数的数据放在第一参数数据的后面
最后的MainActivity代码
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btn_jni(View view){
Toast.makeText(this, helloFromJNI(), Toast.LENGTH_SHORT).show();
}
public void btn_add(View view){
int sum=add(3, 7);
Toast.makeText(this, "相加为:"+sum, Toast.LENGTH_SHORT).show();
}
public void btnString(View view){
String string=javaToC("javatoc ");
Toast.makeText(this, string, Toast.LENGTH_SHORT).show();
}
public native String helloFromJNI();
/**
* 把两个java中的int传递给c语言, c语言处理完毕后,把相加的结果返回给java
* @param x
* @param y
* @return
*/
public native int add(int x,int y);
/**
* 把java中的string传递给c语言, c语言获取到java中的string之后 ,在string后面添加 一个hello 字符串
* @param s
* @return
*/
public native String javaToC(String str);
static {
System.loadLibrary("Hello");
}
}
效果图:
Log:
需求三、把java中的一个int数组 传递给c语言,c语言处理完毕这个java数组 把int数组中的每一个元素+10
nvative:
/**
* 把java中的一个int数组 传递给c语言,c语言处理完毕这个java数组
* 把int数组中的每一个元素+10 ;
* 然后把结果返回给java
* @param iNum
* @return
*/
public native int[] callBackArr(int[] arr);
.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: helloFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_helloFromJNI
(JNIEnv *, jobject);
/*
* Class: com_example_jnidemo_MainActivity
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_add
(JNIEnv *, jobject, jint, jint);
/*
* Class: com_example_jnidemo_MainActivity
* Method: javaToC
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_javaToC
(JNIEnv *, jobject, jstring);
/*
* Class: com_example_jnidemo_MainActivity
* Method: callBackArr
* Signature: ([I)[I
*/
JNIEXPORT jintArray JNICALL Java_com_example_jnidemo_MainActivity_callBackArr
(JNIEnv *, jobject, jintArray);
#ifdef __cplusplus
}
#endif
#endif
.c
JNIEXPORT jintArray JNICALL Java_com_example_jnidemo_MainActivity_callBackArr
(JNIEnv *env, jobject obj, jintArray arr){
int len = (*env)->GetArrayLength(env,arr);
LOGD("shuzu len =%d",len);
// jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
jint* intarr = (*env)->GetIntArrayElements(env,arr,0);
int i =0; //c99
for(;i<len;i++){
//*(intarr+i) += 10;
LOGD("intarr[%d]=%d", i, intarr[i]);
intarr[i]+= 10;
}
return arr;
}
从代码中我们可以看到使用env里面的方法,关于env我们还没有讲解,
但是在第一节中我们说了一个jni.h的文件,这个文件其实就是ndk提供给我们的一些jni的方法,我们可以将jni.h中的方法
找到我们的jni.h文件:ndk解压目录\platforms\随便一个api即可\arch-mips\usr\include
例如我的:D:\My\android-ndk-r13-windows-x86_64\android-ndk-r13\platforms\android-16\arch-mips\usr\include
打开这个文件,我们可以找到
- 可以看到第一个箭头中
根据学习的c语言:typedef const struct JNINativeInterface* JNIEnv; 将
结构体JNINativeInterface命名成JNINativeInterface类型的JNIEnv指针变量,也就是我们每次写.c无论你Java的native方法中有无参数,而我们的.c方法必须有的第一个参数JNIEnv
*env,而第二个参数则是我们的native类对象,你的native在哪个类,表示的就是哪个类对象
- 第二个箭头
表示的就是结构体JNINativeInterface,它里面定义了一些列的方法和字段变量,在里面我们可以找到我们在上面的.c中的GetArrayLength()方法和GetIntArrayElements()方法,根据字面意思我们可以了解一个是得到数据的长度,一个是得到数组的元素
最后的MainActivity:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btn_jni(View view){
Toast.makeText(this, helloFromJNI(), Toast.LENGTH_SHORT).show();
}
public void btn_add(View view){
int sum=add(3, 7);
Toast.makeText(this, "相加为:"+sum, Toast.LENGTH_SHORT).show();
}
public void btnString(View view){
String string=javaToC("javatoc ");
Toast.makeText(this, string, Toast.LENGTH_SHORT).show();
}
public void btnArr(View view){
int[] arr={1,2,3,4,5};
int[] newArr=callBackArr(arr);
for (int i = 0; i < newArr.length; i++) {
int j = newArr[i];
System.out.println(j);
}
}
public native String helloFromJNI();
/**
* 把两个java中的int传递给c语言, c语言处理完毕后,把相加的结果返回给java
* @param x
* @param y
* @return
*/
public native int add(int x,int y);
/**
* 把java中的string传递给c语言, c语言获取到java中的string之后 ,在string后面添加 一个hello 字符串
* @param s
* @return
*/
public native String javaToC(String str);
/**
* 把java中的一个int数组 传递给c语言,c语言处理完毕这个java数组
* 把int数组中的每一个元素+10 ;
* 然后把结果返回给java
* @param iNum
* @return
*/
public native int[] callBackArr(int[] arr);
static {
System.loadLibrary("Hello");
}
}
打开Log,我们可以清楚的看到效果:
5、使用static修饰native方法
java:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btn_jni(View view){
int sum=add(5, 8);
Toast.makeText(this, "static 后的:"+sum, Toast.LENGTH_SHORT).show();
}
public static native int add(int x,int y);
static {
System.loadLibrary("Hello");
}
}
.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_add
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
从代码中我们可以看到之前我们的第二参数是.jobject,在我们使用static修饰了native之后,变成了jclass,之前第4节的第三个需求中我们讲了jobject表示native所在类的对象,而jclass表示native所在的类
.c
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include<stdio.h>
#include <jni.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_add
(JNIEnv *env, jclass cls, jint x, jint y){
LOGI("x=%d",x);
LOGI("y=%d",y);
return x+y;
}
#ifdef __cplusplus
}
#endif
#endif
效果图:
log
6、JNI实际开发的流程
如果实际开发中需要用到jni,一般情况c程序员会写好一些方法,共你去调用,当然你不必知道方法具体的实现,你只要知道方法参数怎么穿,方法的作用即可,下面我们将模拟jni实现一个登录的效果
1.首先.c程序员写好的登录代码:
int login(char* username, char* pwd){
// 连接网络发送数据给服务器,
// username "zhangsan" pwd "123"
char* rightname ="zhangsan";
char* rightpwd ="123";
int i=0;
for(;username[i]!='\0';i++){
if(username[i]!=rightname[i] )
return 404;
}
return 200;
}
从上面我们可以知道参数是账号和密码,返回值是一个结果码,当然这个login不规范
2.然后我们写好java的native的login:
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
EditText et_account,et_psd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_account=(EditText) findViewById(R.id.et_account);
et_psd=(EditText) findViewById(R.id.et_psd);
}
public void btn_login(View view){
Log.i("MainActiviyt", "start");
String account=et_account.getEditableText().toString().trim();
String psd=et_psd.getEditableText().toString().trim();
Log.i("MainActiviyt", "result");
int result=login(account, psd);
if (result==200) {
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "登录失败,错误代码:"+result, Toast.LENGTH_SHORT).show();
}
}
public native int login(String account,String password);
static {
System.loadLibrary("Hello");
}
}
界面是两个EditText分为是账号和密码,还有一个登录的按钮,触发native的方法
3.编写.c文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include<stdio.h>
#include <jni.h>
#include<malloc.h>
#include<string.h>
#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/**
* 返回值 char* 这个代表char数组的首地址
* Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串
*/
char* Jstring2CStr(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");
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;
}
int login(char* username, char* pwd){
// 连接网络发送数据给服务器,
// username "zhangsan" pwd "123"
char* rightname ="zhangsan";
char* rightpwd ="123";
int i=0;
for(;username[i]!='\0';i++){
if(username[i]!=rightname[i] )
return 404;
}
return 200;
}
JNIEXPORT jint JNICALL Java_com_example_jnidemo_MainActivity_login
(JNIEnv *env, jobject obj, jstring account, jstring psd){
char* accountC=Jstring2CStr(env,account);
char* psdC=Jstring2CStr(env,psd);
LOGI("accountC =%s",accountC);
LOGI("psdC =%s",psdC);
return login(accountC,psdC);
}
#ifdef __cplusplus
}
#endif
#endif
- 代码中我们将c程序员写好的login()方法给应用进来了,
- 然后我们的Jstring2CStr()方法还是我们之前需求中的方法,作用是将java中的String转换成char*类型,这是一个辅助方法,我们可以保存起来,以后可以直接用
- 最后就是在c中实现我们java中的native方法()
4、Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE :=Hello
LOCAL_SRC_FILES :=Hello.c
#liblog.so libGLESv2.so
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
运行效果图:
7、C代码回调java中的代码
首先我定义了一个DataProvider
public class DataProvider {
//C调用java空方法
public void helloFromJava(){
System.out.println("hello from java");
}
//C调用java中的带两个int参数的方法
public int Add(int x,int y){
int result = x+y;
System.out.println("java result"+ result);
return result;
}
//C调用java中参数为string的方法
public void printString(String s){
System.out.println("java "+ s);
}
public static void printStaticStr(String s){
System.out.println("java static"+ s);
}
//让c代码调用对应的java代码
public native void callmethod1();
public native void callmethod2();
public native void callmethod3();
//调用一个静态的c代码
public native void callmethod4();
}
代码中我们首先定义了4个java方法,用于c调用
然后我们定义了4个native方法,用于c操作Java,值得注意的是我们定义了一个static的native方法
MainActiviy:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
static{
System.loadLibrary("Hello");
}
private DataProvider dp;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dp = new DataProvider();
}
public void click1(View view){
dp.callmethod1();
}
public void click2(View view){
dp.callmethod2();
}
public void click3(View view){
dp.callmethod3();
}
public void click4(View view){
dp.callmethod4();
}
}
生成.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnidemo_DataProvider */
#ifndef _Included_com_example_jnidemo_DataProvider
#define _Included_com_example_jnidemo_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_DataProvider
* Method: callmethod1
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_jnidemo_DataProvider_callmethod1
(JNIEnv *, jobject);
/*
* Class: com_example_jnidemo_DataProvider
* Method: callmethod2
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_jnidemo_DataProvider_callmethod2
(JNIEnv *, jobject);
/*
* Class: com_example_jnidemo_DataProvider
* Method: callmethod3
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_jnidemo_DataProvider_callmethod3
(JNIEnv *, jobject);
/*
* Class: com_example_jnidemo_DataProvider
* Method: callmethod4
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_jnidemo_DataProvider_callmethod4
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
最后就是最重要的.c文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include<stdio.h>
#include <jni.h>
#include<malloc.h>
#include<string.h>
#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_com_example_jnidemo_DataProvider_callmethod1
(JNIEnv *env, jobject obj){
//1 . 找到java代码的 class文件
// jclass (*FindClass)(JNIEnv*, const char*);
jclass dpclazz = (*env)->FindClass(env,"com/example/jnidemo/DataProvider");
if(dpclazz==0){
LOGI("find class error");
return;
}
LOGI("find class ");
//2 寻找class里面的方法
// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID method1 = (*env)->GetMethodID(env,dpclazz,"helloFromJava","()V");
if(method1==0){
LOGI("find method1 error");
return;
}
LOGI("find method1 ");
//3 .调用这个方法
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env,obj,method1);
}
JNIEXPORT void JNICALL Java_com_example_jnidemo_DataProvider_callmethod2
(JNIEnv *env, jobject obj){
jclass dpclazz = (*env)->FindClass(env,"com/example/jnidemo/DataProvider");
if(dpclazz==0){
LOGI("find class error");
return;
}
LOGI("find class ");
jmethodID method2 = (*env)->GetMethodID(env,dpclazz,"Add","(II)I");
if(method2==0){
LOGI("find method2 error");
return;
}
LOGI("find method2 ");
int result=(*env)->CallIntMethod(env,obj,method2,4,5);
LOGI("c code result=%d", result);
}
JNIEXPORT void JNICALL Java_com_example_jnidemo_DataProvider_callmethod3
(JNIEnv *env, jobject obj){
jclass dpclazz = (*env)->FindClass(env,"com/example/jnidemo/DataProvider");
if(dpclazz==0){
LOGI("find class error");
return;
}
LOGI("find class ");
jmethodID method3 = (*env)->GetMethodID(env,dpclazz,"printString","(Ljava/lang/String;)V");
if(method3==0){
LOGI("find method3 error");
return;
}
LOGI("find method3 ");
(*env)->CallVoidMethod(env,obj,method3,(*env)->NewStringUTF(env,"haha in c"));
}
JNIEXPORT void JNICALL Java_com_example_jnidemo_DataProvider_callmethod4
(JNIEnv *env, jobject obj) {
jclass dpclazz = (*env)->FindClass(env,"com/example/jnidemo/DataProvider");
if(dpclazz==0) {
LOGI("find class error");
return;
}
LOGI("find class ");
jmethodID method4 = (*env)->GetStaticMethodID(env,dpclazz,"printStaticStr","(Ljava/lang/String;)V");
if(method4==0) {
LOGI("find method4 error");
return;
}
LOGI("find method4 ");
//3.调用一个静态的java方法
// void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
(*env)->CallStaticVoidMethod(env,dpclazz,method4,(*env)->NewStringUTF(env,"static haha in c"));
}
#ifdef __cplusplus
}
#endif
#endif
效果图:
从效果图中我们可以看出我们成功的使用jni通过.c调用了java的代码
最后分析我们的.c代码:
-观察代码, 首先通过观察.c代码中的每个方法我们可以看出每个方法中的代码步骤都是一样的,可能就是每个方法传入的参数有点不同
- 分析代码,通过观察我们是知道每个方法基本步骤是一样的,其中每个步骤代表一个方法,所以我就打算只具体分析一个方法然后大家自己举一反三
方法callMmethod1代码再现:
//1 . 找到java代码的 class文件
// jclass (*FindClass)(JNIEnv*, const char*);
jclass dpclazz = (*env)->FindClass(env,"com/example/jnidemo/DataProvider");
if(dpclazz==0){
LOGI("find class error");
return;
}
LOGI("find class ");
//2 寻找class里面的方法
// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID method1 = (*env)->GetMethodID(env,dpclazz,"helloFromJava","()V");
if(method1==0){
LOGI("find method1 error");
return;
}
LOGI("find method1 ");
//3 .调用这个方法
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env,obj,method1);
根据我写的注释可以看到c调用Java方法有三步:
- 找到你需要调用java中代码的class文件 FindClass
- 得到java类中你需要调用的方法 GetMethodID
- 调用2中这个方法 CallVoidMethod
我们之前讲了jni.h文件中有一个结构体JNINativeInterface指向了JNIEnv,所以像我们的FindClass(),GetMethodID(),CallVoidMethod()都是我们结构题中的方法,具体的大家可以打开jni.h文件查看,
方法参数解释:
方法 一. jclass (FindClass)(JNIEnv, const char);*
jclass表示返回值,和java中的class是一回事,只不过jni中为了区分前面加了一个j
JNIEnv* 表示我们的JNINativeInterface结构体
const char* 表示一个java中String字符串,通常传入类所在路径,例如我们这里类DataProvider在com/example/jnidemo/DataProvider路径下
方法二. jmethodID (GetMethodID)(JNIEnv, jclass, const char, const char);**
jmethodID (GetStaticMethodID)(JNIEnv, jclass, const char*, const char*);
为什么这个方法字体这么大,我是告诉大家,如果你定义的java方法是static修饰的话,想通过jni去调用我们必须采用CallStaticVoidMethod方法,才能成功的得到到静态的java代码,
比如我们这里的DataProvider类中的printStaticStr()方法使用static修饰,所以我们在.c代码中可以看到我们是用的这个带有Static方法调用的
jmethodID 返回值,和我们java中的Method一样
JNIEnv* 表示我们的JNINativeInterface结构体
jclass 我们第一不走中得到的类
const char* 表示一个java中String字符串,通常传入所需调用的方法名,如我们需要调用java的第一个方法名为:helloFromJava
const char* 表示一个java中String字符串,通常传入所需调用的方法名的签名,
怎么得到签名?
1.首先打开cmd来到我们工程的bin/classes
2.输入命令 javap -s 包名.类名,如我这里的是javap -s com.example.jnidemo.DataProvider
其中图中红色箭头部分为我们的DataProvider中的方法
白色箭头部分为我们的DataProvider方法的签名,如从图中我们可以看到我的第一个方法helloFromJava()的签名为 ()V
分析签名:
-
第一部分:()表示我们方法的参数的括号,如图中我们第二个方法Add有两个int的参数,所以它的()为(II),其中一个I表示一个int,I为int的开头字母
-
第二部分:V表示我们方法的返回值,因为我们第一个方法helloFromJava()的返回值为void,所以用void的第一个字母表示就是大写的V,如果像我们实例中的Add方法返回值为int所以,是大写的I
以此类推…
方法三:1.void (CallVoidMethod)(JNIEnv, jobject, jmethodID, …);
2.void (CallStaticVoidMethod)(JNIEnv, jclass, jmethodID, …);
3.jint (CallIntMethod)(JNIEnv, jobject, jmethodID, …);
4.jint (CallStaticIntMethod)(JNIEnv, jobject, jmethodID, …);*
首先我们可以看到这是三个方法几乎长的一模一样,首先参数是一定一样的
区别在于:
-
第一个方法是调用java中返回值为空且没有带static关键字的方法
-
第二个方法加入了Static,通过我们刚才第二步中的讲解我们可以很快知道这个方法是用来调用java中使用了static关键字的方法,如果java中的方法使用static关键字修饰,我们就必须采用此方法,例如我们这里的printStaticStr()方法
-
第三个方法表示调用java中返回值为int型的且没有带static关键字的方法,其他的返回值以此类推
-
第四个方法表示调用java中返回值为int类型带有static关键字的方法,以此类推
JNIEnv* 表示我们的JNINativeInterface结构体
jobject 表示我们的类对象,我们这里指的是DataProvider对象,
jmethodID 第二个方法中得到的Mehtod
…表示参数个数可以扩展,例如我们的第二个方法Add()中需要我们传入两个int型的参数,所以这里我们就可以将参数扩展,而第一个方法helloFromJava()是不需要传入参数的,所以不用扩展
以此类推
完结!
8、native和调用的java代码不在同一个类
为了对比起见我直接在第七节中直接增加代码
现在我在MainActivity中定义一个native方法然后调用DataProvider中的java方法
MainActivity:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
static{
System.loadLibrary("Hello");
}
private DataProvider dp;
public native void call_DP();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dp = new DataProvider();
}
public void click1(View view){
dp.callmethod1();
}
public void click2(View view){
dp.callmethod2();
}
public void click3(View view){
dp.callmethod3();
}
public void click4(View view){
dp.callmethod4();
}
public void click5(View view){
call_DP();
}
}
只是在第七节的基础上增加了一个按钮点击native修饰的call_DP()方法
生成一个MainActivity的头文件,复制到jni目录下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnidemo_MainActivity */
#ifndef _Included_com_example_jnidemo_MainActivity
#define _Included_com_example_jnidemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnidemo_MainActivity
* Method: call_DP
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_call_1DP
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
注意:本来我是不想贴这里的代码出来的,细心的同学不知道有没有发现之前我们在java中定义native方法的方法名是不会加入下划线的“”,但是在这里我们的java的native方法我故意加了一个下划线“”,然后在生成的.h文件中神奇的事情发生了,以前我们生成的.h的native方法的名字一般是:Java_包名_类名_方法名,可以看到下划线“"已经被我们的jni给用了所以如果我们的方法名中有下划线的“”通过会在方法名中的下划线的“_”的后面添加一个1,根据我们的例子生成的.h文件名字可以清楚的看到,之前我们定义的方法名为:call_Dp,生成的.h文件名为:call_1Dp,以此类推
在之前的.c文件增加如下代码
JNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_call_1DP
(JNIEnv *env, jobject obj){
//1 . 找到java代码的 class文件
// jclass (*FindClass)(JNIEnv*, const char*);
jclass dpclazz = (*env)->FindClass(env,"com/example/jnidemo/DataProvider");
if(dpclazz==0){
LOGI("MainActivity find class error");
return;
}
LOGI("MainActivity find class ");
//2 寻找class里面的方法
// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID method1 = (*env)->GetMethodID(env,dpclazz,"helloFromJava","()V");
if(method1==0){
LOGI("MainActivity find method1 error");
return;
}
LOGI("MainActivity find method1 ");
//3 .调用这个方法
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env,obj,method1);
}
从第7节过来我相信你一定可以看的懂这个方法的意思,所以我就不解释了
最后运行,我们在MainActivity中定义的native方法是否可以调用到DataProvider类中的代码呢?
很不幸,程序挂掉了,错误如下:
错误信息大概的意思是:DataProvider不是MainActivity实例,而且从Log中可以看到类和方法的Log都找到了,只有最后调用的log没有显示,很显然是最后一个方法出了问题
这是为什么呢?这里的c调用java的代码不是一模一样吗?怎么会出错呢?
我们看到第三个步骤的方法:
void (CallVoidMethod)(JNIEnv, jobject, jmethodID, …);
看到第二个参数,之前我们在第七节讲解了jobject表示native的类对象,但是我们这里有两个类对象,所以jni不能识别,所以我们自己去指定该对象,
那就好办了,既然jni不能识别,我们就去查找jni.h中jni能识别的,很快我们可以找到4个关于new对象的方法:
最后我们修改代码,如下:
JNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_call_1DP
(JNIEnv *env, jobject obj){
//1 . 找到java代码的 class文件
// jclass (*FindClass)(JNIEnv*, const char*);
jclass dpclazz = (*env)->FindClass(env,"com/example/jnidemo/DataProvider");
if(dpclazz==0){
LOGI("MainActivity find class error");
return;
}
LOGI("MainActivity find class ");
//2 寻找class里面的方法
// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID method1 = (*env)->GetMethodID(env,dpclazz,"helloFromJava","()V");
if(method1==0){
LOGI("MainActivity find method1 error");
return;
}
LOGI("MainActivity find method1 ");
//3 .调用这个方法
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject dpobj=(*env)->AllocObject(env,dpclazz);
(*env)->CallVoidMethod(env,dpobj,method1);
}
再次运行,成功在MainActivity定义一个native方法调用了DataProvider中的方法
log效果图: