Make writing a habit together! This is the fifth day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

1. The background

JNI defines how Android bytecode compiled using code written in the Java or Kotlin programming languages interacts with native code written in C/C++. JNI is a standard protocol for loading code from dynamically shared libraries, regardless of hardware constraints, which in some cases is more efficient than using Java directly. We can use the JNI heap view in The Memory Performance Profiler of Android Studio 3.2 and later to view global JNI references and see where they are created and deleted. Based on the prompts in the official Android NDK documentation and my own development process, this paper summarizes the JNI development criteria from the perspective of performance, maintainability, robustness and so on.

2. General guidelines

We want to minimize the footprint of the JNI layer, and we need to consider several aspects to achieve this. We should try to follow these guidelines (in order of importance, starting with the most important) :

  • ** Minimize the number of times resources are marshalled across JNI layers. ** The cost of marshalling across JNI layers is very high. Try to design an interface that minimizes the amount of data that needs to be marshalled and the frequency with which it must be marshalled.
  • Whenever possible, avoid asynchronous communication between code written in a managed programming language and code written in C++. This makes the JNI interface easier to maintain. In general, you can simplify asynchronous interface updates by keeping them asynchronous in the same programming language you write them in. For example, it is better to use the Java programming language to make callbacks between two threads (where one thread makes a blocking C++ call and then notifies the interface thread when the blocking call is complete) than to call C++ functions via JNI from an interface thread using Java code.
  • Minimize the number of threads that need to touch or are touched by JNI. If you do need to use thread pools in both Java and C++, try to keep JNI communication between pool owners rather than individual worker threads.
  • ** saves interface code in a small number of easily recognizable C++ and Java source locations for future refactoring. ** Use JNI auto-generated libraries as appropriate.

3. The JavaVM and JNIEnv

JNI defines two key data structures, “JavaVM” and “JNIEnv.” Both are essentially second-level Pointers to a function table. (in the C++ version, they are classes that have Pointers to function tables and member functions for each JNI function called indirectly through that table.) JavaVM provides “call interface” functions that you can use to create and destroy JavaVM functions. In theory, you can have multiple JavavMs per process, but Android only allows one.

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

This JNIEnv will be used for thread-local storage. Therefore, you cannot share JNIEnv between threads. If a piece of code cannot get its own JNIEnv by other means, you should share the corresponding JavaVM and then use GetEnv to discover the thread’s JNIEnv. (Suppose the thread contains a JNIEnv; See AttachCurrentThread below.)

JNIEnv and JavaVM C declarations are different from C++ declarations. The jni.h” include file provides different type definitions, depending on whether the file is included in C or C++. Therefore, we do not recommend adding the NIEnv argument to header files included in either language. (To put it another way: If your header file requires #ifdef __cplusplus, and anything in that header references JNIEnv, you may have to do something extra.)

4. The thread

All threads are Linux threads and are scheduled by the kernel. Threads are typically started from managed code (using thread.start ()), but can be created elsewhere and then attached to JavaVM. For example, you can use AttachCurrentThread () or AttachCurrentThreadAsDaemon additional through pthread_create () function () or STD: : thread startup thread. Before attaching, the thread does not contain any JNIEnv and cannot call JNI.

In general, it is best to use Thread.start() to create any threads that need to invoke Java code. Doing so ensures that you have enough stack space, belong to the correct ThreadGroup, and use the same ClassLoader as your Java code. Also, it’s easier to set thread names for debugging in Java than through native code (if you have pthread_t or thread_t, see pthread_setname_NP (); If you have STD ::thread and need pthread_T, see STD :: Thread ::native_handle()).

Additional natively created threads build the java.lang.Thread object and add it to the “main” ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread() on an attached thread is a null operation.

Android does not suspend threads that execute native code. If garbage collection is in progress, or if the debugger has issued a suspend request, Android will suspend JNI the next time the thread calls it.

Threads attached via JNI must call DetachCurrentThread() before exiting. This can be tricky to code directly, but in Android 2.0 (Eclair) and later, you can use pthread_key_create() to define the destructor that will be called before the thread exits, and then call DetachCurrentThread(). (Use this key with pthread_setSpecific () to store JNIEnv in ththread local storage; This way, the key is passed to your destructor as a parameter.

5. Jclass, jmethodID and jfieldID

To access the fields of an object through native code, do the following:

  • useFindClassGets a class object reference to a class
  • useGetFieldIDGets the field ID of the field
  • Use the appropriate function to get the contents of the field, for exampleGetIntField

Similarly, if you need to invoke a method, you first get the class object reference and then the method ID. A method ID is usually just a pointer to an internal runtime data structure. Finding a method ID may require multiple string comparisons, but once such an ID is obtained, the actual call to get a field or call a method can be done very quickly.

If performance is important, we recommend that you look up these values once and cache the results in native code. Since each process can contain only one JavaVM, it makes sense to store this data in a static local structure.

The class reference, field ID, and method ID are guaranteed to be valid until the class is unloaded. It is rare, but not impossible in Android, for the system to cancel loading a class only if all classes associated with the ClassLoader can be garbage collected. Note, however, that jClass is a class reference and must be protected by calling NewGlobalRef (see the next section).

If you want to cache the method ID when the class is loaded and automatically recache it when the class is unloaded after reloading, the correct way to initialize the method ID is to add a piece of code similar to the following to the corresponding class:

    companion object {
        /* * We use a static class initializer to allow the native code to cache some * field offsets. This native function looks up and caches interesting * class/field/method IDs. Throws on failure. */
        private external fun nativeInit(a)

        init {
            nativeInit()
        }
    }
    
Copy the code

Create the nativeClassInit method in the C/C++ code that performs the ID lookup. This code is executed once when the class is initialized. If you want to unload the class and reload it later, the code is executed again.

6. 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 a local reference is valid for the duration of the current native method running in the current thread. After the native method returns, the reference is invalid even if the object itself continues to exist.

This applies to all subclasses of Jobject, including JClass, JString, and jarray. (When extended JNI checking is enabled, the runtime warns you of most reference misuse problems.)

The only way to get a non-local reference is through the NewGlobalRef and NewWeakGlobalRef functions.

If you want to keep a reference for a long time, you must use a “global” reference. The NewGlobalRef function takes a local reference as an argument and then returns a global reference. The global reference is guaranteed to be valid until DeleteGlobalRef is called.

This pattern is typically used when caching jclasses returned by FindClass, for example:

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

All JNI methods accept local and global references as arguments. References to the same object may have different values. For example, successive calls to NewGlobalRef on the same object may return different values. ** To know whether two references refer to the same object, you must use the IsSameObject function. ** Never use == to compare references in native code.

If you use this symbol, you cannot assume that an object reference is a constant or unique value in native code. The 32-bit value of an object may be different when the same method is called twice; When a method is called consecutively, two different objects may have the same 32-bit value. Do not use the jobject value as a key.

Programmers need to “not over-allocate” local references. In practice, this means that if you are creating a lot of local references (perhaps while running an array of objects), you should release them manually using DeleteLocalRef rather than having JNI do it for you. The implementation need only keep 16 local references for slot, slot, so if you need more should be deleted as needed, or use EnsureLocalCapacity/PushLocalFrame retain more slots.

Note that jfieldID and jmethodID are opaque types, are not object references, and should not be passed to NewGlobalRef. The primitive data Pointers returned by functions such as GetStringUTFChars and GetByteArrayElements also do not belong to objects. (These Pointers can be passed between threads and remain valid until the matching Release call completes.)

There is one other unusual circumstance that deserves a separate mention. If you attach native threads with AttachCurrentThread, the code you run will never automatically release local references before the thread is detached. Any local references you create must be removed manually. In general, any native code that creates a local reference in a loop may require some manual deletion.

Use global references with caution. Global references are inevitable, but they are difficult to debug and can lead to memory (bad) behavior that is difficult to diagnose. All other things being equal, the fewer global references, the better the solution is likely to work.

7. The character string is UTF-8 and UTF-16

The Java programming language uses UTF-16. JNI also provides a way to use the modified UTF-8 for convenience. The modified encoding is useful for C code because it encodes \u0000 as 0xC0 0x80 instead of 0x00. The advantage of this is that you can rely on zero-terminated C-style strings, which are ideal for use with standard LIBC string functions. The downside, however, is that you can’t pass arbitrary UTF-8 data to JNI and expect it to work.

If possible, it is usually faster to perform operations using UTF-16 strings. Android does not currently require a copy of GetStringChars, which needs to be allocated and converted to UTF-8. Note that UTF-16 strings do not terminate with zero and allow \u0000, so you need to preserve the string length and the jchar pointer.

Don’t forget to Release the string you Get. The string function returns jCHAR * or Jbyte *, which are C-style Pointers to raw data rather than local references. These Pointers are guaranteed to be valid until Release is called, which means they are not released when the native method returns.

Data passed to NewStringUTF must be in the modified UTF-8 format. A common mistake is to read character data from a file or network data stream and pass it unfiltered to NewStringUTF. Unless you are sure that 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 the modified UTF-8 format accordingly. If you do not, utF-16 conversions can produce unexpected results. CheckJNI, enabled by default for the emulator, scans for strings and aborts the virtual machine if invalid input is received.

8. Raw array

JNI provides functions that access the contents of array objects. Although arrays of objects can only be accessed one item at a time, arrays of primitive types can be read and written directly as if they were declared in C.

To make the interface as efficient as possible without limiting the implementation of the virtual machine, the Get ArrayElements family of calls allows the runtime to return a pointer to the actual element, or to allocate some memory and copy it. Either way, the original pointer returned is guaranteed to be valid until the corresponding Release call is issued (which means that if the data is not copied, the position of the array object is fixed and cannot be repositioned during heap compression). ** You must Release each array you Get. ** Also, if the Get call fails, you must ensure that your code does not attempt to Release the NULL pointer later.

You can determine whether data has been copied by passing a non-null pointer to the isCopy parameter. But this is of little use.

The mode argument taken by the Release call can be one of three values. The operation performed by the runtime depends on whether the pointer it returns is to actual data or to a copy of the data:

  • 0
    • Actual data: The array object is not fixed.
    • Data copy: Data has been copied back. The buffer containing the corresponding copy is released.
  • JNI_COMMIT
    • Actual data: No operation is performed.
    • Data copy: Data has been copied back. Buffers containing corresponding copies are not released.
  • JNI_ABORT
    • Actual data: The array object is not fixed. Early writes are not aborted.
    • Data copy: the buffer containing the corresponding copy is released; Any changes made to the copy will be lost.

One reason to check the isCopy tag is to see if you need to call Release using JNI_COMMIT after making changes to the array. If you alternate between making changes and executing code that uses array content, you can skip the null operation commit. Another reason to check this tag might be to handle JNI_ABORT effectively. For example, you might want to take an array, modify it appropriately, pass the fragment to another function, and then discard the changes you made. If you know that JNI is going to create a new copy for you, there is no need to create another “modifiable” copy. If JNI is going to pass you the raw data, you need to make your own copy.

It is a common mistake to assume that a Release call can be skipped when *isCopy is false (a situation repeated in the sample code). That’s not the case. If no copy buffers are allocated, the raw memory must be fixed and cannot be moved by the garbage collector.

Also note that the JNI_COMMIT tag does not free the array, and you eventually need to call Release again using other tags.

9. Regional calls

If you just want to copy data, using alternative methods for calls like Get

ArrayElements and GetStringChars can be useful. Consider running the following code:

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

This grabs the array, copies the first len byte element out of the array, and frees the array. Get calls fix or copy the contents of the array, depending on the implementation. The code copies the data (possibly a second time) and then calls Release; In this case, JNI_ABORT ensures that there is no opportunity for a third copy.

You can also do the same in a simpler way:

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

This approach has many advantages:

  • You need one JNI call instead of two to reduce overhead.
  • No fixed or additional replication of data is required.
  • Reduce programmer error risk because there is no forgetting to call after an operation failsReleaseThe risk.

Similarly, you can use the Set

ArrayRegion call to copy data into an array, and use GetStringRegion or GetStringUTFRegion to copy characters out of String.

Abnormal 10.

** Most JNI functions cannot be called when an exception is suspended. ** Your code should notice the exception (through the return value ExceptionCheck or ExceptionOccurred from the function) and return it, or clear the exception and handle it.

In case of exception suspension, you can only call the following JNI functions:

  • DeleteGlobalRef
  • DeleteLocalRef
  • DeleteWeakGlobalRef
  • ExceptionCheck
  • ExceptionClear
  • ExceptionDescribe
  • ExceptionOccurred
  • MonitorExit
  • PopLocalFrame
  • PushLocalFrame
  • Release<PrimitiveType>ArrayElements
  • ReleasePrimitiveArrayCritical
  • ReleaseStringChars
  • ReleaseStringCritical
  • ReleaseStringUTFChars

Many JNI calls throw exceptions, but often provide an easier way to check for failures. For example, if NewString returns a non-null value, there is no need to check for exceptions. However, if you call a method (using a function such as CallObjectMethod), you must always check for exceptions, because if the system throws an exception, the return value will be invalid.

Note that exceptions thrown by interpreted code do not expand the native stack frame, and Android does not yet support C++ exceptions. The JNI Throw and ThrowNew directives simply set exception Pointers in the current thread. After returning from native code to managed code, these instructions notice the exception and handle it accordingly.

Native code can “catch” exceptions by calling ExceptionCheck or ExceptionOccurred, and then clear them with ExceptionClear. As usual, discarding exceptions without handling them can be problematic.

Because there are no built-in functions to manipulate the Throwable object itself, if you want to get the exception String, you need to go to the Throwable class and look for getMessage “()Ljava/lang/String;” And call the method; If the result is a non-null value, use GetStringUTFChars to get what can be passed to printf(3) or its equivalent.

11. Extended checks

JNI rarely does error checking. Errors usually lead to crashes. Android also provides a pattern called CheckJNI, where the JavaVM and JNIEnv function table Pointers have been switched to function tables that perform an extended set of checks before calling the standard implementation.

Additional checks include:

  • Array: Attempts to allocate arrays of negative size.
  • Error pointer: will the wrong jarray/jclass/jobject/jstring passed to JNI calls, or pass NULL pointer with cannot be set to NULL parameter JNI calls.
  • Class name: Passes everything except the “Java /lang/String” style of the class name to the JNI call.
  • Key calls: JNI is called between the “key” GET and its corresponding release.
  • Direct byte buffer: Error arguments are passed toNewDirectByteBuffer.
  • Exception: JNI is called when an exception is suspended.
  • JNIEnv* : Use the JNIEnv* in the error thread.
  • JfieldID: Using NULL jfieldID, or using jfieldID to set a field to a value of the wrong type (for example, trying to assign a StringBuilder to a String field), or using jfieldID for a static field to set an instance field (and vice versa), Or use jfieldID in one class with an instance of another class.
  • JmethodID: is callingCall*MethodJNI uses the wrong type of jmethodID: returns incorrect type, static/non-static mismatch, type “this” error (for non-static calls), or class error (for static calls).
  • Reference: Use of a reference of the wrong typeDeleteGlobalRef/DeleteLocalRef.
  • Release mode: Pass the wrong Release mode to the Release call (except0,JNI_ABORTJNI_COMMITBeyond).
  • Type safety: Returning incompatible types from native methods (for example, returning StringBuilder from methods that declare a return String).
  • Utf-8: An invalid modified UTF-8 byte sequence is passed to the JNI call.

(Accessibility of methods and fields is still not checked: access restrictions do not apply to native code.)

You can enable CheckJNI in several ways.

If you are using an emulator, CheckJNI is enabled by default.

If you are using a device that has root privileges, you can restart the runtime and enable CheckJNI using the following command sequence:

adb shell stop
    adb shell setprop dalvik.vm.checkjni true
    adb shell start
Copy the code

In either case, when the runtime starts, you will see something like this in the logcat output:

D AndroidRuntime: CheckJNI is ON
Copy the code

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

adb shell setprop debug.checkjni 1
Copy the code

This does not affect applications already running, but any applications launched since then will enable CheckJNI. (Changing the property to any other value, or simply restarting the application, deactivates CheckJNI again.) In this case, the next time the application starts, you’ll see something like this in the logcat output:

D Late-enabling CheckJNI
Copy the code

You can also set the Android: Debuggable property in the application manifest to enable CheckJNI for your application. Note that the Android build tool does this automatically for some build types.

The native library

You can load native code from shared libraries using the standard System.loadLibrary.

In fact, the old Version of Android’s PackageManager has errors, resulting in unreliable installation and update of the native library. The ReLinker project addresses this and other native library loading issues.

Call System.loadLibrary (or relinker.loadLibrary) from a static class initializer. The argument is the “undecorated” library name, so to load libfubar.so, you need to pass “fubar”.

If you have only one class with native methods, it makes sense to place calls to System.loadLibrary in the static initializer for that class. Otherwise, you might need to make the call from Application so that you know that the library is always loaded, and always loaded ahead of time.

The runtime can find your native methods in two ways. You can use RegisterNatives to show registering native methods, or you can have the runtime use DLSYM for dynamic searches. RegisterNatives have the advantage that you can pre-check the presence of symbols, and you can also get a smaller, faster shared library by exporting only JNI_OnLoad. The advantage of having a function discovered by the runtime is that there is slightly less code to write.

To use RegisterNatives, perform the following operations:

  • provideJNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)Function.
  • inJNI_OnLoadIn the useRegisterNativesRegister all native methods.
  • use-fvisibility=hiddenBuild to export only yours from your libraryJNI_OnLoad. This produces faster and smaller code and avoids potential conflicts with other libraries loaded into the application (but creates a stack trail that is less useful if the application crashes in native code).

The static initializer should look like this:

KotlinJava

    companion object {
        init {
            System.loadLibrary("fubar")}}Copy the code

If written in C++, the JNI_OnLoad function would look like this:

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        JNIEnv* env;
        if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) ! = JNI_OK) {return JNI_ERR;
        }

        // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
        jclass c = env->FindClass("com/example/app/package/MyClass");
        if (c == nullptr) return JNI_ERR;

        // Register your class' native methods.
        static const JNINativeMethod methods[] = {
            {"nativeFoo"."()V".reinterpret_cast<void*>(nativeFoo)},
            {"nativeBar"."(Ljava/lang/String; I)Z".reinterpret_cast<void*>(nativeBar)},
        };
        int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
        if(rc ! = JNI_OK)return rc;

        return JNI_VERSION_1_6;
    }
Copy the code

If you want to change to using the Discovery native method, you need to name it in a specific way (see the JNI specification for details). This means that if the method signature is wrong, you won’t know about it until the first time the method is actually called.

Any FindClass call from JNI_OnLoad resolves the class in the context of the classloader used to load the shared library. When called from another context, FindClass uses the classloader associated with the method at the top of the Java stack, or if not (because the call came from the native thread just attached), the “system” classloader is used. Because the system class loader does not know the applied classes, you will not be able to use FindClass to find your own classes in that context. This makes JNI_OnLoad a convenient place to find and cache classes: once you have a valid Jclass, you can use it from any additional thread.

12. 64-bit precautions

To support architectures that use 64-bit Pointers, use long fields instead of int when storing Pointers to native structures in Java fields.

13. Unsupported features/backward compatibility

Support for all JNI 1.6 features except the following:

  • DefineClassUnrealized. Android doesn’t use Java bytecode or class files, so passing in binary class data doesn’t work.

In order to be backward compatible with older Versions of Android, you may want to pay attention to the following:

  • Find native functions on the fly

    Prior to Android 2.0 (Eclair), the “$” character was incorrectly converted to” _00024 “when searching for method names. To solve this problem, use explicit registration or move native methods out of the inner class.

  • Separate thread

    Prior to Android 2.0 (Eclair), it was not possible to use the pthread_KEY_CREATE destructor to avoid the “must detach thread before exit” check. (The runtime also uses the pThread Key destructor, so it sees which function is called first.)

  • Weak global reference

    Prior to Android 2.2 (Froyo), weak global references were not implemented. Older versions would have strongly rejected the use of weak global references. You can use the Android platform version constant to test support.

    Prior to Android 4.0 (Ice Cream Sandwich), weak global references could only be passed to NewLocalRef, NewGlobalRef, and DeleteWeakGlobalRef. (The specification strongly recommends that programmers create hard references to weak globals before handling them, so there should be no restrictions.)

    Starting with Android 4.0 (Ice Cream Sandwich), weak global references can be used just like any other JNI reference.

  • Local reference

    Prior to Android 4.0 (Ice Cream Sandwich), local references were actually direct Pointers. Ice Cream Sandwich adds the necessary indirect support for a better garbage collector, but this means that a large number of JNI errors in older versions are not detected. For more details, see JNI Local Reference Changes in ICS.

    In versions of Android prior to Android 8.0, the maximum number of local references depended on version-specific restrictions. Starting with Android 8.0, Android supports unlimited local references.

  • Use GetObjectRefType to determine the reference type

    Prior to Android 4.0 (Ice Cream Sandwich), GetObjectRefType was not implemented properly due to the use of direct Pointers (see above). Instead, we use heuristics, which look at weak global tables, parameters, local tables, and global tables in sequence. The first time your direct pointer is found, the method reports that your reference type is exactly the type to be checked. This means, for example, that if you call GetObjectRefType on a global JClass that happens to be the same as the jClass passed as an implicit argument to a static native method, Then you get JNILocalRefType instead of JNIGlobalRefType.

14. FAQ: Why do I receive itUnsatisfiedLinkError?

When working with native code, you often see failure messages like this:

java.lang.UnsatisfiedLinkError: Library foo not found
Copy the code

In some cases, literally – libraries cannot be found. In other cases, the library does exist but cannot be opened by dlopen(3), and the failure details can be found in the detailed message about the exception.

Common reasons why you might experience a “library not found” exception are as follows:

  • The library does not exist or the application cannot be accessed. Please use theadb shell ls -l <path>Check the existence and permissions of libraries.
  • Libraries are not built using the NDK. This can lead to dependencies on functions or libraries that do not exist on the device.

The UnsatisfiedLinkError failure messages of other classes are as follows:

java.lang.UnsatisfiedLinkError: myfunc
            at Foo.myfunc(Native Method)
            at Foo.main(Foo.java:10)
Copy the code

In logcat, you will see the following:

W/dalvikvm( 880): No implementation found for native LFoo; .myfunc ()VCopy the code

This means that the runtime tried to find a match method but failed. Some common causes of this problem are as follows:

  • The library is not loaded. Check the logcat output for messages about the library load.
  • The name or signature did not match, so the method could not be found. This is usually caused by:
    • Not available for delayed method lookupsextern "C"And corresponding visibility (JNIEXPORTDeclare C++ functions. Note that before Ice Cream Sandwich, the JNIEXPORT macro was incorrect, so the new GCC was compared with the oldjni.hPairing won’t work. You can usearm-eabi-nmView symbols displayed in the library; If the symbols seem fragmented (similar to_Z15Java_Foo_myfuncP7_JNIEnvP7_jclassRather thanJava_Foo_myfunc), or if the symbol type is lowercase “T” instead of uppercase “t”, you need to adjust the declaration.
    • For explicit registration, a slight error occurs when entering a method signature. Make sure that what is passed to the registration call matches the signature in the log file. Remember, “B” meansbyte, “Z” meansboolean. The class name component in the signature begins with “L” and ends with “;” At the end, use “/” to separate package/class names, and use”
      “To separate the inner class names (for example L j a v a / u t i l / M a p “To separate the inner class names (e.g. ‘Ljava/util/Map
      Entry; `).

Automatic generation of JNI headers using Javah may help avoid some problems.

Frequently asked Questions: WhyFindClassCan’t find my class?

(Much of the advice below also applies if you can’t find a method using GetMethodID or GetStaticMethodID, or if you can’t find a field using GetFieldID or GetStaticFieldID.)

Make sure the class name string is properly formatted. The JNI class name starts with the package name and is separated by a slash, such as Java /lang/String. If you are looking for an array class, you need to start with the appropriate number of English square brackets, and you must also use “L” and “;” Enclose the class, so the one-dimensional array of String will be [Ljava/lang/String;. If you are looking for an inner class, use “$” instead of”. “In general, using Javap on a.class file is a good way to look up the inner name of a class.

If you want to enable code reduction, be sure to configure the code to remain. It is important to configure proper retention rules, otherwise the code compressor may remove classes, methods, or fields that are only used in JNI.

If the class name is in the correct form, you may be experiencing classloader problems. FindClass needs to start a class search in the classloader associated with your code. It checks the call stack as follows:

    Foo.myfunc(Native Method)
        Foo.main(Foo.java:10)
Copy the code

The top-level method is foo.myfunc. FindClass finds the ClassLoader object associated with class Foo and uses it.

Taking this approach will usually accomplish what you want to do. If you create your own thread (possibly by calling pthread_CREATE and then attaching it with AttachCurrentThread), you may run into trouble. Now there are no stack frames in your application. If FindClass is called from this thread, JavaVM starts in the “system” classloader (not the classloader associated with the application), so attempts to find application-specific classes fail.

You can solve this problem in the following ways:

  • inJNI_OnLoadExecute once inFindClassFind, and then cache the class reference for later use. In the implementationJNI_OnLoadAny of the processes issuedFindClassAll calls are used and calledSystem.loadLibraryClass loaders associated with the function of. (This is a special rule to make library initialization easier.) If your application code loads libraries,FindClassThe correct class loader will be used.
  • Get the Class parameter by declaring a native method and passing it inFoo.classTo pass an instance of the class to the function that needs it.
  • Cache pairs in a convenient locationClassLoaderObject, and then emitted directlyloadClassThe call. This takes some effort to accomplish.

16. FAQ: How do I use native code to share raw data?

You may find yourself in situations where you need to access large raw data buffers through both managed and native code. Common examples include manipulating bitmaps or sound samples. You can solve this problem in two basic ways.

You can store data in byte[]. This makes it very quickly accessible through managed code. But with native code, there is no guarantee that you will be able to access the data without copying it. In some implementations, GetByteArrayElements and GetPrimitiveArrayCritical returns pointer to the raw data in the managed heap actual, but in other implementations, it will be allocated buffer and copy the data on the native heap.

Another approach is to store the data in a direct byte buffer. Such buffer can use Java nio. ByteBuffer. AllocateDirect or JNI NewDirectByteBuffer function creates. Unlike regular byte buffers, storage is not allocated on the managed heap and is always directly accessible through native code (using GetDirectBufferAddress to get the address). Depending on how direct byte buffer access is implemented, accessing data through managed code can be very slow.

Choosing which method to use depends on two factors:

  1. Is most of the data accessed through code written in Java or C/C++?
  2. If the data is eventually passed to the system API, what format must it be in? (For example, if the data ends up being passed to a function that uses byte[], then theByteBufferIt may not be wise to handle data in.

Use direct byte buffers if the two approaches are similar. Support for both approaches is directly built into JNI, and performance should improve in future releases.