Here we describe how to develop a simple Android application which is comprised of Java but also C++ and/or C code.
After facing to many obstacles when trying to modify an already existing application in order for it to support native code, the best workaround was simply to browse for tutorials on how to develop NDK compliant applications.
The following instructions assume you already installed NDK acording to the steps shown here.
If you run into some problems with the ActionBarActivity and other related things go to
File → Project Structure → Dependencies → + → Library dependency, choose com.android.support:appcompat-v7:19.0.0, and make sure you set the Scope to Compile.
If you can't find this version, click on the one you find, press Apply and then Ok, and after that open your project's build.gradle file and change the version on the dependencies definition:
dependencies { // Support Libraries compile 'com.android.support:appcompat-v7:19.0.0' }
Sync Project with gradle files and everything should be fine now.
public native int foo();
This step is important because as we will see, all our classes must be up-to-date before generating the JNI file.
Inside the defaultConfig definition add:
ndk { moduleName "trubilib" }
Sync Project with gradle files and everything should be fine now.
static { System.loadLibrary("trubilib"); }
Go to your project's local.properties file and add:
ndk.dir=~/Android/ndk
You can either fire a terminal and do
$ cd <project root directory>/src/main
Or you can open Android Studio's terminal and do
$ cd src/main
Then, theoretically you would do
$ javah -d jni -classpath ~/Android/Sdk/platforms/android-19/android.jar:../../build/intermediates/classes/debug <your app package>.<your activity>
Personally I ran into some problems with the appcompat jars and libraries and so I had to do
$ javah -d jni -classpath ~/Android/Sdk/platforms/android-19/android.jar:~/Android/Sdk/extras/android/support/v7/appcompat/libs/android-support-v7-appcompat.jar:~/Android/Sdk/extras/android/support/v7/appcompat/libs/android-support-v4.jar:../../build/intermediates/classes/debug <your app package>.<your activity>
If everything went right you must already be able to see in your project a new directory called jni right next to the java directory where your activity class stands.
Inside that directory there's a new file with a funny looking name.
This is a header file whose name is basically <your app package>_<your activity>, where every dot of that classe's full name is replaced with an underscore.
In that directory open that header file and copy all of its content.
Create a new file in that same directory called, for example, trubi.c and paste the header file 's content.
Remove the jni.h include tag and add that header to a new include tag.
Remove everything else in the file but the method's header.
Transform that header into a function that returns a 0.
In the end, your trubi.c file must look something like this:
#include "you_app_package_name_YourActivity.h" JNIEXPORT jint JNICALL Java_you_app_package_name_YourActivity_foo (JNIEnv *env, jobject obj) { return 0; }
The lazy way is just to add a print that uses your method and see the output on logcat:
System.err.println(foo());
Alternatively you can create a textview or something of sorts and display the result of your method in your activity.
Launch your emulator.
Build and run your project to see your native method's result.
On top of all these instructions there are other configurations that can help our native application development.
The instructions given above were minimalistic, i.e., they were made considering that gradle would automatically build the apk file containing our application and the native code as a library.
When we added the configuration
ndk { moduleName "trubilib" }
We essentially told gradle that there would be a module called “trubilib” that gradle should compile and append the resulting .so file to our app's apk file.
At the time of building and running the app, gradle would then look for the jni folder (typically in <projec root directory>/src/main/jni), and compile everything in it.
When performing this step, gradle essentially reads the build.gradle file and creates its own Makefile (which can be found, once the app is built, on <project root directory>/build/intermidiates/ndk/debug/lib/Android.mk) and runs it.
However, this leads to poor flexibility.
In this page's Useful Links section you can find videos explaining how to create your own Makefiles, and to make sure they are actually used (because gradle will ignore any other Makefile once you declare your module in the build.gradle file).
When creating big native projects, having our own custom Makefiles for the different parts of the project has its obvious advantages.
Since we only intend to create small native apps to test the results of restricting access to the OS's resources we are only going to target two small but important features we could be interested in setting up in our project.
We explain how to do this with the standard build.gradle file which in turn creates its own Makefile where all the magic happens.
If you are interested in knowing how to set up these two features with custom Makefiles you can check how to here, although I think this example requires you to run the ndk-build command each time you want to run, which is a pain.
Alternatively you can check this example as I believe it explains how to circunvent that ndk-build command issue.
When building your app, gradle automatically compiles your “library” to all available architectures.
Personally I found some problems when trying to run the gdbserver with the various architectures in the app so I tried to see how to create just the architecture I trully need.
In short the only thing you need to do is to edit your build.gradle file, more specifically by creating three new specifications bellow or inside the defaultConfig specification, like this:
flavorDimensions "abi" productFlavors { x86 { flavorDimension "abi" ndk { abiFilter "x86" } versionCode = 3 } } applicationVariants.all { variant -> def abiVersion = variant.productFlavors.get(0).versionCode variant.mergedFlavor.versionCode = abiVersion * 1000000 + defaultConfig.versionCode }
Other important information on this can be found here.
Note: In my case, because I specified only one architecture, in order for me to change my header file in case I wanted to add, edit or remove native methods, the path of the javah classpath changed.
If you decide to specify only one architecture you should follow step 6) and replace the javah command with:
$ javah -d jni -classpath ~/Android/Sdk/platforms/android-19/android.jar:../../build/intermediates/classes/<your achitecture>/debug <your app package>.<your activity>
If you run into the same problem I did, try this:
$ javah -d jni -classpath ~/Android/Sdk/platforms/android-19/android.jar:~/Android/Sdk/extras/android/support/v7/appcompat/libs/android-support-v7-appcompat.jar:~/Android/Sdk/extras/android/support/v7/appcompat/libs/android-support-v4.jar:../../build/intermediates/classes/<your achitecture>/debug <your app package>.<your activity>
<your achitecture> in the case above would be x86.
Another interesting feature we may be interested in, considering, on one hand, that Android Studio doesn't have the debug capabilities of Eclipse, and on the other hand, that gdbserver is a little too complicated to use, is to redirect our native code prints to logcat.
In order to do that, you essentially need to modify your ndk configuration in your build.gradle file, like this:
ndk { moduleName "trubilib" ldLibs "log" }
Then, in our code you can print directly to logcat like this:
#include <string.h> #include <android/log.h> #include "you_app_package_name_YourActivity.h" #define APPNAME "YourAppName" #define printToLogcat(...) __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, __VA_ARGS__) JNIEXPORT jint JNICALL Java_you_app_package_name_YourActivity_foo (JNIEnv *env, jobject obj) { printToLogcat("I'm the foo native method and I'm returning %d, to Java\n", 0); return 0; }
There are other ways for you to redirect your prints to logcat.
More information on that can be found in the Debug page.
Another way to test native code, in order to take advantage of other types of mechanisms (e.g., forking processes), is to build native executables containing main functions.
This way, the fork() call will fork other processes of that executable and not other application processes.
https://rathodpratik.wordpress.com/2013/03/24/build-cc-executables-for-android-using-ndk/
http://stackoverflow.com/questions/6998419/package-android-apk-with-additional-executables
http://stackoverflow.com/questions/18302603/where-to-place-assets-folder-in-android-studio
https://github.com/gimite/android-native-exe-demo/blob/master/src/net/gimite/nativeexe/MainActivity.java
http://stackoverflow.com/questions/10001795/conventional-way-of-copying-files-in-gradle-use-copy-task-or-copy-method
https://gist.github.com/tylerchesley/6198074
http://stackoverflow.com/questions/5486623/android-creating-a-folder-in-the-data-data-pkg-files-directory
http://giderosmobile.com/forum/discussion/5305/plugin-with-android-studio/p1