jni的使用详细Word文档格式.docx
《jni的使用详细Word文档格式.docx》由会员分享,可在线阅读,更多相关《jni的使用详细Word文档格式.docx(26页珍藏版)》请在冰点文库上搜索。
在JNINativeInterface结构体里定义了很多的函数指针:
通过函数指针就可以调用这些方法。
怎么拿这个JNINativeInterface结果体呢?
通过结构体指针就能拿到结构体:
*JNIEnv
而
这里的JNIEnv*是一个结构体指针的指针,所以通过*env拿到的就是JNIEnv,然后再*JNIEnv就拿到了JNINativeInterface结构体
写成一个语句就是:
*(*env)==JNINativeInterface结构体
4.编译.c文件生成动态库
编译C文件需要在项目的根目录下执行编译命令
编译.c文件用的是ndk中的ndk-build命令,而这个命令需要运行在Linux的环境下,所以需要启动Cygwin程序,在Cygwin的命令窗口中进入到安卓项目的根目录,然后执行:
/cygdrive/e/android-ndk-r7b/ndk-build命令,如下图:
这里出了一个错误,第一句:
AndroidNDK:
YourAPP_BUILD_SCRIPTpointstoanunknownfile:
./jni/Android.mk说指向了一个未知的文件
所以需要把一个叫:
Android.mk的文件创建出来放到项目中的jni文件夹中新建一个文件,输入文件名为:
Android.mk
这个Android.mk文件就是用来告诉编译器一些规,如要编译的是哪个C文件呀,编译出来的文件起个什么名字呀。
5、创建Android.mk
那这个Android.mk应该怎么写呢?
可以查找帮助文档,如下图:
把红色的拷到Android.mk文件中,然后指定交叉编译后的文件名与指定要编译哪个C文件。
接着再到Cygwin程序中执行/cygdrive/e/android-ndk-r7b/ndk-build命令,这时会报错这么一个错误:
这是因为C文件中还有一些依赖的头文件还没导入,导入:
#include<
jni.h>
,这时再运行就OK了,hello.c代码如下:
stdio.h>
jstringJava_com_itheima_ndkhelloword_DemoActivity_helloWorldFromC(JNIEnv*env,jobjectobj){
return(*(*env)).NewStringUTF(env,"
hellofromc"
);
}
运行结果如下:
编译成功后会在项目中生成这两些东西:
6.Java代码load动态库.调用native代码
加载之后,在需要的地方调用:
helloWorldFromC()方法即可
查看生成的动态库
进入项目中的bin目录,把apk文件解压,可以看到在:
ndkHelloWorld_2\lib\armeabi目录下有:
libhello.so这个动态库文件
当把这个apk文件安装到手机手,这个动态库的位置在:
data/data/
目录下,可以看到这个
动态库可以被其他应用程序读取和执行。
修改动态库的权限为:
可读可写可读可读
-rw-r--r—
二进制-110100100
换成十进制为:
644,分析如图:
用chmod644libhello.so就可以改变libhello.so的权限,如果用chmod000libhello.so则把它的权限改为全部不可读写不可执行。
命令执行如图所示:
带下划线的方法名注意事项
在Java中声明的native方法名一般要不带下划线,如:
publicnativeStringsay_hell();
那么在C文件中映射方法时为:
Java_com_itheima_ndk4_DemoActivity_say_hello,这是不对的,根据JNI的规范这是说在
DemoActivity类的下面有个内部类say,在say中又有个内部类叫hello,解决办法:
在方法的下划线后加入1,相当于转义了一样。
如上面的代码改为:
Java_com_itheima_ndk4_DemoActivity_say_1hello
问题:
如果有一个方法名为:
hello_1_from(),这又怎么写映射呀?
这写起来会很麻烦,这时就可以使用一个JDK提供的工具:
javah,
它可以帮我们生成jni的头文件,然后在C代码中导入头文件即可。
在CMD命令行输入:
javah可以可看该命令的帮助信息:
javah命令使用
在D:
\test目录下建一个a.java文件,代码为:
publicclassa{
publicnativeStringsay_Hello();
编译该文件生成.class文件,再执行javaha,这时就可以在D:
\test目录看到生成了一个“a.h”文件,用记事本打开:
/*DONOTEDITTHISFILE-itismachinegenerated*/
#include<
/*Headerforclassa*/
#ifndef_Included_a
#define_Included_a
#ifdef__cplusplus
extern"
C"
{
#endif
/*
*Class:
a
*Method:
say_Hello
*Signature:
()Ljava/lang/String;
*/
JNIEXPORTjstringJNICALLJava_a_say_1Hello
(JNIEnv*,jobject);
红色框中的即为自动生成的方法签名(映射)。
如果Java的.class文件是带包名的,那么执行命令要带上包名:
javahaa.bb.a
所以我们想要生成安卓项目里的方法签名,就可以到classes目录下执行javah命令,然后把生成的.h的文件拷贝到项目中的jni目录中,
然后在C代码中用“#include“aa_bb_a.h””的方式把生成的头文件引入,这里用双引号,是说引入的是C源代码文件的当前目录的资源,如果要引用ndk中的
这个目录下的头文件引入要用<
>
,如#include<
,
引用头文件之后,拷贝那个生成的native方法到C文件中,加以修改即可。
Android.mk文件详解
#local_path代表的是当前android.mk文件所在的路径
#$()代表的是一个函数.
LOCAL_PATH:
=$(callmy-dir)
#CLEAR_VARS清空变量
#清空所有以LOCAL_开头的变量里面的内容(不会清空LOCAL_PATH里面的数据);
include$(CLEAR_VARS)
#定义编译后的c代码库的名称.
LOCAL_MODULE:
=Hello//这个名字我们可以显示的在名字前面加上:
lib,但不能在后面加“.so“,否则会报错。
#定义makefile编译的源文件
#依赖的头文件是不需要指定的.
LOCAL_SRC_FILES:
=Hello.c
#生成一个动态的代码库
include$(BUILD_SHARED_LIBRARY)
#include$(BUILD_STATIC_LIBRARY);
生成一个静态的代码库
#静态代码库的作用主要就是用来提供一些库函数编译的时候可能需要用到静态代码库.
Ndk中的库文件保存在:
静态库和动态库的区别?
一般来说静态库扩展名.a静态库的体积比较大:
包含了所有的可执行的代码,使用的时候就不需要依赖其他代码库了
动态库扩展名.so(Windows系统下的动态库为.dll)动态库的体积很小:
包含了可执行代码的引用.在使用时,依赖于其他库,在用到哪个依赖库时才会去找那个引用,安卓手机中的库文件在:
,动态库就是来这里找所依赖的库文件的。
Android.mk的含义
LOCAL_PATH:
=$(callmy-dir)
LOCAL_PATH是定义源文件在哪个目录用的.
my-dir是个定义的宏方法,$(callmy-dir)就是调用这个叫my-dir的宏方法,这个方法返回值就是
Android.mk文件所在的目录
include$(CLEAR_VARS)
CLEAR_BARS变量是buildsystem里面的一个变量
这个变量指向了所有的类似LOCAL_XXX的变量,
执行完这一句话,这个编译系统就把所有的类似
LOCAL_MODULE,_SRC_FILELOCALS,LOCAL_STATIC_LIBRARIES,...这样的变量都清除掉
但是不会清除掉LOCAL_PATH
LOCAL_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.cc
LOCAL_LDLIBS+=-llog-lvmsagent-lmpnet-lmpxml-lH264Android
//指定需要加载一些别的什么库.
ndk开发的常见错误
1.忘记书写android.mk文件anunknownfile:
./jni/Android.mk
$ndk-build
./jni/Android.mk
/cygdrive/c/android-ndk-r7b/build/core/add-application.mk:
133:
***AndroidNDK:
Aborting...。
停止。
2.android.mk文件里面有非法字符
jni/Android.mk:
2:
***遗漏分隔符。
3.ndk-build命令生成so文件的时候有的时候会报出来错误.可以采用ndk-buildclean清空生成的中间文件.再调用ndk-build重新编译
SharedLibrary:
libHello.so
./obj/local/armeabi/objs/Hello/Hello.o:
filenotrecognized:
可以采用ndk-buildclean清空生成的中间文件.
4.java.lang.UnsatisfiedLinkError:
helloFromC
一般就是库文件没有正常的加载到java虚拟机:
System.loadLibrary();
或者是方法的签名不正确
或者是库文件的名称不正确.
java.lang.UnsatisfiedLinkError:
Couldn'
tloadHell0:
findLibraryreturnednull
5.make:
***[obj/local/armeabi/objs/Hello/Hello.o]Error1
c语言的语法出现了问题.
6.程序运行突然异常终止,logcat控制台打印很多堆栈的寄存器地址.代码的业务逻辑出了问题.
android/log.h>
#defineLOG_TAG"
Daizhenliang"
#defineLOGD(...)__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#defineLOGI(...)__android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
在c代码中使用logcat
1、C代码中增加点这里复制代码
#include引入的头文件都是在:
android-ndk-r7b\platforms\android-8\arch-arm\usr\include这个目录下的
这里引入的android/log.h头文件在:
由于这里使用到了log.h这个头文件,那么在编译的时候要把log.h对应的二进制代码库(liblog.so)引入到交叉编译的系统里面
2、Android.mk文件增加LOCAL_LDLIBS+=-llog//把一个函数库引入到交叉编译的系统里面
这里引入了一个“liblog.so”函数库,引入的方式为–l加log(减掉前面lib,与后面的.so),即–llog就引入了loblog.so这个函数库
3、在方法体中使用LogCat
LOGI("
x=%d"
x);
LOGD("
y=%d"
y);
也可以直接这样用:
注:
C语言中的LogCat不支持中文,而且不能这样写:
LOGD(“ab”,”ccc”);
这样写只会输出“ab”
java与c之间的数据传递
具体演示代码:
ndkpassdata项目
演示代码在:
ndkpassdata项目中
●在C语言中没有jstring的类型(也就是Java的String类型),可以通过个函数把jstring转的成C语言中字符数组:
char*Jstring2CStr(JNIEnv*env,jstringjstr)
{
char*rtn=NULL;
jclassclsstring=(*env)->
FindClass(env,"
java/lang/String"
jstringstrencode=(*env)->
NewStringUTF(env,"
GB2312"
jmethodIDmid=(*env)->
GetMethodID(env,clsstring,"
getBytes"
"
(Ljava/lang/String;
)[B"
jbyteArraybarr=(jbyteArray)(*env)->
CallObjectMethod(env,jstr,mid,strencode);
//String.getByte("
jsizealen=(*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);
//
returnrtn;
上面的语句中:
char*rtn=NULL;
这里的NULL在C语言中表示数据为空的内存地址,要使用这个类型,需要导入#include<
string.h>
●可以通过(*env)->
NewStringUTF(env,"
hello_from_c"
将一个C语言的字符数组变成一个jstring返回给Java
●
通过(*evn)->
GetIntArrayElements(JNIEnv*,jintArray,jboolean*)方法把一个Java的int数组转的为C语言的int数组
这个方法返回的是一个jint*这是一个指针,也就是说是一个内存地址,这个地址就是这个jintArray数组的首地址。
C语言中获取数组长度的函数:
这个函数的返回值是:
jsize,通过查找看到:
它是一个jint(Java中的int),它和C语言中的int其实是一样的,因为都是4个字节大小的。
所以这个值可以直接赋值给C语言中的int
所有的基本类型的数组都属于jarray:
int*adrress=(*env)->
GetIntArrayElements(env,arr,0);
该方法返回的是数组的首地址,示例代码如下:
JNIEXPORTjintArrayJNICALLJava_com_itheima_logcat_provider_DataProvider_intMethod
(JNIEnv*env,jobjectobj,jintArrayarr){
int*adrress=(*env)->
//返回arr数组的首地址
intlen=(*env)->
GetArrayLength(env,arr);
inti;
for(i=0;
i<
len;
i++){
*(adrress+i)+=10;
returnarr;
//因为在C中改变的数组的地址和在Java中的是一样的,所以直接返回传进来的Java数组即可
真实企业开发jni的流程:
1.全新的项目.
java程序员抽象natvie()->
生成头文件->
c工程师实现头文件.
2.历史存在的项目.
c语言已经写好了.
包装这个c代码,包装成一个适应于java代码调用的c代码.
开发步骤:
1、了解C函数,在看C代码时,不需要去看代码体,只需要关心返回值、函数名、参数这三样
2、写安卓项目,根据C函数写native方法
3、javah生成头文件
4、根据头文件写出C代码,把之前的C函数的代码直接复制过来
5、完成剩下的所有操作。
C语言回调java方法
具体实例演示代码在:
ndkcallback项目
1、获取类
这是JNINativeInterface结构体中的一个函数指针,该方法使用演示:
jclassclazz=(*env)->
FindClass(env,"
com/itheima/c/MethodProvider"
相当于Java中的Class.forName(className),不同的是Java中返回的是Class对象,而C中的方法返回的是Class对象的地址,如果找不到这个类,则返回0。
2、获取方法
获取静态方法:
获取非静态方法:
返回值也是一个地址,如果找不到这个方法就返回0
方法类型签名介绍:
从这里可以看到jemthodID是一个构造体的地址的别名。
该方法的使用演示:
jmethodIDmethodClazzId=(*env)->
GetMethodID(env,clazzId,"
nullMethod4Java"
()V"
查看方法类型的签名:
执行命令,可以看到结果如下:
3、调用方法
调用返回值为void的方法
(*CallIntMethod)(JNIEnv*,jobject,jmethodID,...);
//调用返回值为int的方法,还有返回返回值为其他的方法类似,查jni.h
方法的使用示例:
(*env)->
CallVoidMethod(env,obj,methodClazzId);
调用静态方法:
参数列表
C语言中常用类型的联合体:
不能直接newActivity对象,因为平时我们创建Activity我们都没有new过,是系统自动new出来的,系统在进行一系列的初始化时把上下文绑到Activity中,所以如果我们直接new的话这个Activity就没有上下文环境这个