JNI Tips

閑來翻譯了一篇官方的JNI Tips挂洛,網(wǎng)上看到的翻譯版本要么是時間久了不同步了礼预,要么翻譯的過于生硬,看得我懷疑自己的中文是否及格虏劲,因此自己翻譯再理解下托酸,算是溫故而知新。

JNI is the Java Native Interface. It defines a way for the bytecode that Android compiles from managed code (written in the Java or Kotlin programming languages) to interact with native code (written in C/C++). JNI is vendor-neutral, has support for loading code from dynamic shared libraries, and while cumbersome at times is reasonably efficient.

JNI 是Java Native Interface柒巫, 它定義了一種用于Android從托管代碼(用Java或者Kotlin編寫的代碼)編譯得到的字節(jié)碼和native代碼交互的方式励堡。 JNI 是無廠商中立的,它支持動態(tài)地加載shared libraries(用C/C++編寫的)堡掏,雖然這樣會稍顯麻煩应结,但有時這是相當(dāng)有效的。

Note: Because Android compiles Kotlin to ART-friendly bytecode in a similar manner as the Java programming language, you can apply the guidance on this page to both the Kotlin and Java programming languages in terms of JNI architecture and its associated costs. To learn more, see Kotlin and Android.

因為Android會編譯kotlin會得到跟java一樣對ART運(yùn)行同樣友好的字節(jié)碼布疼,所以這篇文章對你學(xué)習(xí)kotlin和java是一樣有用的摊趾,如果想了解更多請查閱Kotlin和Android.

如果你對JNI還不是太熟悉币狠,可以先通讀Java Native Interface Specification這篇文章來對JNI如何工作以及哪些特性可用有個大致的印象游两。這種接口的一些方面不能立即一讀就顯而易見,所以你會發(fā)現(xiàn)接下來的幾個章節(jié)很有用處漩绵。

To browse global JNI references and see where global JNI references are created and deleted, use the JNI heap view in the Memory Profiler in Android Studio 3.2 and higher.

想看全局的JNI references和了解全局JNI references是如何創(chuàng)建和刪除的贱案,可使用Memory Profiler的JNI heap view,它在至少Android Studio 3.2版本里提供。

General tips

Try to minimize the footprint of your JNI layer. There are several dimensions to consider here. Your JNI solution should try to follow these guidelines (listed below by order of importance, beginning with the most important):

關(guān)于如何盡量減少JNI層的占用空間宝踪,這里有幾個需要遵循的準(zhǔn)則侨糟,你的JNI應(yīng)該盡可能按照這些指導(dǎo)方針來實現(xiàn)(下面以重要性列優(yōu)先級鞭缭,最上面的是最重要的)

  • Minimize marshalling of resources across the JNI layer. Marshalling across the JNI layer has non-trivial costs. Try to design an interface that minimizes the amount of data you need to marshall and the frequency with which you must marshall data.

最小化跨JNI層的資源傳輸痰洒」邮迹跨越JNI層進(jìn)行傳輸具有非常重要的成本塑煎。嘗試設(shè)計一個界面墩瞳,以最大限度地減少傳輸所需的數(shù)據(jù)量以及必須對數(shù)據(jù)進(jìn)行傳輸?shù)念l率查吊。

  • Avoid asynchronous communication between code written in a managed programming language and code written in C++ when possible. This will keep your JNI interface easier to maintain. You can typically simplify asynchronous UI updates by keeping the async update in the same language as the UI. For example, instead of invoking a C++ function from the UI thread in the Java code via JNI, it's better to do a callback between two threads in the Java programming language, with one of them making a blocking C++ call and then notifying the UI thread when the blocking call is complete.

盡可能避免托管(Java或者Kotlin)代碼與C++代碼進(jìn)行異步通信通惫,這么做可以使你的JNI 接口更易于維護(hù)鼻忠。你通常應(yīng)通過使用與接口相同的語言保持異步更新來簡化異步UI更新服鹅。例如凳兵,不應(yīng)該在UI線程中用java封裝代碼通過JNI來調(diào)用C++函數(shù),而是最好在Java編程語言中的兩個線程之間建立回調(diào)企软,其中一個線程進(jìn)行阻塞C++調(diào)用庐扫,然后當(dāng)阻塞調(diào)用執(zhí)行結(jié)束后再通知UI線程完成了。

  • Minimize the number of threads that need to touch or be touched by JNI. If you do need to utilize thread pools in both the Java and C++ languages, try to keep JNI communication between the pool owners rather than between individual worker threads.

減少需要創(chuàng)建的線程數(shù)量仗哨,通過JNI創(chuàng)建的線程也要盡可能減少形庭。如果你真要在Java和C++中使用線程池,盡量保持在同一個線程池創(chuàng)建的線程之間的JNI通信厌漂,而不是單個工作線程之間的通信碘勉。

  • Keep your interface code in a low number of easily identified C++ and Java source locations to facilitate future refactors. Consider using a JNI auto-generation library as appropriate.

將你的JNI接口代碼盡可能放在集中的地方,不要到處存放桩卵,這樣方便以后進(jìn)行代碼重構(gòu)验靡,可以適當(dāng)考慮自動生成JNI的庫。

JavaVM and JNIEnv

JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentially pointers to pointers to function tables. (In the C++ version, they're classes with a pointer to a function table and a member function for each JNI function that indirects through the table.) The JavaVM provides the "invocation interface" functions, which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process, but Android only allows one.

JNI定義了兩種關(guān)鍵數(shù)據(jù)結(jié)構(gòu)雏节,“JavaVM”和“JNIEnv”胜嗓。它們本質(zhì)上都是指向函數(shù)表指針的指針(在C++版本中,它們被定義為類钩乍,該類包含一個指向函數(shù)表的指針辞州,以及一系列可以通過這個函數(shù)表間接地訪問對應(yīng)的JNI函數(shù)的成員函數(shù))。JavaVM提供“調(diào)用接口(invocation interface)”函數(shù), 允許你創(chuàng)建和銷毀一個JavaVM寥粹。理論上你可以在一個進(jìn)程中擁有多個JavaVM對象变过,但安卓只允許一個。

The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv as the first argument.

JNIEnv 提供了絕大部分JNI函數(shù)涝涤,你的native函數(shù)的第一個參數(shù)都是JNIEnv媚狰。

The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads. If a piece of code has no other way to get its JNIEnv, you should share the JavaVM, and use GetEnv to discover the thread's JNIEnv. (Assuming it has one; see AttachCurrentThread below.)

JNIEnv是用作線程局部存儲。因此阔拳,你不能在線程間共享一個JNIEnv變量崭孤。如果在一段代碼中沒有其它辦法獲得它的JNIEnv,你可以共享JavaVM對象,使用GetEnv來取得該線程下的JNIEnv(如果該線程有一個JavaVM的話辨宠;見下面的AttachCurrentThread)遗锣。

The C declarations of JNIEnv and JavaVM are different from the C++ declarations. The "jni.h" include file provides different typedefs depending on whether it's included into C or C++. For this reason it's a bad idea to include JNIEnv arguments in header files included by both languages. (Put another way: if your header file requires #ifdef __cplusplus, you may have to do some extra work if anything in that header refers to JNIEnv.)

C語言對JNIEnv和JavaVM的定義和C++是不同的,頭文件“jni.h”根據(jù)它是以C還是以C++模式包含來提供不同的類型定義(typedefs)嗤形。因此精偿,不建議把JNIEnv參數(shù)放到可能被兩種語言引入的頭文件中(換一句話說:如果你的頭文件需要#ifdef __cplusplus,你可能不得不在任何涉及到JNIEnv的內(nèi)容處都要做些額外的工作)赋兵。

Threads

All threads are Linux threads, scheduled by the kernel. They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then attached to the JavaVM. For example, a thread started with pthread_create can be attached with the JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv, and cannot make JNI calls.

所有的線程都是Linux線程还最,都有l(wèi)inux內(nèi)核統(tǒng)一調(diào)度。他們通常都是由托管代碼(Java或Kotlin)啟動(通過使用Thread.start),但它們也能夠在其他任何地方創(chuàng)建毡惜,然后連接(attach)到JavaVM拓轻。例如,一個用pthread_create啟動的線程能夠使用JNI AttachCurrentThread 或 AttachCurrentThreadAsDaemon函數(shù)連接到JavaVM经伙。在一個線程成功連接(attach)之前扶叉,它沒有JNIEnv,不能夠調(diào)用JNI函數(shù)帕膜。

Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main" ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread is a no-op.

連接一個本地環(huán)境創(chuàng)建的線程會觸發(fā)構(gòu)造一個java.lang.Thread對象枣氧,然后其被添加到主線程群組(main ThreadGroup),以讓調(diào)試器可以探測到。對一個已經(jīng)連接的線程使用AttachCurrentThread不做任何操作(no-op)垮刹。

Android does not suspend threads executing native code. If garbage collection is in progress, or the debugger has issued a suspend request, Android will pause the thread the next time it makes a JNI call.

Android不能中止正在執(zhí)行本地代碼的線程达吞。如果正在進(jìn)行垃圾回收,或者調(diào)試器已發(fā)出了中止請求荒典,安卓會在下一次調(diào)用JNI函數(shù)的時候中止線程酪劫。

Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)

連接過的(attached)線程在它們退出之前必須通過JNI調(diào)用DetachCurrentThread。如果你覺得直接這樣編寫不太優(yōu)雅寺董,在安卓2.0(Eclair)及以上覆糟, 你可以使用pthread_key_create來定義一個析構(gòu)函數(shù),它將會在線程退出時被調(diào)用遮咖,你可以在那兒調(diào)用DetachCurrentThread (使用生成的key與pthread_setspecific將JNIEnv存儲到線程局部空間內(nèi)滩字;這樣JNIEnv能夠作為參數(shù)傳入到析構(gòu)函數(shù)當(dāng)中去)。

jclass, jmethodID, and jfieldID

If you want to access an object's field from native code, you would do the following:

如果你想通過native代碼訪問對象的屬性御吞,你要做的事情應(yīng)該如下:

  • Get the class object reference for the class with FindClass
  • Get the field ID for the field with GetFieldID
  • Get the contents of the field with something appropriate, such as GetIntField

通過使用FindClass方法獲取class對象的引用
通過使用GetFieldID方法獲取field的ID
通過使用例如GetIntField這樣的方法獲取field的值

Similarly, to call a method, you'd first get a class object reference and then a method ID. The IDs are often just pointers to internal runtime data structures. Looking them up may require several string comparisons, but once you have them the actual call to get the field or invoke the method is very quick.

類似的要執(zhí)行一個方法麦箍,你首先要獲取class對象的引用,再然后就是方法的ID陶珠。ID通常就是指向內(nèi)部運(yùn)行結(jié)構(gòu)體的指針挟裂,遍歷尋找他們可能需要一些字符串比較工作,但是一旦找到它們背率,獲取field值或者調(diào)用方法是非郴扒疲快的嫩与。

If performance is important, it's useful to look the values up once and cache the results in your native code. Because there is a limit of one JavaVM per process, it's reasonable to store this data in a static local structure.

如果對性能要求很高寝姿,最好不要多次讀取這些值交排,應(yīng)當(dāng)將讀取的結(jié)果存在你的native代碼中。因為每個進(jìn)程JavaVM被限制為只能有一個饵筑。將它們存在本地靜態(tài)struct中是推薦做法埃篓。

The class references, field IDs, and method IDs are guaranteed valid until the class is unloaded. Classes are only unloaded if all classes associated with a ClassLoader can be garbage collected, which is rare but will not be impossible in Android. Note however that the jclass is a class reference and must be protected with a call to NewGlobalRef (see the next section).

類引用(class reference),字段ID(field ID)以及方法ID(method ID)在類被卸載前都是有效的根资。如果與一個類加載器(ClassLoader)相關(guān)的所有類都能夠被垃圾回收架专,但是這種情況在安卓上是罕見甚至不可能出現(xiàn),只有這時類才被卸載玄帕。注意雖然jclass是一個類引用部脚,但是必須要調(diào)用NewGlobalRef保護(hù)起來(見下個章節(jié))。

If you would like to cache the IDs when a class is loaded, and automatically re-cache them if the class is ever unloaded and reloaded, the correct way to initialize the IDs is to add a piece of code that looks like this to the appropriate class:

當(dāng)一個類被加載時如果你想緩存些ID裤纹,而后當(dāng)這個類被卸載后再次載入時能夠自動地更新這些緩存ID委刘,正確做法是在對應(yīng)的類中添加一段像下面的代碼來初始化這些ID:

/*
 * 我們在一個類初始化時調(diào)用本地方法來緩存一些字段的偏移信息
 * 這個本地方法查找并緩存你感興趣的class/field/method ID
 * 失敗時拋出異常
 */
private static native void nativeInit();

static {
    nativeInit();
}

Create a nativeClassInit method in your C/C++ code that performs the ID lookups. The code will be executed once, when the class is initialized. If the class is ever unloaded and then reloaded, it will be executed again.

在C/C++里創(chuàng)建一個叫nativeClassInit的方法用于尋找那些ID。一旦代碼初始化ok鹰椒,這段代碼就會執(zhí)行锡移,如果類被卸載然后又重新被加載這個方法會再次執(zhí)行一次。

Local and global references

Every argument passed to a native method, and almost every object returned by a JNI function is a "local reference". This means that it's valid for the duration of the current native method in the current thread. Even if the object itself continues to live on after the native method returns, the reference is not valid.

每一個傳入本地方法的參數(shù)和對象通過JNI方法返回的都是一個局部引用漆际。這意味著當(dāng)前本地方法所在的當(dāng)前線程中這個本地引用是有效的淆珊。但是隨后即使這個對象本身在本地方法返回之后仍然存在,這個引用也是無效的奸汇。

This applies to all sub-classes of jobject, including jclass, jstring, and jarray. (The runtime will warn you about most reference mis-uses when extended JNI checks are enabled.)

這個特性適用于所有jobject子類施符,包括jclass,jstring以及jarry擂找。(當(dāng)JNI擴(kuò)展檢查是打開的時候操刀,運(yùn)行時會警告你對大部分對象引用的誤用)

The only way to get non-local references is via the functions NewGlobalRef and NewWeakGlobalRef.

唯一獲得非局部引用(即更長時間引用)的辦法是通過NewGlobalRef和NewWeakGlobalRef這兩個方法。

If you want to hold on to a reference for a longer period, you must use a "global" reference. The NewGlobalRef function takes the local reference as an argument and returns a global one. The global reference is guaranteed to be valid until you call DeleteGlobalRef.

如果你想持有一個更長時間的引用婴洼,你不得不用全局引用骨坑。NewGlobalRef函數(shù)以一個局部引用作為參數(shù)并且返回一個全局引用。全局引用能夠保證在你調(diào)用DeleteGlobalRef前都是有效的柬采。

This pattern is commonly used when caching a jclass returned from FindClass, e.g.:

這種模式通常被用在緩存一個從FindClass返回的jclass對象的時候欢唾,例如:

jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));

All JNI methods accept both local and global references as arguments. It's possible for references to the same object to have different values. For example, the return values from consecutive calls to NewGlobalRef on the same object may be different. To see if two references refer to the same object, you must use the IsSameObject function. Never compare references with == in native code.

所有的JNI方法都接收局部引用和全局引用作為參數(shù)。相同對象的引用卻可能具有不同的值粉捻。例如礁遣,用相同對象連續(xù)地調(diào)用NewGlobalRef得到返回值可能是不同的。為了檢查兩個引用是否指向的是同一個對象肩刃,你必須使用IsSameObject函數(shù)祟霍。絕不要在本地代碼中用==符號來比較兩個引用杏头。

One consequence of this is that you must not assume object references are constant or unique in native code. The 32-bit value representing an object may be different from one invocation of a method to the next, and it's possible that two different objects could have the same 32-bit value on consecutive calls. Do not use jobject values as keys.

因此得出一個結(jié)論:你千萬不能在本地代碼中假設(shè)對象引用為常量或者唯一的,一個32位值代表一個對象的從方法沸呐,從第一次調(diào)用到下一次調(diào)用可能有不同的值醇王。在連續(xù)的調(diào)用過程中兩個不同的對象卻可能擁有相同的32位值。因此崭添,不要使用jobject的值作為key.

Programmers are required to "not excessively allocate" local references. In practical terms this means that if you're creating large numbers of local references, perhaps while running through an array of objects, you should free them manually with DeleteLocalRef instead of letting JNI do it for you. The implementation is only required to reserve slots for 16 local references, so if you need more than that you should either delete as you go or use EnsureLocalCapacity/PushLocalFrame to reserve more.

開發(fā)者們應(yīng)該不要“過度分配”局部引用寓娩。在實際操作中這意味著如果你正在創(chuàng)建大量的局部引用,或許是通過對象數(shù)組呼渣,你應(yīng)該使用DeleteLocalRef手動地釋放它們棘伴,而不是寄希望JNI來為你做這些。實現(xiàn)上只預(yù)留了16個局部引用的空間屁置,所以如果你需要更多焊夸,要么你刪掉以前的,要么使用EnsureLocalCapacity/PushLocalFrame來預(yù)留更多蓝角。

Note that jfieldIDs and jmethodIDs are opaque types, not object references, and should not be passed to NewGlobalRef. The raw data pointers returned by functions like GetStringUTFChars and GetByteArrayElements are also not objects. (They may be passed between threads, and are valid until the matching Release call.)

注意jfieldID和jmethodID是映射類型(opaque types)阱穗,不是對象引用,不應(yīng)該被傳入到NewGlobalRef帅容。原始數(shù)據(jù)指針颇象,像GetStringUTFChars和GetByteArrayElements的返回值,也都不是對象(它們能夠在線程間傳遞并徘,并且在調(diào)用對應(yīng)的Release函數(shù)之前都是有效的)遣钳。

One unusual case deserves separate mention. If you attach a native thread with AttachCurrentThread, the code you are running will never automatically free local references until the thread detaches. Any local references you create will have to be deleted manually. In general, any native code that creates local references in a loop probably needs to do some manual deletion.

還有一種不常見的情況值得一提,如果你使用AttachCurrentThread連接(attach)了本地進(jìn)程麦乞,正在運(yùn)行的代碼在線程分離(detach)之前決不會自動釋放局部引用蕴茴。你創(chuàng)建的任何局部引用必須手動刪除。通常姐直,任何在循環(huán)中創(chuàng)建局部引用的本地代碼可能都需要做一些手動刪除倦淀。

Be careful using global references. Global references can be unavoidable, but they are difficult to debug and can cause difficult-to-diagnose memory (mis)behaviors. All else being equal, a solution with fewer global references is probably better.

在使用全局引用時候需要小心對待,全局引用的使用是不可避免的声畏,但是它們的調(diào)試比較困難撞叽,而且還會導(dǎo)致難以診斷的內(nèi)存行為。其他條件一樣插龄,最少的全局引用來實現(xiàn)功能或許更好愿棋。

UTF-8 and UTF-16 strings

The Java programming language uses UTF-16. For convenience, JNI provides methods that work with Modified UTF-8 as well. The modified encoding is useful for C code because it encodes \u0000 as 0xc0 0x80 instead of 0x00. The nice thing about this is that you can count on having C-style zero-terminated strings, suitable for use with standard libc string functions. The down side is that you cannot pass arbitrary UTF-8 data to JNI and expect it to work correctly.

Java使用UTF-16格式,為了方便均牢,JNI也提供了支持變形UTF-8(Modified UTF-8)的方法糠雨。這種變形編碼對于C代碼是非常有用的,因為它將\u0000編碼成0xc0 0x80徘跪,而不是0x00甘邀。最愜意的事情是你能在具有C風(fēng)格的以\0結(jié)束的字符串上計數(shù)琅攘,同時兼容標(biāo)準(zhǔn)的libc字符串函數(shù)。不好的一面是你不能傳入隨意的UTF-8數(shù)據(jù)到JNI函數(shù)而還指望它正常工作松邪。

If possible, it's usually faster to operate with UTF-16 strings. Android currently does not require a copy in GetStringChars, whereas GetStringUTFChars requires an allocation and a conversion to UTF-8. Note that UTF-16 strings are not zero-terminated, and \u0000 is allowed, so you need to hang on to the string length as well as the jchar pointer.

如果可能的話坞琴,直接操作UTF-16字符串通常更快些。Android當(dāng)前在調(diào)用GetStringChars時不需要拷貝测摔,而GetStringUTFChars需要一次分配并且轉(zhuǎn)換為UTF-8格式置济。注意UTF-16字符串不是以零終止字符串解恰,\u0000是被允許的锋八,所以你需要像對jchar指針一樣地處理字符串的長度。

Don't forget to Release the strings you Get. The string functions return jchar* or jbyte*, which are C-style pointers to primitive data rather than local references. They are guaranteed valid until Release is called, which means they are not released when the native method returns.

不要忘記釋放你獲得的strings护盈。那些strings函數(shù)返回的jchar* 或者 jbyte* 本質(zhì)是C語言指向基本數(shù)據(jù)類型的指針挟纱,并不是局部引用。它們在被Release調(diào)用前都是有效的腐宋,這意味著當(dāng)本地方法返回時它們并不主動釋放紊服。

Data passed to NewStringUTF must be in Modified UTF-8 format. A common mistake is reading character data from a file or network stream and handing it to NewStringUTF without filtering it. Unless you know the data is valid MUTF-8 (or 7-bit ASCII, which is a compatible subset), you need to strip out invalid characters or convert them to proper Modified UTF-8 form. If you don't, the UTF-16 conversion is likely to provide unexpected results. CheckJNI—which is on by default for emulators—scans strings and aborts the VM if it receives invalid input.

傳入NewStringUTF函數(shù)的數(shù)據(jù)必須是變形UTF-8格式。一種常見的錯誤情況是胸竞,從文件或者網(wǎng)絡(luò)流中讀取出的字符數(shù)據(jù)欺嗤,沒有過濾直接使用NewStringUTF處理。除非你確定數(shù)據(jù)是7位的ASCII格式卫枝,否則你需要剔除超出7位ASCII編碼范圍(high-ASCII)的字符或者將它們轉(zhuǎn)換為對應(yīng)的變形UTF-8格式煎饼。如果你沒那樣做,UTF-16的轉(zhuǎn)換結(jié)果可能不會是你想要的結(jié)果校赤。JNI擴(kuò)展檢查將會掃描字符串吆玖,然后警告你那些無效的數(shù)據(jù),但是它們將不會發(fā)現(xiàn)所有潛在的風(fēng)險马篮。

Primitive arrays

JNI provides functions for accessing the contents of array objects. While arrays of objects must be accessed one entry at a time, arrays of primitives can be read and written directly as if they were declared in C.

JNI提供了一系列方法用于訪問數(shù)組內(nèi)容沾乘。對象數(shù)組的訪問只能一次一條,但如果原生類型數(shù)組以C方式聲明浑测,則能夠直接進(jìn)行讀寫翅阵。

To make the interface as efficient as possible without constraining the VM implementation, the Get<PrimitiveType>ArrayElements family of calls allows the runtime to either return a pointer to the actual elements, or allocate some memory and make a copy. Either way, the raw pointer returned is guaranteed to be valid until the corresponding Release call is issued (which implies that, if the data wasn't copied, the array object will be pinned down and can't be relocated as part of compacting the heap). You must Release every array you Get. Also, if the Get call fails, you must ensure that your code doesn't try to Release a NULL pointer later.

為了讓接口更有效率而不受VM實現(xiàn)的制約,Get<PrimitiveType>ArrayElements系列調(diào)用允許運(yùn)行時返回一個指向?qū)嶋H元素的指針迁央,或者是分配些內(nèi)存然后拷貝一份掷匠。不論哪種方式,返回的原始指針在相應(yīng)的Release調(diào)用之前都保證有效(這意味著漱贱,如果數(shù)據(jù)沒被拷貝槐雾,實際的數(shù)組對象將會受到牽制,不能重新成為整理堆空間的一部分)幅狮。你必須釋放(Release)每個你通過Get得到的數(shù)組募强。同時株灸,如果Get調(diào)用失敗,你必須確保你的代碼在之后不會去嘗試調(diào)用Release來釋放一個空指針(NULL pointer)擎值。

You can determine whether or not the data was copied by passing in a non-NULL pointer for the isCopy argument. This is rarely useful.

你可以用一個非空指針作為isCopy參數(shù)的值來決定數(shù)據(jù)是否會被拷貝慌烧。這相當(dāng)有用。

The Release call takes a mode argument that can have one of three values. The actions performed by the runtime depend upon whether it returned a pointer to the actual data or a copy of it:

Release類的函數(shù)接收一個mode參數(shù)鸠儿,這個參數(shù)的值可選的有下面三種屹蚊。而運(yùn)行時具體執(zhí)行的操作取決于它返回的指針是指向真實數(shù)據(jù)還是拷貝出來的那份。

  • 0

    Actual: the array object is un-pinned.
    Copy: data is copied back. The buffer with the copy is freed.

  • JNI_COMMIT

    Actual: does nothing.
    Copy: data is copied back. The buffer with the copy is not freed.

  • JNI_ABORT

    Actual: the array object is un-pinned. Earlier writes are not aborted.
    Copy: the buffer with the copy is freed; any changes to it are lost.

  • 0
    真實的:數(shù)組對象不受牽制
    拷貝的:數(shù)據(jù)被復(fù)制进每,備份空間將會被釋放

  • JNI_COMMIT
    事實上:什么都沒做
    拷貝的:數(shù)據(jù)被復(fù)制汹粤,備份空間不會被釋放

  • JNI_ABORT
    實際上:數(shù)組對象不受牽連,之前的寫入不會被取消
    拷貝的:備份空間將會被釋放田晚;里面所有的變更都會丟失

One reason for checking the isCopy flag is to know if you need to call Release with JNI_COMMIT after making changes to an array — if you're alternating between making changes and executing code that uses the contents of the array, you may be able to skip the no-op commit. Another possible reason for checking the flag is for efficient handling of JNI_ABORT. For example, you might want to get an array, modify it in place, pass pieces to other functions, and then discard the changes. If you know that JNI is making a new copy for you, there's no need to create another "editable" copy. If JNI is passing you the original, then you do need to make your own copy.

檢查isCopy標(biāo)識的一個原因是對一個數(shù)組做出變更后確認(rèn)你是否需要傳入JNI_COMMIT來調(diào)用Release函數(shù)嘱兼。如果你交替地執(zhí)行變更和讀取數(shù)組內(nèi)容的代碼,你也許可以跳過無操作(no-op)的JNI_COMMIT贤徒。檢查這個標(biāo)識的另一個可能的原因是使用JNI_ABORT可以更高效芹壕。例如,你也許想得到一個數(shù)組接奈,適當(dāng)?shù)匦薷乃哂浚瑐魅氩糠值狡渌瘮?shù)中,然后丟掉這些修改序宦。如果你知道JNI是為你做了一份新的拷貝睁壁,就沒有必要再創(chuàng)建另一份“可編輯的(editable)”的拷貝了。如果JNI傳給你的是原始數(shù)組挨厚,這時你就需要創(chuàng)建一份你自己的拷貝了堡僻。

It is a common mistake (repeated in example code) to assume that you can skip the Release call if *isCopy is false. This is not the case. If no copy buffer was allocated, then the original memory must be pinned down and can't be moved by the garbage collector.

另一個常見的錯誤(在示例代碼中出現(xiàn)過)是認(rèn)為當(dāng)isCopy是false時你就可以不調(diào)用Release, 實際上是沒有這種情況的。如果沒有分配備份空間疫剃,那么初始的內(nèi)存空間會受到牽制钉疫,位置不能被垃圾回收器移動。

Also note that the JNI_COMMIT flag does not release the array, and you will need to call Release again with a different flag eventually.

同時需要注意的是JNI_COMMIT flag不會釋放數(shù)組巢价,最終你需要用不同的flag再次釋放一遍牲阁。

Region calls

There is an alternative to calls like Get<Type>ArrayElements and GetStringChars that may be very helpful when all you want to do is copy data in or out. Consider the following:

當(dāng)你想做的只是拷出或者拷進(jìn)數(shù)據(jù)時,可以選擇調(diào)用像GetArrayElements和GetStringChars這類非常有用的函數(shù)壤躲。想想下面:

jbyte* data = env->GetByteArrayElements(array, NULL);
if (data != NULL) {
    memcpy(buffer, data, len);
    env->ReleaseByteArrayElements(array, data, JNI_ABORT);
}

This grabs the array, copies the first len byte elements out of it, and then releases the array. Depending upon the implementation, the Get call will either pin or copy the array contents. The code copies the data (for perhaps a second time), then calls Release; in this case JNI_ABORT ensures there's no chance of a third copy.

這里獲取到了數(shù)組城菊,從當(dāng)中拷貝出開頭的len個字節(jié)元素,然后釋放這個數(shù)組碉克。根據(jù)代碼的實現(xiàn)凌唬,Get函數(shù)將會牽制或者拷貝數(shù)組的內(nèi)容。上面的代碼拷貝了數(shù)據(jù)(為了可能的第二次)漏麦,然后調(diào)用Release客税;這當(dāng)中JNI_ABORT確保不存在第三份拷貝了况褪。

One can accomplish the same thing more simply:

另一種更簡單的實現(xiàn)方式:

env->GetByteArrayRegion(array, 0, len, buffer);

This has several advantages:

它有幾個優(yōu)勢:

  • Requires one JNI call instead of 2, reducing overhead.
  • Doesn't require pinning or extra data copies.
  • Reduces the risk of programmer error — no risk of forgetting to call Release after something fails.
  • 只需要調(diào)用一個JNI函數(shù)而不是2個,減少了開銷
  • 不需要牽扯到別的數(shù)據(jù)拷貝
  • 減少了代碼錯誤風(fēng)險更耻,沒有在某些錯誤后忘記調(diào)用Release的風(fēng)險

Similarly, you can use the Set<Type>ArrayRegion call to copy data into an array, and GetStringRegion or GetStringUTFRegion to copy characters out of a String.

類似的测垛,你可以用Set<Type>ArrayRegion拷貝數(shù)據(jù)到數(shù)組中,同時利用GetStringRegion和GetStringUTFRegionc從strings中拷貝字符串秧均。

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.

當(dāng)異常發(fā)生時你一定不能調(diào)用大部分的JNI函數(shù)食侮。你的代碼收到異常(通過函數(shù)的返回值,ExceptionCheck目胡,或者ExceptionOccurred)锯七,然后返回,或者清除異常讶隐,處理掉起胰。

The only JNI functions that you are allowed to call while an exception is pending are:

當(dāng)異常發(fā)生時候你僅僅能調(diào)用的函數(shù)如下:

  • DeleteGlobalRef
  • DeleteLocalRef
  • DeleteWeakGlobalRef
  • ExceptionCheck
  • ExceptionClear
  • ExceptionDescribe
  • ExceptionOccurred
  • MonitorExit
  • PopLocalFrame
  • PushLocalFrame
  • ReleaseArrayElements
  • 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的調(diào)用會拋出異常久又,但往往提供了一個類似的方式用于檢查失敗巫延。比如,如果NewString返回了一個不為空的值地消,你無需檢查錯誤炉峰。如果你調(diào)用一個方法(使用了一個類似CallObjectMethod的方法),你必須總是檢查是否有異常拋出脉执,因為當(dāng)異常發(fā)生時候返回值就不會正確了疼阔。

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.

注意中斷代碼拋出的異常不會展開本地調(diào)用堆棧信息,Android也還不支持C++異常半夷。JNI Throw和ThrowNew指令僅僅是在當(dāng)前線程中放入一個異常指針婆廊。從本地代碼返回到托管代碼時,異常將會被注意到巫橄,得到適當(dāng)?shù)奶幚怼?/p>

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.

本地代碼能通過調(diào)用ExceptionCheck或ExceptionOccurred來catch異常淘邻,并可以通過ExceptionClear來清除異常。通常來說湘换,不處理異常直接丟棄異常會導(dǎo)致問題宾舅。

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.

沒有內(nèi)建的函數(shù)來處理Throwable對象自身,所以如果你想獲取異常字符串你需要找到Throwable class彩倚,再找到getMessage()"()Ljava/lang/String;"這個方法的ID筹我,最后執(zhí)行此方法,如果結(jié)果是非空則調(diào)用GetStringUTFChars得到結(jié)果帆离,你可以傳到printf(3) 或者其它類似功能的函數(shù)輸出蔬蕊。

Extended checking

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的錯誤檢查很少, 錯誤發(fā)生時通常會導(dǎo)致崩潰。Android也提供了一種模式哥谷,叫做CheckJNI岸夯,這當(dāng)中JavaVM和JNIEnv函數(shù)表指針被換成了函數(shù)表概而,它在調(diào)用標(biāo)準(zhǔn)實現(xiàn)之前執(zhí)行了一系列擴(kuò)展檢查的。

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.
  • Class names: passing anything but the “java/lang/String” style of class name to a JNI call.
  • Critical calls: making a JNI call between a “critical” get and its corresponding release.
  • Direct ByteBuffers: passing bad arguments to NewDirectByteBuffer.
  • Exceptions: making a JNI call while there’s an exception pending.
  • JNIEnvs: using a JNIEnv from the wrong thread.
  • 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.
  • 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).
  • References: using DeleteGlobalRef/DeleteLocalRef on the wrong kind of reference.
  • Release modes: passing a bad release mode to a release call (something other than 0, JNI_ABORT, or JNI_COMMIT).
  • Type safety: returning an incompatible type from your native method (returning a StringBuilder from a method declared to return a String, say).
  • UTF-8: passing an invalid Modified UTF-8 byte sequence to a JNI call.
  • 數(shù)組:試圖分配一個長度為負(fù)的數(shù)組囱修。
  • 壞指針:傳入一個不完整jarray/jclass/jobject/jstring對象到JNI函數(shù)赎瑰,或者調(diào)用JNI函數(shù)時使用空指針傳入到一個不能為空的參數(shù)中去。
  • 類名:傳入了除“java/lang/String”之外的類名到JNI函數(shù)破镰。
  • 關(guān)鍵調(diào)用:在一個“關(guān)鍵的(critical)”get和它對應(yīng)的release之間做出JNI調(diào)用餐曼。
  • 直接的ByteBuffers:傳入不正確的參數(shù)到NewDirectByteBuffer。
  • 異常:當(dāng)一個異常發(fā)生時調(diào)用了JNI函數(shù)鲜漩。
  • JNIEnvs:在錯誤的線程中使用一個JNIEnv源譬。
  • jfieldIDs:使用一個空jfieldID,或者使用jfieldID設(shè)置了一個錯誤類型的值到字段(比如說孕似,試圖將一個StringBuilder賦給String類型的域)踩娘,或者使用一個靜態(tài)字段下的jfieldID設(shè)置到一個實例的字段(instance field)反之亦然,或者使用的一個類的jfieldID卻來自另一個類的實例喉祭。
  • jmethodIDs:當(dāng)調(diào)用Call*Method函數(shù)時時使用了類型錯誤的jmethodID:不正確的返回值养渴,靜態(tài)/非靜態(tài)的不匹配,this的類型錯誤(對于非靜態(tài)調(diào)用)或者錯誤的類(對于靜態(tài)類調(diào)用)泛烙。
  • 引用:在類型錯誤的引用上使用了DeleteGlobalRef/DeleteLocalRef理卑。
  • 釋放模式:調(diào)用release使用一個不正確的釋放模式(其它非 0,JNI_ABORT蔽氨,JNI_COMMIT的值)藐唠。
  • 類型安全:從你的本地代碼中返回了一個不兼容的類型(比如說,從一個聲明返回String的方法卻返回了StringBuilder)鹉究。
  • UTF-8:傳入一個無效的變形UTF-8字節(jié)序列到JNI調(diào)用宇立。

(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默認(rèn)是啟用著的妈嘹。

If you have a rooted device, you can use the following sequence of commands to restart the runtime with CheckJNI enabled:

如果你有一個root后的設(shè)備,你可以使用以下命令流程來重新啟動運(yùn)行時匿级,讓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:

不管以上那種情況,你都可以在runtime啟動后在logcat中查看是否啟用了CheckJNI:

D AndroidRuntime: CheckJNI is ON

If you have a regular device, you can use the following command:

如果你有一個常規(guī)設(shè)備痘绎,你可以輸入以下命令:

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:

這個操作不會影響已經(jīng)運(yùn)行起來的App津函,但是從這一刻開始啟動的App它的CheckJNI就是開啟的。(改變這個屬性值為其它值或者重啟設(shè)備將又再次關(guān)閉CheckJNI)在這種情況下孤页,下次你將在logcat的輸出中看到如下信息:

D Late-enabling CheckJNI

You can also set the android:debuggable attribute in your application's manifest to turn on CheckJNI just for your app. Note that the Android build tools will do this automatically for certain build types.

你也可以通過在manifest中設(shè)置android:debuggable為true來開啟你的App的CheckJNI尔苦。需要注意的是,Android 編譯工具將自動為特定的build type做這件事情。

Native libraries

You can load native code from shared libraries with the standard System.loadLibrary. The preferred way to work with native methods is:

你可以通過使用標(biāo)準(zhǔn)的方法(System.loadLibrary)從共享庫中加載本地代碼, 和本地代碼交互的推薦的做法如下:

  • Call System.loadLibrary from a static class initializer. The argument is the "undecorated" library name, so to load "libfubar.so" you would pass in "fubar".
  • Provide a JNI_OnLoad function: JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
  • In your JNI_OnLoad, register all of your native methods using RegisterNatives.
  • Build with -fvisibility=hidden so that only your JNI_OnLoad is exported from your library. This produces faster and smaller code, and avoids potential collisions with other libraries loaded into your app (but it creates less useful stack traces if you app crashes in native code).
  • 在一個靜態(tài)類初始化時調(diào)用System.loadLibrary(見之前的一個例子中允坚,當(dāng)中就使用了nativeClassInit)魂那。參數(shù)是“未加修飾(undecorated)”的庫名稱,因此要加載“l(fā)ibfubar.so”稠项,你需要傳入“fubar”涯雅。
  • 提供一個叫JNI_OnLoad的函數(shù):JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
  • 在JNI_OnLoad中,注冊所有你的本地方法展运。你應(yīng)該聲明方法為“靜態(tài)的(static)”因此名稱不會占據(jù)設(shè)備上符號表的空間活逆。
  • 以-fvisibility=hidden構(gòu)建,為的是在你的Library中僅僅讓你的JNI_OnLoad是暴露的拗胜。這將產(chǎn)生更快更少的代碼蔗候,同時避免潛在的與加載到App中的別的library沖突(但它會有個缺陷:當(dāng)App的本地代碼崩潰時候會創(chuàng)建更少的有用錯誤堆棧信息)。

The static initializer should look like this:

標(biāo)準(zhǔn)的初始化代碼如下:

static {
    System.loadLibrary("fubar");
}

The JNI_OnLoad function should look something like this if written in C++:

以C++de寫法JNI_OnLoad函數(shù)應(yīng)該定義如下:

JNIEXPORT 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.

你也可以使用共享庫的全路徑來調(diào)用System.load埂软。對于Android app锈遥,你也許會發(fā)現(xiàn)從context對象中得到應(yīng)用私有數(shù)據(jù)存儲的全路徑是非常有用的。

Using JNI_OnLoad is the recommended approach, but not the only approach. Explicit registration of native methods with RegisterNatives is not required, nor is it necessary that you provide any 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 means that if a method signature is wrong, you won't know about it until the first time the method is actually invoked.

使用JNI_OnLoad是推薦的做法,但不是唯一的做法。顯式的通過RegisterNatives注冊本地方法不是強(qiáng)制的搞挣,提供JNI_OnLoad方法也不是必須的。你可以使用基于特殊命名的“發(fā)現(xiàn)(discovery)”方式來注冊本地方法(更多細(xì)節(jié)見:JNI spec), 雖然這意味著如果一個方法的簽名錯了庆寺,直到真正調(diào)用它之前你是不知道這里的錯誤的。

If you have only one class with native methods, it makes sense for the call to System.loadLibrary to be in that class. Otherwise you should probably make the call from Application so you know that it's always loaded, and always loaded early.

如果定義本地方法只在一個類里定義诉字,那么在這個類里調(diào)用System.loadLibrary是沒有問題的。否則你可能需要在Application里調(diào)用知纷,至少你是知道它們肯定是會被加載的壤圃,且總是很早被加載。

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.

關(guān)于JNI_OnLoad另一點(diǎn)注意的是:任何你在JNI_OnLoad中對FindClass的調(diào)用都發(fā)生在用作加載共享庫的類加載器的上下文(context)中琅轧。一般FindClass使用與“調(diào)用椢樯”頂部方法相關(guān)的加載器,如果當(dāng)中沒有加載器(因為線程剛剛連接)則使用“系統(tǒng)(system)”類加載器乍桂。這就使得JNI_OnLoad成為一個查尋及緩存類引用很便利的地方冲杀。

64-bit considerations

To support architectures that use 64-bit pointers, use a long field rather than an int when storing a pointer to a native structure in a Java field.

為了支持64位的架構(gòu)平臺,當(dāng)存儲指向本地結(jié)構(gòu)的指針時候在Java中應(yīng)使用long類型而不是int類型睹酌。

Unsupported features/backwards compatibility

All JNI 1.6 features are supported, with the following exception:

所有JNI 1.6版本都被支持权谁,除了以下例外:

DefineClass is not implemented. Android does not use Java bytecodes or class files, so passing in binary class data doesn't work.

DefineClass沒有實現(xiàn),Android不使用Java字節(jié)碼或者class文件憋沿,因此傳入二進(jìn)制class數(shù)據(jù)將不會有效旺芽。

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.

在Android 2.0(Eclair)之前,在搜索方法名稱時采章,字符“$”不會轉(zhuǎn)換為對應(yīng)的“_00024”运嗜。要使它正常工作需要使用顯式注冊方式或者將本地方法的聲明移出內(nèi)部類。

  • 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.)

在Android 2.0(Eclair)之前悯舟,使用pthread_key_create析構(gòu)函數(shù)來避免“退出前線程必須分離”檢查是不可行的(運(yùn)行時(runtime)也使用了一個pthread key析構(gòu)函數(shù)担租,因此這是一場看誰先被調(diào)用的競賽)。

  • 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.

在Android 2.2(Froyo)之前抵怎,全局弱引用沒有被實現(xiàn)翩活。如果試圖使用它們,老版本將完全不兼容便贵。你可以使用Android平臺版本號常量來測試系統(tǒng)的支持性菠镇。

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.)
在Android 4.0 (Ice Cream Sandwich)之前,全局弱引用只能傳給NewLocalRef, NewGlobalRef, 以及DeleteWeakGlobalRef(強(qiáng)烈建議開發(fā)者在使用全局弱引用之前都為它們創(chuàng)建強(qiáng)引用hard reference承璃,所以這不應(yīng)該在所有限制當(dāng)中)利耍。 從Android 4.0 (Ice Cream Sandwich)起,全局弱引用能夠像其它任何JNI引用一樣使用了盔粹。

From Android 4.0 (Ice Cream Sandwich) on, weak global references can be used like any other JNI references.

從Android 4.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在老的系統(tǒng)版本中不存在捻脖。詳情查看JNI Local Reference在ICS系統(tǒng)上的改變。

In Android versions prior to Android 8.0, the number of local references is capped at a version-specific limit. Beginning in Android 8.0, Android supports unlimited local references.

在高于Android8.0的版本里中鼠,本地引用的數(shù)量限制在特定于版本可婶。從Android 8.0開始支持不受限制的局部引用個數(shù)。

  • Determining reference type with 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.

在Android4.0(Ice Cream Sandwich)之前援雇,因為使用直接指針導(dǎo)致實現(xiàn)GetObjectRefType是一件不可能的事情矛渴。相反,我們按順序來查看弱全局表惫搏、參數(shù)表和局部表以及局部表具温。第一次匹配到你的直接指針時,就表明你的引用類型是當(dāng)前正在檢測的類型筐赔。這意味著铣猩,例如,如果你在一個全局jclass上使用GetObjectRefType川陆,而這個全局jclass碰巧與作為靜態(tài)本地方法的隱式參數(shù)傳入的jclass一樣的剂习,你得到的結(jié)果是JNILocalRefType而不是JNIGlobalRefType蛮位。

FAQ: Why do I get UnsatisfiedLinkError?

When working on native code it's not uncommon to see a failure like this:

當(dāng)與本地代碼工作時候,經(jīng)常碰到以下的錯誤:

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.

在一些情況下這意味著so庫沒有找到鳞绕,另外一些情況意味著so庫存在但是不能被dlopen(3)打開失仁,錯誤消息異常消息中找到。

Common reasons why you might encounter "library not found" exceptions:

常見的發(fā)生 "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.
  • The library wasn't built with the NDK. This can result in dependencies on functions or libraries that don't exist on the device.
  • so庫不存在或者app不可訪問们何。通過使用adb shell ls -s <path>檢查so庫是否存在以及訪問權(quán)限萄焦。
  • so庫并沒有通過NDK打包,這就導(dǎo)致設(shè)備上并不存在它所依賴的函數(shù)或者庫冤竹。

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:

這意味著運(yùn)行時嘗試去找匹配的方法但是失敗了。一些常見原因如下:

  • The library isn't getting loaded. Check the logcat output for messages about library loading.

  • 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.
    • 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/MapEntry;, say).
  • 庫文件沒有得到加載鹦蠕。檢查日志輸出中關(guān)于庫文件加載的信息冒签。
  • 由于名稱或者簽名錯誤,方法不能匹配成功钟病。這通常是由于:
    • 對于方法的懶查尋萧恕,使用 extern "C"和對應(yīng)的可見性(JNIEXPORT)來聲明C++函數(shù)沒有成功。注意Ice Cream Sandwich之前的版本肠阱,JNIEXPORT宏是不正確的票唆,因此對新版本的GCC使用舊的jni.h頭文件將不會有效。你可以使用arm-eabi-nm查看它們出現(xiàn)在庫文件里的符號屹徘。如果它們看上去比較凌亂(像_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass這樣而不是Java_Foo_myfunc)走趋,或者符號類型是小寫的“t”而不是一個大寫的“T”,這時你就需要調(diào)整聲明了。
    • 對于顯式注冊噪伊,在進(jìn)行方法簽名時可能犯了些小錯誤簿煌。確保你傳入到注冊函數(shù)的簽名能夠完全匹配上日志文件里提示的。記住“B”是byte酥宴,“Z”是boolean啦吧。在簽名中類名組件是以“L”開頭的,以“;”結(jié)束的拙寡,使用“/”來分隔包名/類名,使用“”符來分隔內(nèi)部類名稱(比如說琳水,Ljava/util/MapEntry;)肆糕。

Using javah to automatically generate JNI headers may help avoid some problems.

使用javah自動生成JNI headers或許能避免很多問題。

FAQ: Why didn't FindClass find my class?

(Most of this advice applies equally well to failures to find methods with GetMethodID or GetStaticMethodID, or fields with GetFieldID or GetStaticFieldID.)

這種情況同樣適用于使用GetMethodID或GetStaticMethodID在孝,或使用GetFieldID或GetStaticFieldID的字段查找方法失敗

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;. If you're looking up an inner class, use '$' rather than '.'. In general, using javap on the .class file is a good way to find out the internal name of your class.

確保class名字是正確的格式诚啃,JNI class名以package名字開頭且以斜線分割,例如:java/lang/String. 如果你尋找一個數(shù)組類私沮,應(yīng)當(dāng)以一定數(shù)量的括號開始并且在class前加上‘L’和‘;’始赎。因此,一維數(shù)組應(yīng)當(dāng)為[Ljava/lang/String;。如果你要尋找一個內(nèi)部類造垛,得使用“$”代替“.”魔招。通常在.class上使用javap找出內(nèi)部class的ame是一個好的方式。

If you're using ProGuard, make sure that ProGuard didn't strip out your class. This can happen if your class/method/field is only used from JNI.

如果你在使用ProGuard, 確保ProGuard不要混淆你的類五辽。當(dāng)你的class办斑,method或者field在被JNI使用時候你需要這些防范操作。

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:

如果類名能找到杆逗,你將進(jìn)入class加載環(huán)節(jié)乡翅。FindClass想要啟動在class加載器中找到的關(guān)聯(lián)你代碼的類。檢查調(diào)用堆棧罪郊,如下所示:

Foo.myfunc(Native Method)
Foo.main(Foo.java:10)

The topmost method is Foo.myfunc. FindClass finds the ClassLoader object associated with the Foo class and uses that.

最上面的方法是Foo.myfunc蠕蚜。FindClass發(fā)現(xiàn)關(guān)聯(lián)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 there are no stack frames from 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.

一般運(yùn)行如你所愿悔橄,但你也會陷入問題靶累,如果你自己創(chuàng)建了一個線程(可能通過調(diào)用pthread_create然后又調(diào)用AttachCurrentThread)。這時候你的應(yīng)用程序沒有堆棧信息橄维。如果你在線程中調(diào)用FindClass尺铣,JavaVM將啟動系統(tǒng)級別的類加載器而不是與你應(yīng)用程序關(guān)聯(lián)的類加載器,因此這種情況嘗試去找app指定的classes注定會失敗争舞。

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一次凛忿,并為了隨后的使用緩存住這些引用。作為執(zhí)行JNI_OnLoad的部分竞川,任何FindClass的調(diào)用將使用與函數(shù)(called System.loadLibrary)關(guān)聯(lián)的類加載器(這是一個特定規(guī)則店溢,為了使共享庫的初始化更加方便)。如果你的App正在加載共享庫委乌,F(xiàn)indClass將會使用正確的類加載器床牧。

  • 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.

    通過定義一個含有Class作為參數(shù)的本地方法,以這種方式將一個類的實例進(jìn)入本地方法遭贸。

  • Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.

    在方便的地方緩存ClassLoader對象的引用戈咳,這樣加載類就來的比較直接。不過這需要些工作壕吹。

FAQ: How do I share raw data with native code?

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.

你能會發(fā)現(xiàn)你在某種場景需要既要在java層面也要在native層面訪問一個大的數(shù)據(jù)著蛙。常見的案例是操作bitmap或者音頻資源,以下有兩種基本方案:

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.

你可以把數(shù)據(jù)存在byte[]中耳贬,這樣你可以很快的從java代碼訪問它踏堡。在本地方法那邊,然而咒劲,您不能保證在不復(fù)制數(shù)據(jù)的情況下訪問數(shù)據(jù)顷蟆。在部分實現(xiàn)中诫隅,GetByteArrayElements和GetPrimitiveArrayCritical返回的指針將會確切地指向Java堆棧數(shù)據(jù),但是其他情況則是本地堆棧分配內(nèi)存并做數(shù)據(jù)拷貝帐偎。

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.

或者另一個替代選擇是在direct byte buffer中存儲數(shù)據(jù)逐纬。可以通過java.nio.ByteBuffer.allocateDirect或者JNI的NewDirectByteBuffer 函數(shù)來創(chuàng)建direct byte buffer肮街。不同于常見的byte buffer风题,這種存儲不會在java端堆棧開辟內(nèi)存,并且它總能從本地代碼端訪問(通過GetDirectBufferAddress獲取地址)嫉父。受限于于direct byte buffer的實現(xiàn)沛硅,從java端訪問它的數(shù)據(jù)速度比較慢。

The choice of which to use depends on two factors:

這些方案糾結(jié)用哪種好取決于以下兩個因素:

  1. Will most of the data accesses happen from code written in Java or in 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.)
  1. 否絕大部分的數(shù)據(jù)訪問發(fā)生Java端還是C/C++端绕辖?
  2. 如果數(shù)據(jù)最終被傳到系統(tǒng)api摇肌,會以哪種形式存在?(比如:如果數(shù)據(jù)最終被傳給一個方法并以byte[]傳入,用direct ByteBuffer處理或許是不明智的)

If there's no clear winner, use a direct byte buffer. Support for them is built directly into JNI, and performance should improve in future releases.

如果沒有明顯的贏家仪际,請還是使用direct byte buffer围小。因為JNI默認(rèn)就是支持的并且在未來它的性能也會被得到提高。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末树碱,一起剝皮案震驚了整個濱河市肯适,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌成榜,老刑警劉巖框舔,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赎婚,居然都是意外死亡刘绣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門挣输,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纬凤,“玉大人,你說我怎么就攤上這事撩嚼⊥J浚” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵完丽,是天一觀的道長向瓷。 經(jīng)常有香客問我,道長舰涌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任你稚,我火速辦了婚禮瓷耙,結(jié)果婚禮上朱躺,老公的妹妹穿的比我還像新娘。我一直安慰自己搁痛,他們只是感情好长搀,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鸡典,像睡著了一般源请。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上彻况,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天谁尸,我揣著相機(jī)與錄音,去河邊找鬼纽甘。 笑死良蛮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悍赢。 我是一名探鬼主播决瞳,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼左权!你這毒婦竟也來了皮胡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赏迟,失蹤者是張志新(化名)和其女友劉穎屡贺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瀑梗,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烹笔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了抛丽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谤职。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖亿鲜,靈堂內(nèi)的尸體忽然破棺而出允蜈,到底是詐尸還是另有隱情,我是刑警寧澤蒿柳,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布饶套,位于F島的核電站,受9級特大地震影響垒探,放射性物質(zhì)發(fā)生泄漏妓蛮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一圾叼、第九天 我趴在偏房一處隱蔽的房頂上張望蛤克。 院中可真熱鬧捺癞,春花似錦、人聲如沸构挤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筋现。三九已至唐础,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矾飞,已是汗流浹背一膨。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凰慈,地道東北人汞幢。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像微谓,于是被迫代替她去往敵國和親森篷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,294評論 0 10
  • 以下是用swift寫的一個錄音demo, 把聲音錄制保存到沙盒里, 并獲得聲音的分貝值大小. (親測可用!) im...
    coder_xiang閱讀 2,165評論 0 0
  • 凌晨三點(diǎn) 冷雨敲門前 記憶一條條 脫掉羽絨線 露出白皙肌膚和臉 包容著骨頭血液 悱惻纏綿 因為愛 千里迢迢 流淌過...
    小小菠蘿閱讀 105評論 0 3
  • 這世界上很多事情豺型,看起來就像彩虹一樣炫目而神奇仲智,實際上背后蘊(yùn)含著隨處可見的原理。就好像靜兒幾年前買過一件超貴...
    編程一生閱讀 332評論 0 0
  • 我感覺就這樣順其自然的過了一年…十年…一個世紀(jì)…… 看書一直是我的愛好姻氨,每天都會把自己的心情和對生活的心得寫在厚厚...
    無明市閱讀 130評論 0 0