一:NDK的函数调用时出现 “UnsatisfiedLinkError : 函数名” 这样的异常
解决办法:
-
网上能找到的提醒无一不是让你去仔细检查NDK中的函数命名是否正确,也就是“
JAVA_调用该函数的JAVA类名(完整路径区分大小写)_函数名
”这样的JNI格式。然而我在确定这个命名准确无误后仍得到这个异常。 -
为什么呢?反复捣鼓后幡然醒悟:我用的是C++(文件后缀用
.cpp
,按C++来编译)。
由于JNI是采用了C风格的函数命名,所以如果用C++编译,则必须记得在每个函数前加上extern "C"
的标记。这个东西记得NDK的docs里面有提到(现在找不到在哪了,那些docs只能自己一份份地看),实际写的时候还是忘了。
二:进行jfieldID
和jmethodID
的cache
时,不能转化为global reference
-
解答:local/global reference这些概念,只是跟
jobject
的指针相关(包括它的派生类如jclass
),因为这些指针所指空间,如果为local
的话,则会在作用域结束时被回收,所以做cache时才需要转化为global reference
。 -
而
jfieldID
和jmethodID
查看头文件便可知与jobject
无关,只是一些native的空间分配,因此与local/global这些JNI加入的新概念无关,按C/C++的做法去cache即可。
三:为何调用GetFieldID
时,传入参数如果为类则必须按照这种格式:“Ljava/lang/String;
”,而调用FindClass
时则直接“"java/lang/String
"即可?
-
解答:因为
GetFieldID
函数要解析类型签名,这些签名既包括int、bool这些原生类,也包括String或者自定义的JAVA类,因此需要对String这些需要指定类路径的设计一个额外的格式,以作为区分(估计可以提高解析签名的效率)。 -
而
FindClass
函数不针对int、bool那些原生类(这些在C++中不算class),因此无需要加入这种格式上的区分。
四:能够创建的local ref
的数量是有限的吗?如果要创建一个比较大的jobjectArray
并返回怎么办?
-
解答:
local ref
的数量的确是有限的,这个限制大概比512小一点点(JNI背后是用一张表来记录你创建的每个local ref,这个表有长度限制),意味着你调用一个native函数时不能创建超过这个数量的local ref。 -
于是你试图访问/返回一个超过该限制长度的
jobjectArray
时就很郁闷了,不过这是因为你对Get/SetObjectArrayElement
这一对函数产生了一个误解。以SetObjectArrayElement
为例,你传入一个local ref的参数,看上去是把这个local ref给放到了你的array中,实际上这个local ref并未放进去,放进去的只是ref指向的内容本身,所以这个函数执行后那个local ref就可以删除了,所以就不存在长度限制的问题。同样,GetObjectArrayElement
每次会新产生一个local ref,所以你用完要立刻删掉,不然就可能超过local ref的数量限制。
这个误解应该很普遍吧?其实Sun官方的JNI Spec文档中都有写,范例代码也都这么做了,不过还是强调得不够,第一次写的时候还是很容易被表面代码所误导。
五:GetStringRegion
和GetStringUTFRegion
的特别提醒
- 这两个函数都有一个
len
的参数,表示Unicode
字符的个数,但是要注意,这两个函数会在copy
完字符串后,自动把数组的len+1
个元素赋为0
!因此分配空间时就要分配len+1
,否则嘛。。你的程序随时crash(当你执行delete[]
时)
六:如何在 JNI 中打 Log
第一步:在对应的mk
文件中加入:LOCAL_LDLIBS := -llog
第二步:在要使用LOG的cpp
文件中加入:
#include <android/log.h>
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "keymatch", __VA_ARGS__)
第三步:这样就可以使用了:LOGD("我要看到的调试信息^_^");
这样,在logcat端看到的输出是:
D/keymatch( 32):我要看到的调试信息^_^
如果想改变输出中的各项内容,可以参考相应颜色的标示,比如,如果想定义LOGE
,就可以把上面的ANDROID_LOG_DEBUG
改成ANDROID_LOG_ERROR
,同理,LOGI
神马的也都以此类推:
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "ProjectName", __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "ProjectName", __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , "ProjectName", __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN , "ProjectName", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , "ProjectName", __VA_ARGS__)
当然,如果不嫌麻烦,也可以直接使用__android_log_print
函数,而不define
定义LOGxxx
另外,有文章称此方法在编译动态库的时候可能会出问题,会提示cannot find -llog
的错误。意思是找不到liblog.so
这个库文件。因此需要改成 LOCAL_LDLIBS:= -L$(SYSROOT)/usr/lib -llog
才可以正常编译。但是我这边编译动态库的时候,好像不用这样改也行,没发现编译时提示“cannot find -llog”的错误。