Shared C++ layer with Android using JNI – Tutorial – Part 2

Uncategorized

If you missed the beginning of this, and landed on this page somehow, and something is not clear, read this link first – with pictures too!
http://codingsimplicity.com/shared-c-layer-with-objective-c-and-java-using-jni-tutorial-part1


First thing is first. You need to make sure you have Android NDK installed and available in PATH.

Modify your .bash_profile or .profile to export PATH and setup ANDROID_NDK variable. Something like this:

And then in a new terminal window try executing command line:

These errors are good. This means NDK is configured correctly, you just did not provide any project to build.
Now let’s try executing the same command from jni subfolder which contains all of our C++ stuff. The jni folder is located app/src/main/

Yeah! We compiled a shared library for x86 architecture. The folder structure is self contained. You can move this jni folder anywhere outside the project and it will compile just fine.
The output of this compilation is ../libs which is the default folder that gets packaged into an Android APK. The debug version of this command is ndk-build NDK_DEBUG=1

Inside the jni folder you will see two configuration files which are relevant to jni

Application.mk specifies various C++ options such as architecture, stl, optimization, exceptions, etc. The dev version of this can just contain the x86 architecture. But when you are ready to release, you should have the 3 prominent architectures specified. This is to spend time compiling while development. You only need one architecture to compile and test on.

Android.mk specifies what to compile and what libraries to link with.
LOCAL_C_INCLUDES are the include files, like where to find headers.
LOCAL_SRC_FILES are the source files, separated by spaces.
Again, in this particular example everything is included inside this jni folder, but you may not necessarily have it set up this way.
LOCAL_MODULE is the name of output binary.

will output libDataStore.so
And inside Java this shared library will get loaded like this:

 

If you look at sources specified in LOCAL_SRC_FILES

one of the files is sqlite3.c which is the sqlite3 database itself in an amalgamated form. Like I mentioned earlier, we are not using any of Android’s libraries for database management. We are using our own copy of sqlite3. This is a good thing because we have much more control around database access and optimizing queries, and beyond. In future tutorials you will see some of the features around the sqlite3 database which are not even possible with the java API included with Android SDK.

The wrapper files are from the SQLiteCpp project mentioned earlier in an iOS part of this tutorial.
The DataStoreImpl.cpp is the implementation file mentioned earlier, the very file we have our findWords implementation in.
The DataStoteJava.cpp is new. It is the JNI wrapper on top of our C++ implementation.

Needless to say, the JNI wrapper is the hardest part of this project. It’s an error prone process, the debugging abilities are limited at best, and you have to get it just right or else you are risking memory leaks or rouge crashes. But if you stick to proven coding constructs and simply do “more of the same” type of coding inside the JNI wrapper the risks are low. The “pass an empty collection and fill it inside the data layer” is a very simple construct which covers a lot of data related cases. It has no memory leaks. If you are a JNI beginner try sticking to such setup.

In this project, the JNI wrapper was hand written, but there are tools out there that generate this layer for you. More on this later.

The JNI Business – a quick overview

You start JNI by defining a set of classes in Java. In our simple Android app there are two classes that fly around the JNI boundaries.

Now pay attention to the fields inside the WordItem class

These fields, along with the package name, will need to be recreated from the inside JNI wrapper class.

Next is the interface class between the Java and the native code. In our app called DataStore.

One of the methods has native specifier. In this cases it tells the Java that the implementation of this method is outside Java in a native library.
In order to implement this method we need to know it’s EXACT signature in C/C++. This is how Java (or in our case dalvik) runtime will know which native method to call at runtime.

The generation of this signature is done by a tool called javah , which is included in Java SDK. Make sure the Java is in your PATH if you intend to use this tool to generate additional methods in this interface class. Type javah in a new terminal window and make sure you get back the usage text printed.

Let’s generate a C signature based on

or perhaps a new method in DataStore class.

Navigate to java folder in terminal and type javah -v kolyakov.com.play1c.DataStore

you should see a file kolyakov_com_play1c_DataStore.h in the same app/src/main/java folder

Open this file and examine the signature which will look like this:

We only need this signature pasted in the implementation file, which is DataStoreImpl.cpp.
We don’t need the actual header file, it can just be deleted.

Implementing the findWords JNI wrapper

The code is tedious, but simple. The  JNI_POSREC structure maps to WordItem java class plus class object and the constructor. The fields are initialized by their corresponding GetFieldID calls. When setting this up manually be very careful with parameters to GetFieldID call, everything is case sensitive and corresponding types must match exactly. No field should be left uninitialized.

The findWordsNative routine creates a new array object by calling NewObjectArray and then simply iterates though values in DataStoreImpl::WordStruct set and adds a newly created
jobject jPosRec = env->NewObject(jniPosRec->cls, jniPosRec->ctorID)
env->SetObjectField(jPosRec, jniPosRec->locationID, jLoc);
env->DeleteLocalRef(jPosRec);

It’s important to properly balance the DeleteLocalRef calls or else there will be crashes. Again, the code is simple, but it needs to be handled with care.

That’s about it for the JNI intro. You’ve survived!


 

Gradle

The JNI compilation is hooked into app’s gradle script.

As you can see the path to ndk-build is hardcoded. Change it to your own path or figure out how to use ndk.dir value in local.properties file. I had trouble getting it to work, but you may have better luck.

 


Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in /home/codingsi/public_html/wp-includes/cache.php on line 679