JNI问题<转>
JNI是Java本地接口(Java Native Interface)的简称。它定义了托管代码(用Java编程语言写的)与本地代码(用C/C++写的)交互的一种方式(译者注:这里的托管代码应该理 解成受Java运行时环境监管的代码)。它与厂商无关,支持从动态共享库中加载代码,虽然繁琐但有时是合理有效的。
你应该通读JNI spec for J2SE 6来获取对JNI是如何工作的以及它有什么可用的功能的一个认知。在你读第一遍的时候可能对JNI的某些方面的理解不会立刻就清晰,所以你会发现一下的章节可能会对你有所帮助。JNI Programmer's Guide and Specification里有更为详细的资料。
JavaVM and JNIEnv-JavaVM和JNIEnv
JNI定义了2种关键的数据结构,"JavaVM"和"JNIEnv"。它们本质上都是指向函数表的指针的指针。(在C++的版本中,它们被定义成 类(译者注:准确来说是C++中的结构体),类里面包含一个指向函数表的指针,以及与JNI函数一一对应的用来间接访问函数表的成员函数。)JavaVM 提供了"调用接口"的函数,允许你创建和销毁一个JavaVM。理论上每个进程你可以有多个JavaVM,但Android只允许有一个。
JNIEnv提供了大多数的JNI函数。你的本地方法都会接收JNIEnv作为第一个参数。
JNIEnv用于本地线程存储。因此,你不能在线程间共享同一个JNIEnv。如果一个代码段没有其他方式获取它自身线程的JNIEnv, 你可以共享JavaVM,用GetEnv来获取线程的JNIEnv。(假设这个线程有一个JavaVM;参见下面的 AttachCurrentThread。)
C版本的JNIEnv和JavaVM的声明是异于C++版本的。"jni.h"头文件根据被包含在C或是C++文件中来提供不同类型的 typedefs。因此在被两种语言包含的头文件中包含JNIEnv参数不是明智的选择。(换句话说:如果你的头文件中需要用到#ifdef __cplusplus,那么在有涉及到JNIEnv的内容的时候你需要做一些额外的工作。)
Threads-线程
所有的线程都是Linux的线程,由内核调度。它们通常由托管代码启动(使用Thread.start),但是也可以在别的地方创建它们,并把它们 连接到JavaVM上。比如,一个由pthread_create方法启动的线程可以用JNI的AttachCurrentThread或者 AttachCurrentThreadAsDaemon函数来连接。在线程没有被连接到JavaVM之前是不会有JNIEnv的,也无法发起JNI的调 用。
连接一个本地创建的线程会构造一个java.lang.Thread的对象,并把这个对象添加到"主"线程组里面,使之对调试者可见。对一个已被连接的线程调用AttachCurrentThread不做任何操作。
Android不会暂停正在执行本地代码的线程。如果垃圾收集器正在运行,或者是调试者发出了暂停的请求,Android会在它发起下一个JNI调用的时候暂停线程。
通过JNI连接的线程在它们退出之前必须调用DetachCurrentThread方法。如果直接编写这样的调用代码会显得很笨拙,在 Android2.0(Eclair)及更高的版本,你可以使用pthread_key_create来定义一个析构函数,并在析构函数里调用 DetachCurrentThread方法,析构函数会在线程退出之前被调用。
jclass, jmethodID, and jfieldID
如果你想在本地代码中访问一个对象的字段,你将会做以下事情:
- 取得FindClass得到的类的类对象引用
- 取得GetFieldID得到的字段的字段ID
- 用合适的方法取得字段的内容,比如GetIntField
同样的,要调用一个方法,你必须先得到一个类对象的引用和方法ID。这些ID通常来说只是指向内部的运行时数据结构。找到它们可能需要一些字符串的比较,但一旦你得到它们之后实际的字段取值或者方法调用将会非常的快。
Exceptions-异常处理
You must not call most JNI functions while an exception is pending. Your code is expected to notice the exception (via the function's return value, ExceptionCheck, or ExceptionOccurred) and return, or clear the exception and handle it.
大多数情况下,当程序发生异常的时候你是无法调用JNI模块的。因为你的代码不但需要标记出异常出现的位置(可以通过ExceptionCheck或者ExceptionOccurred的返回值),而且需要对这些所抛出的异常进行处理。
The only JNI functions that you are allowed to call while an exception is pending are:
仅在以下异常被抛出时,你可以调用JNI函数:
- DeleteGlobalRef
- DeleteLocalRef
- DeleteWeakGlobalRef
- ExceptionCheck
- ExceptionClear
- ExceptionDescribe
- ExceptionOccurred
- MonitorExit
- PopLocalFrame
- PushLocalFrame
- Release<PrimitiveType>ArrayElements
- ReleasePrimitiveArrayCritical
- ReleaseStringChars
- ReleaseStringCritical
- ReleaseStringUTFChars
Many JNI calls can throw an exception, but often provide a simpler way of checking for failure. For example, if NewString returns a non-NULL value, you don't need to check for an exception. However, if you call a method (using a function like CallObjectMethod), you must always check for an exception, because the return value is not going to be valid if an exception was thrown.
多数JNI在被调用时能够抛出一个异常,但通常还有一种更简单的方式来检验该调用是否出现失效。比如说,如果调用NewString方法返 回一个非null值,那么你就不需要再对其进行异常检查。但如果你调用的是一个类似CallObjectMethod这样的函数时,你还是必须做异常的检 查处理,这是因为当出现异常时,调用该模块并不会返回一个有效值。
Note that exceptions thrown by interpreted code do not unwind native stack frames, and Android does not yet support C++ exceptions. The JNI Throw and ThrowNew instructions just set an exception pointer in the current thread. Upon returning to managed from native code, the exception will be noted and handled appropriately.
需要注意的是,由翻译码抛出的异常并不会释放堆栈帧空间,且目前的Android系统也不支持C++异常处理。所以JNI的Throw和 ThrowNew指令仅仅只是在当前的线程中设置了一个异常处理指针,而当从本地代码返回托管代码时,该异常才能被标注并得到有效的处理。
Native code can "catch" an exception by calling ExceptionCheck or ExceptionOccurred, and clear it with ExceptionClear. As usual, discarding exceptions without handling them can lead to problems.
当然,本地代码可以通过调用ExceptionCheck或者 ExceptionOccurred函数来捕获相应的异常,也能通过调用ExceptionClear函数来清理该异常。同样,如果对抛出的异常不作处理也会造成问题。
There are no built-in functions for manipulating the Throwable object itself, so if you want to (say) get the exception string you will need to find the Throwable class, look up the method ID for getMessage "()Ljava/lang/String;", invoke it, and if the result is non-NULL use GetStringUTFChars to get something you can hand to printf(3) or equivalent.
由于对Throwable对象的操作不支持内置函数,所以如果你需要获取相应的异常字符串,你首先需要找到Throwable类,然后找到 getMessage "()Ljava/lang/String;”方法的ID,调用它,假如调用后返回的结果是非null值,则需要通过GetStringUTFChars 方法来把获取的结果传给printf(3)或者是采用其他类似的方法完成这个工作。
JNI does very little error checking. Errors usually result in a crash. Android also offers a mode called CheckJNI, where the JavaVM and JNIEnv function table pointers are switched to tables of functions that perform an extended series of checks before calling the standard implementation.
JNI很少对错误进行检查。而错误常会导致程序崩溃。Android为此提供了一种CheckJNI处理模式,当JAVA虚拟机以及JNIEnv函数表指针被切换到函数列表时,该模式会在调用标准实现之前做出一系列的附加检查措施。
The additional checks include:
附加的检查包括:
- Arrays: attempting to allocate a negative-sized array.
- 数组:尝试分配一个长度大小为负数的数组。
- Bad pointers: passing a bad jarray/jclass/jobject/jstring to a JNI call, or passing a NULL pointer to a JNI call with a non-nullable argument.
- 坏指针:将一个错误的jarray/jclass/jobject/jstring传递给JNI调用,或者是带一个非空参数将空指针传递给JNI调用。
- Class names: passing anything but the “java/lang/String” style of class name to a JNI call.
- 类名称:JNI调用时未采用类似于“java/lang/String”这种格式的类名。
- Critical calls: making a JNI call between a “critical” get and its corresponding release.
- 临界调用:在“临界区”获取或者释放的时候进行JNI调用操作。
- Direct ByteBuffers: passing bad arguments to NewDirectByteBuffer.
- 直接ByteBuffers:将错误的参数传递给NewDirectByteBuffer函数。
- Exceptions: making a JNI call while there’s an exception pending.
- 异常:当发生异常时进行JNI调用。
- JNIEnv*s: using a JNIEnv* from the wrong thread.
- JNIEnv*s: 在错误的线程中使用JNIEnv指针。
- jfieldIDs: using a NULL jfieldID, or using a jfieldID to set a field to a value of the wrong type (trying to assign a StringBuilder to a String field, say), or using a jfieldID for a static field to set an instance field or vice versa, or using a jfieldID from one class with instances of another class.
- jfieldIDs:使用空的jfieldID,或是通过jfieldID设置变量值时指定了错误的类型(比如说尝试将一个 String型变量设置为StringBuilder类型),又或者是通过jfieldID将一个静态变量设置为实例变量,反之亦然,还有可能是,通过 jfieldID将一个类的对象指定为另外一个类的对象。
- jmethodIDs: using the wrong kind of jmethodID when making a Call*Method JNI call: incorrect return type, static/non-static mismatch, wrong type for ‘this’ (for non-static calls) or wrong class (for static calls).
- jmethodIDs:运用Call*Method的JNI调用时,jmethodID方法运用错误:返回错误的类型值,静态/非静态匹配错误,‘this’类型错误(当使用非静态调用时)以及错误的类定义(使用静态调用时)。
- References: using DeleteGlobalRef/DeleteLocalRef on the wrong kind of reference.
- 引用:调用DeleteGlobalRef/DeleteLocalRef函数时使用了错误的引用。
- Release modes: passing a bad release mode to a release call (something other than 0, JNI_ABORT, or JNI_COMMIT).
- Release模式:进行release调用时使用了错误的release模式(即选择0,JNI_ABORT 或者JNI_COMMIT以外的值)
- Type safety: returning an incompatible type from your native method (returning a StringBuilder from a method declared to return a String, say).
- 类型安全:从你的本地方法中返回了一个不匹配的类型值(比如从一个声明为String作为返回值的方法中返回一个StringBuilder类型的返回值)
- UTF-8: passing an invalid Modified UTF-8 byte sequence to a JNI call.
- UTF-8编码:将一个无效的Modified UTF-8字节串传递给JNI调用。
(Accessibility of methods and fields is still not checked: access restrictions don't apply to native code.) (可访问的方法和变量在此并没有被检查:这是因为访问限制并不适用于本地代码)
There are several ways to enable CheckJNI. 下列方法可以用来使能CheckJNI模式。
If you’re using the emulator, CheckJNI is on by default. 如果你用的是模拟器,那么CheckJNI模式默认已经被开启。
If you have a rooted device, you can use the following sequence of commands to restart the runtime with CheckJNI enabled: 如果你的设备已经获取root权限,可以通过以下的命令行来重新启动运行设备,使能CheckJNI模式:
adb shell stop adb shell setprop dalvik.vm.checkjni true adb shell start
In either of these cases, you’ll see something like this in your logcat output when the runtime starts: 然后,你可以通过查看设备启动运行阶段时打印的logcat信息来确认是否开启该模式。
D AndroidRuntime: CheckJNI is ON
If you have a regular device, you can use the following command: 如果你手里的机器只是普通发行版本,你需要使用以下的命令行来开启模式:
adb shell setprop debug.checkjni 1
This won’t affect already-running apps, but any app launched from that point on will have CheckJNI enabled. (Change the property to any other value or simply rebooting will disable CheckJNI again.) In this case, you’ll see something like this in your logcat output the next time an app starts:
该方法不会对已经运行的应用软件起作用,只能使之后启动的应用软件带有CheckJNI模式。(如果将property参数值作任意修改或 者是重启系统都就能重新禁用CheckJNI模式)。如果你采用这个方法,那么当任何一个应用软件启动时,你都会在logcat的输出窗口看到如下的信 息:
D Late-enabling CheckJNI
Native Libraries-本地库
You can load native code from shared libraries with the standard System.loadLibrary call. The preferred way to get at your native code is: 你可以通过标准的System.loadLibrary调用从共享库中载入本地代码。但你还有更好的渠道来获取本地代码,方法如下:
- Call System.loadLibrary from a static class initializer. (See the earlier example, where one is used to call nativeClassInit.) The argument is the "undecorated" library name, so to load "libfubar.so" you would pass in "fubar".
- 在静态类的初始化时调用System.loadLibrary。(可以参考之前的例子,比如调用nativeClassInit函数时)。将“原始”的库名作为参数,你将其传给"fubar"来载入"libfubar.so"动态链接库。
- Provide a native function: jint JNI_OnLoad(JavaVM* vm, void* reserved)
- 提供一个本地函数:int JNI_OnLoad(JavaVM* vm, void* reserved) 来实现。
- In JNI_OnLoad, register all of your native methods. You should declare the methods "static" so the names don't take up space in the symbol table on the device.
- 在JNI_OnLoad函数中注册你所有的本地方法。你可以将所有的方法都声明为“static”,这样就不会占用设备中符号列表上的空间。
The JNI_OnLoad function should look something like this if written in C++: 在C++中你可以参考如下方式来构造JNI_OnLoad函数:
jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } // Get jclass with env->FindClass. // Register methods with env->RegisterNatives. return JNI_VERSION_1_6; }
You can also call System.load with the full path name of the shared library. For Android apps, you may find it useful to get the full path to the application's private data storage area from the context object.
你还可以通过共享库的完整路径来调用System.load。在Android的应用软件中,你会发现,在对象的上下文中,通过获取完整路径来共享应用的私有数据存储空间是非常有用的方法。
This is the recommended approach, but not the only approach. Explicit registration is not required, nor is it necessary that you provide a JNI_OnLoad function. You can instead use "discovery" of native methods that are named in a specific way (see the JNI spec for details), though this is less desirable because if a method signature is wrong you won't know about it until the first time the method is actually used.
以上是推荐的方法,但并不是唯一方法。例如,明文注册并不是必要的环节同样,JNI_OnLoad函数的设置也不是。你可以通过对本地代码 做特殊命名(参考the JNI spec这一章的细节)来手动“寻找”它们,但是这个方法并不是那么让人满意,因为如果你在某个方法的标记上如果出了错,除非在第一时间这个方法就被调 用,否则你并将不会意识到它有问题。
One other note about JNI_OnLoad: any FindClass calls you make from there will happen in the context of the class loader that was used to load the shared library. Normally FindClass uses the loader associated with the method at the top of the interpreted stack, or if there isn't one (because the thread was just attached) it uses the "system" class loader. This makes JNI_OnLoad a convenient place to look up and cache class object references.
关于JNI_OnLoad的其他备注:你在上文中所调用的FindClass 函数都可以在共享库的类装载器中得到。通常来说,FindClass会使用相关方法的装载器来分析栈顶数据,但如果没有这样的装载器(有可能是已经附着到 线程上了),那么它也会使用“系统”的类装载器。这样做使得JNI_OnLoad很容易被找到,而且也能缓存类对象的引用。
64-bit Considerations-64-bit 环境注意事项
Android is currently expected to run on 32-bit platforms. In theory it could be built for a 64-bit system, but that is not a goal at this time. For the most part this isn't something that you will need to worry about when interacting with native code, but it becomes significant if you plan to store pointers to native structures in integer fields in an object. To support architectures that use 64-bit pointers, you need to stash your native pointers in a long field rather than an int.
Android当前多运行于32位平台环境中。理论上来说,它同样也能编译成64位版本,但这并不是最终的解决方案。一般来说,当你使用本 地代码的时候,你并不需要担心64位的版本问题。但是,假如你打算在某个对象中的integer变量的本地结构体中存储指针,那么64位版本就会变成为很 大的麻烦。因此,为了能够在64位架构下使用指针,你必须将你的本地指针存储为long形而非int型。
Unsupported Features/Backwards Compatibility-不支持的特性/向前兼容性
All JNI 1.6 features are supported, with the following exception: 除了以下情况外,所有的1.6版本的JNI的特性都被支持:
- DefineClass is not implemented. Android does not use Java bytecodes or class files, so passing in binary class data doesn't work.
- DefineClass方法还没有被实现。Android当前没有使用JAVA比特编码或者类,所以是无法传递给它们二进制类文件的。
For backward compatibility with older Android releases, you may need to be aware of: 对早期Android发布版本的向前兼容性,你需要注意以下几点:
- Dynamic lookup of native functions
- 动态查找本地函数
- Until Android 2.0 (Eclair), the '$' character was not properly converted to "_00024" during searches for method names. Working around this requires using explicit registration or moving the native methods out of inner classes.
- 到Android2.0(Eclair)版本为止,在搜索方法名称时,’$’字符还不能够被准确转换为"_00024"。为了解决这个问题,你可以使用明文注册或者是将本地方法从内部类中移出。
- Detaching threads
- 分离线程
- Until Android 2.0 (Eclair), it was not possible to use a pthread_key_create destructor function to avoid the "thread must be detached before exit" check. (The runtime also uses a pthread key destructor function, so it'd be a race to see which gets called first.)
- 到Android2.0(Éclair)版本为止,利用pthread_key_create析构函数来免除“线程在退出时先断开”的检查是无法做到的(运行时同样会用到一个pthread关键析构函数,所以看谁能更快的取得这个函数,这是个比赛)。
- Weak global references
- 弱全局引用
- Until Android 2.2 (Froyo), weak global references were not implemented. Older versions will vigorously reject attempts to use them. You can use the Android platform version constants to test for support.
- 到Android2.2(Froyo)版本为止,弱全局引用还不能实现。旧版本不遗余力的拒绝使用该功能。你可以用android的平台版本常数来测试是否支持这个功能。
- Until Android 4.0 (Ice Cream Sandwich), weak global references could only be passed to NewLocalRef, NewGlobalRef, and DeleteWeakGlobalRef. (The spec strongly encourages programmers to create hard references to weak globals before doing anything with them, so this should not be at all limiting.)
- 而到Android4.0(Ice Cream Sandwich)版本为止,弱全局引用也仅能在NewLocalRef, NewGlobalRef, 以及DeleteWeakGlobalRef几个函数中实现(规范中鼓励程序员在引用操作之前创造强(hard)引用,从而代替弱全局引用,因而弱全局引 用并未被完全限制使用)
- From Android 4.0 (Ice Cream Sandwich) on, weak global references can be used like any other JNI references.
- 但从Android4.0(Ice Cream Sandwich)版本起,弱全局引用可以在除了JNI引用之外的所有地方使用了。
- Local references
- 局部引用
- Until Android 4.0 (Ice Cream Sandwich), local references were actually direct pointers. Ice Cream Sandwich added the indirection necessary to support better garbage collectors, but this means that lots of JNI bugs are undetectable on older releases. See JNI Local Reference Changes in ICS for more details.
- 到Android 4.0 (Ice Cream Sandwich)版本为止,所谓的本地引用都是指直接指针。Ice Cream Sandwich版本为了更好进行垃圾回收,添加间接指针机制,但是这样一来也就意味着大量的JNI 的BUG在旧版本中无法被发现了。具体的细节可以参考JNI Local Reference Changes in ICS一节。
- Determining reference type with GetObjectRefType
- 通过GetObjectRefType确认引用类型
- Until Android 4.0 (Ice Cream Sandwich), as a consequence of the use of direct pointers (see above), it was impossible to implement GetObjectRefType correctly. Instead we used a heuristic that looked through the weak globals table, the arguments, the locals table, and the globals table in that order. The first time it found your direct pointer, it would report that your reference was of the type it happened to be examining. This meant, for example, that if you called GetObjectRefType on a global jclass that happened to be the same as the jclass passed as an implicit argument to your static native method, you'd get JNILocalRefType rather than JNIGlobalRefType.
- 到Android 4.0 (Ice Cream Sandwich)版本为止,由于一直使用直接指针的缘故(见上),导致系统无法实现GetObjectRefType方法。我们只能探索性的使用弱全局 列表,参数,本地列表以及全局列表。首先它会找到你的直接指针,然后报告你的引用刚好是可以被验证的类型。这也就是说,如果你在一个全局jclass中调 用GetObjectRefType函数,那么与jclass将一个隐形参数传给静态本地方法的效果是一样的,此时你获得的其实是 JNILocalRefType的值,而非JNIGlobalRefType的。
FAQ: Why do I get UnsatisfiedLinkError?-FAQ: 为什么我得到了UnsatisfiedLinkError 错误?
When working on native code it's not uncommon to see a failure like this: 在本地代码环境下,出现如下的错误是很常见的:
java.lang.UnsatisfiedLinkError: Library foo not found
In some cases it means what it says — the library wasn't found. In other cases the library exists but couldn't be opened by dlopen(3), and the details of the failure can be found in the exception's detail message. 有时候,这个问题的原因就像它的字面意思一样——这个库找不到了。而还有些情况是这个库文件存在,但是你无法通过dlopen(3)函数打开它,关于该错 误问题的细节你可以在异常的详细消息中进行查询。
Common reasons why you might encounter "library not found" exceptions: 对于你遇到的异常library not found"常见的原因是:
- The library doesn't exist or isn't accessible to the app. Use adb shell ls -l <path> to check its presence and permissions.
- 该库文件不存在,或者是无法被应用所访问。使用adb shell ls -l <path>命令来检查它的状态以及访问权限。
- The library wasn't built with the NDK. This can result in dependencies on functions or libraries that don't exist on the device.
- 该库文件还没有被NDK编译出来。这会导致依赖函数或者库文件在设备上不存在。
Another class of UnsatisfiedLinkError failures looks like: 另外一类错误UnsatisfiedLinkError表示如下:
java.lang.UnsatisfiedLinkError: myfunc at Foo.myfunc(Native Method) at Foo.main(Foo.java:10)
In logcat, you'll see: 在logcat中,你可以看到:
W/dalvikvm( 880): No implementation found for native LFoo;.myfunc ()V
This means that the runtime tried to find a matching method but was unsuccessful. Some common reasons for this are: 这是指运行时尝试去寻找一个匹配的方法但是失败了。这个问题的原因有可能是:
- The library isn't getting loaded. Check the logcat output for messages about library loading.
- 该库文件无法被载入。请检查库文件载入过程中的logcat输出消息。
- The method isn't being found due to a name or signature mismatch. This is commonly caused by:
- 由于方法的名称或者是方法的签名问题,导致该方法无法被找到。这种情况经常是以下问题导致的:
-
- For lazy method lookup, failing to declare C++ functions with extern "C" and appropriate visibility (JNIEXPORT). Note that prior to Ice Cream Sandwich, the JNIEXPORT macro was incorrect, so using a new GCC with an old jni.h won't work. You can use arm-eabi-nm to see the symbols as they appear in the library; if they look mangled (something like _Z15Java_Foo_myfuncP7_JNIEnvP7_jclass rather than Java_Foo_myfunc), or if the symbol type is a lowercase 't' rather than an uppercase 'T', then you need to adjust the declaration.
- 对于惰性寻找方法,未能在C++函数前加入”C”的声明以及设置合适的访问权限会导致这个问题(JNIEXPORT宏)。注意在早于 Ice Cream Sandwich的版本中,宏JNIEXPORT是不存在的,因此如果在新版本的GCC编译器中编译老版本的jni.h文件会失败。你可以通过arm- eabi-nm来查看库文件中是否存在上述文件符;如果这些文件符看起来混乱不堪(比如说类似 _Z15Java_Foo_myfuncP7_JNIEnvP7_jclass,而不是标准的Java_Foo_myfunc),或者,如果这些文件符的 类型(type)都用小写字母‘t’来替代大写字母’T’,那么你需要调整声明部分来解决这个问题。
-
- For explicit registration, minor errors when entering the method signature. Make sure that what you're passing to the registration call matches the signature in the log file. Remember that 'B' is byte and 'Z' is boolean. Class name components in signatures start with 'L', end with ';', use '/' to separate package/class names, and use '$' to separate inner-class names (Ljava/util/Map$Entry;, say).
- 在明文注册中,在调用方法签名环节出现级别为minor的错误。请确认你传递给注册函数的签名与log文件中的需要一致。切记’B’指 的是比特,而’Z’指的是布尔值。签名中的类文件名需要以’L’开头,以’;’结尾,用’/’来分格包和类名,使用’$’来声明一个内部类名(比如说 Ljava/util/Map$Entry)。
Using javah to automatically generate JNI headers may help avoid some problems. 如果使用javah工具来自动生成JNI头文件,能避免很多麻烦。
FAQ: Why didn't FindClass find my class?-FAQ: 为什么我的类文件中找不到FindClass?
Make sure that the class name string has the correct format. JNI class names start with the package name and are separated with slashes, such as java/lang/String. If you're looking up an array class, you need to start with the appropriate number of square brackets and must also wrap the class with 'L' and ';', so a one-dimensional array of String would be [Ljava/lang/String;.
请确认你的类文件名称格式是否正确。JNI的类名的格式是从所在包的包名开始,以分隔符区分,比如java/lang/String。如果 你需要查找一个数组类,你得以适当数目的方括号([)作为开始,然后以’L’开头,’;’结尾作为文件名称,以一个一位数组为例,它的名称应该是 [Ljava/lang/String;。
If the class name looks right, you could be running into a class loader issue. FindClass wants to start the class search in the class loader associated with your code. It examines the call stack, which will look something like:
如果类的名称看起来没有设置错,可能问题出在类装载器上。FindClass方法可以在你的代码的类装载器中找到该类。它以如下方式来检查调用堆栈:
Foo.myfunc(Native Method) Foo.main(Foo.java:10) dalvik.system.NativeStart.main(Native Method)
The topmost method is Foo.myfunc. FindClass finds the ClassLoader object associated with the Foo class and uses that.
排在首位的方法是Foo.myfunc。FindClass方法找到Foo类的类装载器并使用它。
This usually does what you want. You can get into trouble if you create a thread yourself (perhaps by calling pthread_create and then attaching it with AttachCurrentThread). Now the stack trace looks like this:
这样做可以达到你的目的。但如果创建一个线程你就会陷入困境之中(可能是通过调用pthread_create方法,然后将其引入AttachCurrentThread方法)。此时,堆栈跟踪表显示如下:
dalvik.system.NativeStart.run(Native Method)
The topmost method is NativeStart.run, which isn't part of your application. If you call FindClass from this thread, the JavaVM will start in the "system" class loader instead of the one associated with your application, so attempts to find app-specific classes will fail.
排在首位的方法是NativeStart.run,但该方法并不在你的应用中。如果你在本线程中调用FindClass方法,JAVA虚拟机将会在”system”类装载器里,而不是从你的应用中启动,所以此时你去从应用中去寻找特定类是找不到的。
There are a few ways to work around this:
以下的方法可以解决这个问题:
- Do your FindClass lookups once, in JNI_OnLoad, and cache the class references for later use. Any FindClass calls made as part of executing JNI_OnLoad will use the class loader associated with the function that called System.loadLibrary (this is a special rule, provided to make library initialization more convenient). If your app code is loading the library, FindClass will use the correct class loader.
- 你是否在JNI_OnLoad函数中仅调用了一次FindClass,并且为了后续的使用将类引用缓存。任何一个FindClass调用 都可以被视为执行JNI_OnLoad函数的一部分,这样做会使用System.loadLibrary库的类装载器的功能(这是一个特别的功能,专门用 于初始化库文件,使其更加方便)。如果你的应用调用了库文件,那么FindClass会使用正确的类装载器。
- Pass an instance of the class into the functions that need it, by declaring your native method to take a Class argument and then passing Foo.class in.
- 通过声明你的本地方法,将一个类的实例传递给需要它的函数中,然后可以将类的参数传递给Foo.class。
- Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.
- 将引用缓存至ClassLoader的对象是很方便的,而且可以直接调用loadClass方法。当然,这需要一定的工作量。
You may find yourself in a situation where you need to access a large buffer of raw data from both managed and native code. Common examples include manipulation of bitmaps or sound samples. There are two basic approaches.
当需要在本地代码和托管代码间直接传递大量的原始数据时,你会陷入麻烦中。常见的情况包括对位图或者是声音样本的操作。这有两种基本方法来处理上述问题:
You can store the data in a byte[]. This allows very fast access from managed code. On the native side, however, you're not guaranteed to be able to access the data without having to copy it. In some implementations, GetByteArrayElements and GetPrimitiveArrayCritical will return actual pointers to the raw data in the managed heap, but in others it will allocate a buffer on the native heap and copy the data over.
你可以通过byte[]来存储数据。这样做可以从托管代码中快速访问数据段。而在本地代码侧,你不能保证如果没有复制代码还能够成功的访问 它。在某些实现中,可以通过GetByteArrayElements以及GetPrimitiveArrayCritical函数返回原始数据在托管堆 中的指针,另一些情况下,也可以在本地堆中分配一段缓冲区用来复制数据。
The alternative is to store the data in a direct byte buffer. These can be created with java.nio.ByteBuffer.allocateDirect, or the JNI NewDirectByteBuffer function. Unlike regular byte buffers, the storage is not allocated on the managed heap, and can always be accessed directly from native code (get the address with GetDirectBufferAddress). Depending on how direct byte buffer access is implemented, accessing the data from managed code can be very slow.
还有一个方法,将数据存储至直接比特缓冲区中。缓冲区可以通过java.nio.ByteBuffer.allocateDirect或者 是JNI NewDirectByteBuffer函数来创建。不同于一般的比特缓冲区,该缓冲区并不会由托管堆来分配地址,且可以由本地代码直接访问(通过 GetDirectBufferAddress函数就可以直接获得访问地址)。但考虑直接比特缓存访问的实现方式,从托管代码获取数据的过程可能会非常缓 慢。
The choice of which to use depends on two factors:
下面两个因素决定你怎么选择合适的方法:
1.Will most of the data accesses happen from code written in Java or in C/C++?
1.数据访问部分的代码是由JAVA还是C/C++写的?
2.If the data is eventually being passed to a system API, what form must it be in? (For example, if the data is eventually passed to a function that takes a byte[], doing processing in a direct ByteBuffer might be unwise.)
2.如果数据最终需要传递给系统的API,它需要被包装为什么格式(举个例子,如果数据最终需要以byte[]的格式传给某个函数,将它包装为ByteBuffer就不合适了)?