—————————- Here is the right stuff you may need ——————————–
This
post shows how to develop an Android NDK application with OpenCV
included using Android Studio and Gradle. If you’re working on migrating
your original Eclipse Project to Android Studio, you may find this post
is what exactly you want!
OK,Let’s start!
Section 1: Three things you must know
1.Firstly,
if you are not familiar with Android Studio and Gradle, you may find
these links useful. (if you already know these well, skip this part)
2.Secondly, if your android ndk project is not that complicated(for example, having no opencv included), you may wanna see ph0b ‘s introduction here, it’s quite a nice job with a video recorded! (you
can also follow Section 2 in this post to get a simple Android NDK demo
application)
3.Create a new directory jni in folder app/src/main, then you have java, jni and res in this folder.
4.This step is very important! You can add a external tool to run the javah command without typing that much code!
Open AS’s Preferences, then find External Tools in IDE Settings, click + to add one tool with the following configurations. (Make sure you have add JDK tools in your system path, if you don’t know how, click here)
With the help of this tool, each time we right click on a class file, then choose Android Tools -> javah to run this tool, it will automatically generate a C head file for us in the target folder $ModuleFileDir$/src/main/jni , in this case, it is app/src/main/jni. Try this on MyActivity.java file now! The console will print out a log like:
Then you get a com_android_hacks_ndkdemo_MyActivity.h file in jni folder with the following content.
123456789101112131415161718192021
/* DO NOT EDIT THIS FILE - it is machine generated */#include<jni.h>/* Header for class com_android_hacks_ndkdemo_MyActivity */#ifndef_Included_com_android_hacks_ndkdemo_MyActivity#define_Included_com_android_hacks_ndkdemo_MyActivity#ifdef__cplusplusextern"C"{#endif/* * Class: com_android_hacks_ndkdemo_MyActivity * Method: hello * Signature: ()Ljava/lang/String; */JNIEXPORTjstringJNICALLJava_com_android_hacks_ndkdemo_MyActivity_hello(JNIEnv*,jobject);#ifdef__cplusplus}#endif#endif
5.Write a simple C implementation file named main.c in jni folder
1234567
#include<jni.h>#include"com_android_hacks_ndkdemo_MyActivity.h"JNIEXPORTjstringJNICALLJava_com_android_hacks_ndkdemo_MyActivity_hello(JNIEnv*env,jobjectobj){return(*env)->NewStringUTF(env,"Hello from JNI");}
6.In the build.gradle file under app module, add the following codes to configure ndk in defaultConfig element, here we just give the uni module a name hello, you can find other configurations in
7.In order to let Gradle run ndk-build command (in some task, maybe NdkCompile task), we should configure the ndk.dir in local.properties file in Project root.
8.OK, everything is ready, click Run to give it a try, you will see the result like
All right, so what’s happening inside?
Since you have a jni folder, Gradle will consider it as a default native code folder. When Gradle builds the app, it will run ndk-build command(since you have configured ndk.dir, Gradle knows where to find it) with a generated Android.mk file(locates in app/build/intermediates/ndk/debug/Android.mk), after compiling the native codes, it will generate the libs and obj folder into folder app/build/intermediates/ndk/debug/. Gradle will then package the libs into final apk file in folder app/build/outputs/apk/app-debug.apk(you can unarchive this file to check whether libs is contained)
app/build/intermediates/ndk/debug (lib and obj folders)
app/build/outputs/apk/app-debug.apk (and files within it)
Secontion 3: Using OpenCV
If your project do not use OpenCV, then the section 2 is just enough.
But what if you wanna use OpenCV to do other stuff? Of course, we want
to use OpenCV for Android instead of JavaCV here, and Of course, we need to package OpenCV library for Android into
our application’s APK file (then users who use this app does not have to
install OpenCV Manager). So, how can we achieve these goals?
The simplest way has been posted by TGMCians on Stack Overflow
Thanks to Gaku Ueda, he had made a great job explaining how to achieve that goal with this post. The core idea of his method is to run ndk-build command in some task, then zip the <abi>/*.so files under the output app/build/libs/ folder into a jar file which is finally put in app/build/libs/ folder, then add a compile dependency to this jar file. The key code for his method listed below
Notice 1: When using custom Android.mk, we should first disable Gradle to build the jni folder as before, and sourceSets.main.jni.srcDirs = [] just does this job!
Notice 2: The code is not exactly the same with Gaku Ueda’s code: tasks.withType(Compile) to tasks.withType(JavaCompile), because Compile is deprecated.
Notice 3: You can get $ndkDir variable with project.plugins.findPlugin('com.android.application').getNdkFolder() or you can define it in grade.properties file under Project root, so you need to add ndkDir=path/to/your/ndk in that file, if the file is not created, simply create a new one.
android{...sourceSets.main.jni.srcDirs=[]taskndkBuild(type:Exec,description:'CompileJNIsourceviaNDK'){ndkDir=project.plugins.findPlugin('com.android.application').getNdkFolder()//on Windows, you need to add ".cmd" after "ndk-build" belowcommandLine"$ndkDir/ndk-build",'NDK_PROJECT_PATH=build','APP_BUILD_SCRIPT=src/main/jni/Android.mk','NDK_APPLICATION_MK=src/main/jni/Application.mk'}taskndkLibsToJar(type:Zip,dependsOn:'ndkBuild',description:'CreateaJARofthenativelibs'){destinationDirnewFile(buildDir,'libs')baseName'ndk-libs'extension'jar'from(newFile(buildDir,'libs')){include'**/*.so'}into'lib/'}tasks.withType(JavaCompile){compileTask->compileTask.dependsOnndkLibsToJar}...}dependencies{compilefileTree(dir:'libs',include:['*.jar'])// add begincompilefileTree(dir:newFile(buildDir,'libs'),include:'*.jar')// add end}
But we can still do a little improvements here. We have already know that Gradle will take jniLibs folder as its default native libraries folder, so we can simply output the libs/<abi>/*.so files generated by ndk-build command into jniLibs folder, so there’s no need to zip these *.so files into a jar file.
So simple, right? 'NDK_LIBS_OUT=src/main/jniLibs' helps us do the right job!
For testing, you can also add some lines relating with OpenCV in your Android.mk file and some line in your main.c to check whether everything is readlly working. For example, add #include <opencv2/core/core.hpp> in main.c file, and change Android.mk to
In Gradle Console window, you can see these similar lines
*.so files relating with OpenCV has been packaged into the final APK
One More Thing
Of course, maybe you don’t want to change your build.grale file with that much code, and Of course, you also don’t want to run ndk-build outside the IDE, then copy the <abi>/*.so files into jniLibs folder each time you want to rebuild the native codes!
At last, I came out another nicer solution, if you like, that is to create a ndk-build external tool in Android Studio, and every time you want to rebuild the
native codes, simply run the external tool, then it automatically
generates the libs/<abi>/*.so files into jniLibs folder, so everything is ready to run this app, :-)