Android Official Development Document Training Series Course Chinese version: The JNI correlation of Android

Source: Internet
Author: User
Tags throwable

Original address: http://android.xsoftlab.net/training/articles/perf-jni.html

JNI is all called Java Native Interface, Chinese meaning is the Java local interface. It defines the way in which Java code interacts with C + + code. It is a bridge between the two and supports loading code from a dynamic shared library. It's a little complicated, but its execution is pretty high.

If you are not familiar with JNI, you can use the Java Native Interface specification to learn about the approximate workflow of JNI and the features of JNI.

JAVAVM and JNIEnv

JNI defines two key data structures: "JAVAVM" and "jnienv". Both functions are essentially pointer tables that point to function pointers. JAVAVM provides an "interface call" feature that allows you to create and destroy JAVAVM. Theoretically, each process can have multiple virtual machines, but only one is allowed in Android.

The jnienv provides most of the JNI functionality. Any local method takes jnienv as the first callback parameter.

JNIEnv is used for thread-local storage. For this reason, it is not possible to share jnienv between threads. If it is not possible to obtain its corresponding JNIEnv object by other means, then the JAVAVM should be shared first, and then the jnienv of that thread is obtained through the GETENV function (assuming that the thread has a jnienv, please look down).

C differs from C + + in declaring jnienv and JAVAVM differently. The header file "jni.h" provides a different type definition for C or C + +. For this reason, it is not a wise idea to include the jnienv parameter in the header file.

Thread

All threads in Android are Linux threads, which are executed by the kernel. Typically started by controlled code (such as Thread.Start), but can also be created elsewhere, and then attached to JAVAVM. For example, a thread can be created by the Pthread_create function and then appended to the JAVAVM by Attachcurrentthread or Attachcurrentthreadasdaemon.

Android does not suspend threads that are executing local code. If garbage collection is in progress, or the debugger initiates a pending request, Android pauses the thread the next time the JNI call is made.

Threads that are attached through JNI must call the Detachcurrentthread function before exiting.

Jclass, Jmethodid, and Jfieldid

If you need to access the properties of an object in your local code, you need to do the following:

    • Get a reference to a class object by Findclass
    • Get the ID of a property by Getfieldid
    • Get the contents of an object by a corresponding method, such as Getintfield

Accordingly, if you want to invoke a method, first get a reference to the class object, and then get the ID of the method. The ID usually just points to an internal run-time data structure. Finding these methods usually requires several string pairs, but once found, the later fetch properties or method calls will be very fast.

If performance is important to you, you should cache the properties or methods after they are found. Because only one JAVAVM is allowed per process in Android, it is reasonable to cache the data in a static local structure.

The reference to the class, the ID of the property, and the ID of the method guarantee that they are valid until the class is unloaded. A class is unloaded only in this case: the ClassLoader associated with the class can also be recycled. Although this is a low probability, it is not possible in Android.

If you want to cache these IDs when the class is loaded and re-load them after the class is unloaded, the most correct way to do this is to add a piece of code:

    /*     * We use a 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.     */    privatestaticnativevoidnativeInit();    static {        nativeInit();    }

Create a method, named Nativeclassinit, in C + + code that is used for the lookup and caching of IDs. This method is executed once when the class is initialized. Even if the class is unloaded and reloaded, the method will be executed once.

Local references, global references

Each parameter that is called back to the local method, and almost all of the objects returned by the Jni method, are local variables. This means that all local variables within the method in the current thread are legal. after the local method returns, although the object still survives, the reference is invalid.

This applies to all Jobject subclasses: Jclass, Jstring, and Jarray.

The only way to get a non-local variable is through the Newglobalref and NEWWEAKGLOBALREF functions.

If you need to hold a reference for a long time, you must use a global reference. The Newglobalref function converts a local reference to a global reference. The global reference is valid until the Deleteglobalref method is called.

This pattern is typically used to cache a Jclass object returned by Findclass:

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

All JNI methods can use these two references as parameters. However, referencing the same values may have different results. For example, two consecutive calls to the same reference as a parameter newglobalref may get different values. If you want to see whether two references point to the same object, you must use the Issameobject function. never use "= =" in your local code to compare two references.

Never assume that an object reference in your local code is a constant or unique. the method invocation of an object represented by a 32-bit value may be different from the next call, possibly because two different objects have the same 32-bit value. Do not use the value of Jobject as a key.

Programmers are often asked not to overdo the application of local variables. This means that if you create a large number of local variables, you should manually release them through the DELETELOCALREF function instead of letting JNI do it for you.

Note that jfieldids, Jmethodid are not object references, so they cannot be passed to the NEWGLOBALREF function. The Getstringutfchars function and the original data pointer returned by the Getbytearrayelements function are also not objects.

An unusual situation requires a separate explanation: if the Attachcurrentthread function is attach to a local thread, all local variables in the code are not automatically freed until the thread is Detache. Any local variables that are created need to be manually deleted.

UTF-8 and UTF-16 strings

The Java language uses a UTF-16 string. For the sake of convenience, the method provided by JNI works under the modified UTF-8 string. The revised encoding is useful for the C language code because it encodes the \u0000 encoding in order to 0xc0 0x80.

don't forget to release the string you obtained . String functions return jchar* or jbyte*, which are pointers to raw data rather than local references. They are valid until they are released, which means that they are not released after the local method returns.

the data passed to the NEWSTRINGUTF function must be in the modified UTF-8 format . A common mistake is to read the string data from a file stream or a network stream, and then give it to the NEWSTRINGUTF function directly without filtering. Unless you know that the data is 7-bit ASCII, you will need to remove high-level ASCII strings or convert them to the correct modified UTF-8 format. If you do not, the result of the conversion may not be what you want to see. Additional JNI checks will scan strings and warn you that this is invalid data, but they will not catch anything.

Original array

JNI provides the ability to access an array of objects. However, only one element can be accessed at the same time, I ask read and write operations directly to the array, as if declared directly in C.

To make the JNI interface as efficient as possible and not limited by the implementation of the virtual machine, the related function that calls getarrayelements can return a pointer to the actual value, or can request some memory to complete the replication. Either way, the returned pointer is guaranteed to be valid until the appropriate release method is triggered. You must release each array that you have obtained . If the Get method fails, you also need to ensure that you do not release an empty pointer object.

You can use the Iscopy parameter to detect whether an array is copied by a pointer, which is useful.

The release method requires a mode parameter, which has three values. The action performed by the runtime depends on whether it returns a pointer to the actual data or a copy of the pointer:

    • 0
      • Actual pointer: A non-final decorated array object
      • Copy of the pointer: copy of the array data, the copied buffer will be released
    • Jni_commit
      • Actual pointers: Don't do anything
      • Copy of the pointer: copy of the array data, the copied buffer will not be released
    • Jni_abort
      • Actual pointer: A non-final decorated array object. Earlier writes are not aborted.
      • Copy of the pointer: the copied buffer is freed, and any changes in the buffer are lost.

One of the reasons to check the ISCOPY flag is to know if you need to call Jni_commit's related release method after making changes to the array, and if you want to change an operation that is making changes and reading the contents of the arrays, you can skip this operation based on that flag. Another possible cause is the effective handling of jni_abort. For example, you might want to get an array and then pass it on to a function after it has been modified. If you know that JNI will make a copy for you, then you don't need to create another editable copy. If JNI passes back the raw data, then you need to create a copy yourself.

A common mistake is that if *iscopy is false, then the related deallocation method can be not called. However, this is not the case, if the copy buffer is not requested, the original data memory must always be occupied, and will not be reclaimed by the garbage collector.

Also note that Jni_commit does not release the array, and you need to perform a release once the additional flag is executed.

Method invocation

There are two ways in which JNI can be used in methods, one of which is as follows:

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

The above code first gets an array, then copies the Len byte elements, and finally releases the array. Depending on the implementation, the get call returns either the original data or a copy of the data. In this case, jni_abort can ensure that no third copy is present.

Another implementation is simpler:

    0, len, buffer);

There are several recommendations for this:
-Reducing JNI calls can save overhead.
-No raw data or additional copies of data.
-Reduce the risk of programmer errors – they will forget to invoke the associated release method after certain operations fail.

Similarly, you can use the Setarrayregion function to copy data into an array, and the Getstringregion function or getstringutfregion can copy any length of character from a string.

Abnormal

When an exception occurs, do not continue down execution . The code should take note of these exceptions and return them, or handle the exceptions.

When an exception occurs, only the following JNI methods are allowed to be called:

    • Deleteglobalref
    • Deleteglobalref
    • Deletelocalref
    • Deleteweakglobalref
    • Exceptioncheck
    • Exceptionclear
    • Exceptiondescribe
    • Exceptionoccurred
    • Monitorexit
    • Poplocalframe
    • Pushlocalframe
    • Releasearrayelements
    • Releaseprimitivearraycritical
    • Releasestringchars
    • Releasestringcritical
    • Releasestringutfchars

Many JNI functions throw exceptions, but only a very simple method of checking is provided. For example, if the NewString function returns a non-empty value, you do not need to check for exceptions. However, if you call a method, such as Callobjectmethod, you need to check the exception every time, because if the exception is thrown, the return value is invalid.

It is important to note that exceptions thrown by interrupts do not release the local stack frame, and Android does not currently support C + + exceptions. The throw and thrownew structure of JNI is also just an exception pointer set on the current thread. When an exception occurs, it is returned only to the code call, and the exception is not properly handled.

Local code can catch exceptions through the Exceptioncheck function or the exceptionoccurred function, and they can be cleaned up by the exceptionclear function. Typically, not handling these exceptions can cause some problems to occur.

There is no mapping function corresponding to Throwable in JNI, so if you want to get the exception string, you need to find the Throwable class first and then find the relevant GetMessage "() ljava/lang/string;" Method ID, and then call these methods, if the returned value is non-null, then call the Getstringutfchars function to get the exception string you want, and finally print the exception.

Local library

You can load the local code in the shared library with the standard system.loadlibrary function. The recommended ways to get local code are:

    • System.loadlibrary (), the only parameter of the method is a brief library name, so if you want to load "libfubar.so", you only need to pass "Fubar".
    • Local method: Jint jni_onload (javavm* vm, void* reserved);
    • Inside the Jni_onload method, all local methods are registered. If you declare a method as "static," the method name does not occupy the space of the symbol table.

If the Jni_onload function is implemented by C + +, it should look like this:

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 load the local library with the System.load function plus the fully qualified name of the library.

Using jni_onload Another note is that any findclass call takes place in the context of the ClassLoader, which is used to load the shared library. Typically, the loader used by Findclass is at the top of the interpretation stack, and if there is no loader, it uses the system loader.

64-bit considerations

Android is currently running on a 32-bit platform. Although it is theoretically possible to build a system for a 64-bit platform, it is not a major goal at this time. In most cases, this is not something you need to worry about, but if you want to store the pointer on an int property of an object in your local structure, it's worth paying attention to. To support the 64-bit pointer structure, you need to store the local pointer in a long property .

Unsupported features and backwards compatibility

All JNI1.6 features are supported, along with the following exceptions:
-DefineClass has not yet been implemented. Android does not use Java bytecode and class files, so incoming binary class data is not executed.

If you need to be compatible with older versions of Android, you should check the following sections:

  • Dynamic Query Local functions
    • Before Android 2.0, the character ' $ ' would not be correctly converted to "_00024" when looking for a method. So using the method requires explicit registration or moving the inner class method out.
  • Detach Thread
    • Before Android 2.0, you cannot use the Pthread_key_create destructor to avoid the check that the thread must be detached before exiting.
  • weak global references
    • Before Android 2.2, weak global references were not implemented. Previous versions would refuse to use them. You can use the Android platform version to detect support.
    • Prior to Android 4.0, weak global references could only be passed into Newlocalref, newglobalref, and DELETEWEAKGLOBALREF functions.
    • Starting with Android 4.0, weak global references can be used just like other JNI references.
  • Local References
    • Before Android 4.0, a local reference is actually a pointer. The necessary intermediate roles were added after Android 4.0 to better support the work of the garbage collector, but this means that there are many JNI bugs that are not detectable in the old version. See JNI Local Reference changes in ICS for more information.
  • checking reference types by Getobjectreftype
    • Prior to Android 4.0, Getobjectreftype was not implemented correctly due to the use of direct pointers. We find through weak global tables, parameters, local tables, and global tables. First it will find your direct pointer and return the type of reference it checks. This means that if you function Getobjectreftype on a global jclass, and this jclass is passed to a static local method with an implicit parameter, you will get jnilocalreftype instead of Jniglobalreftype.

Android Official Development Document Training Series Course Chinese version: The JNI correlation of Android

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.