Mentioning JNI lots of programmers subconsciously experience some unexplainable fear. JNI looks suspiciously difficult, and at first glance its mechanism resembles magic. However, those who looked closer into it highly appreciate its properties.

If you haven’t heard about this technology, Java Native Interface or JNI is a standard Java mechanism that allows Java code to interact with C and C++ code. Wikipedia says “JNI enables programmers to write native methods to handle situations when an application cannot be written entirely in the Java programming language, e.g. when the standard Java class library does not support the platform-specific features or program library” which means that in an Android application we can use a C++ library we need and interact with it directly from Java code and vice-versa.

Sounds great, ha?

So, here are three reasons why we love JNI:

  1. JNI makes some processes that aren’t implemented in Java possible. Like hardware-sensitive or direct OS API commands, for example. For Android developers, it opens a lot of opportunities outside Dalvik. Compiled C/C++ code will work on any Java device because JNI is independent from Dalvik as it was designed for JVM.
  2. Ability to increase app performance with the help of low-level libraries for things like graphics, calculations, different types of rendering, etc.
  3. Huge number of libraries has already been written for all the different tasks. And being able to reuse the code without rewriting it in other language makes a developer’s life much easier. This way such popular libraries as FFmpeg and OpenCV become available for Android developers.

But let’s look closer at this technology. As Linus Torvalds said “Talk is cheap. Show me the code.”

The interaction scheme looks like this:

J N I - Java native and C++ interaction using JNI

To call a C++ method from Java, you need to:

  1. Create a method in a Java class
  2. Create a header and a cpp file in a jni folder. It will contain C++ code called by a native function mentioned above.
  3. In the header we define its signature like this:

    • extern “C” is required to keep C++ compiler from changing the names of declared functions;
    • JNIEXPORT is a necessary modifier for JNI;
    • Data types with “j” prefix: jdouble, jobject, jstring, etc – reflect Java objects and types in C/C++;
    • JNIEnv* is an interface for Java. It allows calling Java methods, creating Java objects and doing other most useful Java stuff;
    • The second crucial parameter is jobject or jclass, regarding if the method is static. If it is, the argument is going to be jclass(a link to a class in which the code is declared) and if static, it will be jobject(a link to an object in which the method was called).
      Actually, you don’t have to write the code manually. You can use a javah utility but I found it easier and clearer to do it by myself. The function itself is realized in a .cpp file;
  4. Return to Java where we defined the native function and add:

    in the very beginning of the file, above the declaration of the native method. A library name is kept in Android.mk.
  5. I’d also like to pay your attention to the files like Android.mk and Application.mk. In Android.mk we store the names of all the .cpp files from jni folder that we are going to compile, any specific flags and also paths to headers and additional libraries, in other words, some linking parameters and settings and other things needed to assemble a library.
    In Android.mk we keep additional assembling parameters like required platform version, architecture type etc.

Let me sum it all up. Calling C++ from Java:

  • We create a function with a native modifier and call it from any Java method;
  • Java compiler generates the bytecode;
  • C/C++ compiler creates a dynamic library .so;
  • When we run the app, Java device starts to process the bytecode;
  • When it meets loadLibrary call, it adds a .so file to the process;
  • When it meets the call of the native method, it searches a method in opened .so files by its signature;
  • If the method is present, it will be called. If not, the app crashes.

But what if we need to do the opposite? (to call a Java method from C/C++ code)

Sometimes we need to call a method from a Java native. Like when there is a long-lasting operation in the native and we have to track its progress. It is called a callback.

The logic of a callback:

Java code calls a C++ method ->
When processed, the method calls its SDK and sends the information it needs. To send this info to the app, you have to do the following:

  1. In the jni folder, you create a class AndroidGUIHandler which extends IGUIHandler. Its methods receive parameters from SDK (wstring and others), convert it into a Java-compatible format and call a Java method sending those parameters.
  2. (AndroidGUIHandler class methods won’t have any signature and will look just like C++ methods).
  3. Before you call Java methods, first you need to connect to a Java thread with the help of a wrapping class. Then, you need to use another wrapping class for callbacks. In that class, using jni methods GetObjectClass, GetMethodID, you search for Java methods that you need to call(reflection mechanism is used during class and method search). And then you call standard jni methods like CallIntMethod, CallVoidMethod to call previously found methods from Java and to send them all the required information from SDK.

And that is the basis of the given technology. I’d say that if you don’t love JNI, you probably don’t know it enough. But, as always, there are some flaws in it:

  • JNI doesn’t catch exceptions like NullPointerException or IllegalArgumentException because of lowered performance and because the majority of functions in C libraries can’t handle that kind of problems;
  • BUT: JNI allows using Java Exception. So we can almost negate this flaw by processing the JNI code manually and by checking the error codes and then throwing exceptions into Java;
  • The difficulty of working with JNI from native threads. To simplify the interaction, you have to write a wrapping class that’s going to do all the required manipulations;
  • Increase in an apk file size;
  • ”Expensive” transition from Java code to native and back;
  • Debugging a C++ code is a problem by itself;
  • In some cases, working with JNI could be MUCH slower than with a Java analog;
  • But the main disadvantage of JNI is that any native code firmly ties your Java app to a particular platform. And using it ruins a conception of Write Once or Run Anywhere. And that’s what you’ll have to deal with.

And, regardless of all its flaws (nobody’s perfect), this technology is loved and appreciated.

 

See how we developed a mobile browser with advanced Omnibox using Java and C++

Please enter your business email
Section image