How to Use NDK to Build A shared Android Native Library

This post aims at providing a brief guide on using NDK (Native Development Kit) to build a shared library for an Android application.
Be noted that this post will not teach you how to develop a whole JNI supported Android application, the outcome of the following procedure will only lead you to generate a shared library (*.so file) which you can use for your android application.
And if you want to really use this generated library, you will have to change some function formats according to your project, modification part will be covered of course.

Download Essentials

Android NDK is a must.

Set Up NDK Environment

As we need to use ndk-build command to build the C/C++ code for Android applications running under the ARM architecture. First we need to let the system to know where to find ndk-build.
Open a terminal and type as follows (noted that you need you change the ndk path according to your download and location):

1
2
cd # Guarantee that you are under the "/Home" directory, works for Ubuntu at least, for other systems "cd /Home" may work.
vim .bashrc # Or use command "gedit .bashrc" if you are not a vimer.

Then edit your .bashrc file.

1
2
3
4
5
6
7
export NDK_HOME=/home/yeephycho/Android/android-ndk-r13 # Change this path according to your own system. 
# You can cd to your directory and use command "pwd" to check your exact ndk path.
export PATH=$PATH:$NDK_HOME # This line add NDK to your system environment.
# Save and quit .bashrc file use "Esc + :wq" if you are a vimer.
# Back to your "/Home" directory and type
source .bashrc # To guarantee the change can be effective.
ndk-build # To test whether the setting is correct.

If the setting is wrong, you may see the result “ndk-build: command not found”.
If you get “Android NDK: Could not find application project directory!”, then everything seems work well.

Prepare Code

Now, if you work under a Android project, create a new folder “jni” under your project folder, we are going to place all the native related code under this folder; inside of “jni” folder, create another folder “include”, we are to place all the native (we call C/C++ code under java: native code, get use to it, there’s no why, I also feels weird to this name, but native is indeed easier to pronounce when comparing to C and C plus plus.) header file under this folder.
Create a file named “Android.mk” under “jni” folder, this file will provide necessary information to ndk-build tool, and also create a file named HelloNative.c under “jni” folder, this file will be the C/C++ source code; create “HelloNative.h” file under “/jni/include/“ folder, this include file is the interface header of Java and C/C++.
Source tree structure will be something like:

1
2
3
4
5
6
7
8
Android_Project_Directory:
...
jni:
include:
HelloNative.h
Android.mk
HelloNative.c
...

“…” represents other Android related files or folders. It’s ok if you are not working under a real Android project, that means “…” part can be NULL.

Prepare HelloNative.h file:

1
2
3
4
5
6
7
JNIEXPORT jstring JNICALL Java_io_github_yeephycho_hellojni_JNIActiviy_getMessage(JNIEnv *, jobject);
/* The syntax of JNI function header is as follows: */
/* JNICALL Java_packageName_className_methodName(JNIEnv *, jobject); */
/* Replace all the "." in the above names with "_"; */
/* JNICALL Java_$(modify_here)(JNIEnv *, jobject, $(arguments)); */
/* Modify the $(modify_here) field according to the above format; */
/* Modify $(arguments) field according to your function input arguments. Google how to pass arguments to JNI by your self, it's not intuitive. */

Prepare HelloNative.c file:

1
2
3
4
5
6
#include <jni.h> /* This header file is a must for JNI source. Just include it, JDK and compiler will handle it.*/
#include "include/HelloNative.h" /* Include our interface header file. */

JNIEXPORT jstring JNICALL Java_io_github_yeephycho_hellojni_JNIActiviy_getMessage(JNIEnv *, jobject){
return (*env)->NewStringUTF(env, "Bello?! This message comes from JNI!");
};

Prepare Android.mk file:

1
2
3
4
5
6
7
8
9
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := hellojni
LOCAL_SRC_FILES := HelloNative.c

include $(BUILD_SHARED_LIBRARY)
# You can find more information about Android.mk at https://developer.android.com/ndk/guides/android_mk.html.

Now, we have all the necessary source for ndk to build.

Commands

Under the working directory:

1
2
ndk-build	
# ndk-build command can have many parameters, you can get more information at https://developer.android.com/ndk/guides/ndk-build.html.

Output

After you typed the command, “obj” folder and “libs” folder will be generated under your work directory.
Inside of the “libs” folder, there will be a few sub-folders corresponding to different hardware architectures, for example “arm64-v8a”, “armeabi-v7a”, “x86”, “x86_64” or “mips”.
Each of the above sub-folder will contain a file named “libhellojni.so”. This is the target dynamic library we want.

How to use the library

Add Application.mk for an Android application.
Android.mk tells the ndk compiler how to build a specific native module (shared library), application.mk tells the compiler which module to use for your application and some characters of the modules.
Edit Application.mk under jni folder:

1
2
3
4
APP_OPTIM := debug   # debug mode
APP_CFLAGS := -g # compiler instruction
APP_ABI := armeabi-v7a # build for armeabi-v7a architecture
# More information about Application.mk can be found at: https://developer.android.com/ndk/guides/application_mk.html

After add this file, only armeabi-v7a version library will be generated.

And also you need a Android java activity, edit file JNIActivity.java file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.yeephycho.androidhellojni;

import android.os.Bundle;
import android.app.Activity;
import android.widget.TextView;

public class JNIActivity extends Activity{
static {
System.loadLibrary("hellojni"); // Load shared library here.
}

public native String getMessage(); // Declare your native method here, the format is public native return_type methodName();

public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// Create a TextView
TextView textView = new TextView(this);
// Retrieve the text from native method getMessage()
textView.setText(getMessage());
setContentView(textView);
}
}

Then generate .apk file by any way you like.

Reference

This post mainly takes here as reference.

I Love This World!

George Winston - Autumn


Cheers!


License


The content of this blog itself is licensed under the Creative Commons Attribution 4.0 International License.
CC-BY-SA LICENCES

The containing source code (if applicable) and the source code used to format and display that content is licensed under the Apache License 2.0.
Copyright [2016] [yeephycho]
Licensed under the Apache License, Version 2.0 (the “License”);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Apache License 2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language
governing permissions and limitations under the License.
APACHE LICENCES