Create an example CMake based Android Studio NDK shared library and application.
Created on: 2017-03-08
IDE: Android Studio 2.3
OS: LinuxMint 17.2
This project assumes a basic understanding of C++, CMake, Java, Android and Android Studio.
Where not otherwise noted, Android Studio defaults were chosen.
Where noted, use the <Sync Checkpoint> <Build Checkpoint> and <Run Checkpoint>'s to confirm your progress.
Select menu: File | New | New Project...
Configure your new project
Application name: My NDK Lib App
Company Domain: mycompany.example.com
Include C++ Support: Check On
Add an Activity to Mobile
Basic Activity
Customize the Activity
Use a Fragment: Check On
Customize C++ Support
C++ Standard: C++11
Exceptions Support (-fexceptions): Check On
<Build Checkpoint>
Select menu: Build | Make Project
Note for Android Studio versions older than 2.3:
You may see the following error: "cannot find symbol variable sample_text"
If you do, fix the error by modifying the file "MyNDKLibApp/app/src/main/res/layout/fragment_main.xml" as follows:
android:id="@+id/sample_text"
to the TextView
<TextView
android:id="@+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Run Checkpoint>
Select menu: File | New | New Module...
New Module
Select: Android Library
Android Library
Application/Library name: MyNDKLib
Module name: MyNDKModule
<Run Checkpoint>
Create the following folders:
MyNDKLibApp/MyNDKModule/src/main/cpp
MyNDKLibApp/MyNDKModule/src/main/cpp/MyCppLib
MyNDKLibApp/MyNDKModule/src/main/cpp/MyCppLib/src
Create:
// file: MyNDKLibApp/MyNDKModule/src/main/cpp/MyCppLib/src/MyCppClass.h
#ifndef MYCPPLIB_MYCPPCLASS_H
#define MYCPPLIB_MYCPPCLASS_H
class MyCppClass
{
public:
static int fourtyTwo();
};
#endif // MYCPPLIB_MYCPPCLASS_H
Create:
// file: MyNDKLibApp/MyNDKModule/src/main/cpp/MyCppLib/src/MyCppClass.cpp
#include "MyCppClass.h"
#if defined(__GNUC__) && __GNUC__ >= 4
# define MYCPPLIB_EXPORT __attribute__((visibility("default")))
#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)
# define MYCPPLIB_EXPORT __attribute__((visibility("default")))
#else
# define MYCPPLIB_EXPORT
#endif
MYCPPLIB_EXPORT int MyCppClass::fourtyTwo()
{
const auto result = 42;
return result;
}
Note the file MyCppClass.cpp is not yet part of the project.
<Run Checkpoint>
Create:
// File: MyNDKLibApp/app/src/main/java/com/example/mycompany/myndklibapp/MyNativeClass.java
package com.example.mycompany.myndklibapp;
public class MyNativeClass {
public native static int fourtyTwo();
}
Note the corresponding JNI method for native method "fourtyTwo()" is not yet declared or implemented.
<Run Checkpoint>
If not already configured, Add javah to Android Studio external tools. This is a one off step.
The javah tool will be used to generate a JNI header for native Java method MyNativeClass.fourtyTwo().
Select menu: File | Settings... | Tools | External Tools | [+]
Create Tool
Name: javah
Description: javah
Show console when a message is printed to standard output stream: Check On
Show console when a message is printed to standard error stream: Check On
Program:
Parameters: -classpath "$Classpath$" -v -jni
Working Directory:
Create folder: MyNDKLibApp/app/src/main/jni by Right-clicking the "app" module in the Project view.
Select: New | Folder | JNI Folder
Configure Component
Choose default values and press Finish.
Build the project. This will generate the class file required by the javah tool.
<Build Checkpoint>
Failure to successfully build before using the javah tool will generate the following message:
Error: Could not find class file for 'com.example.mycompany.myndklibapp.MyNativeClass'.
Right-click: MyNDKLibApp/app/src/main/java/com/example/mycompany/myndklibapp/MyNativeClass.java
Select menu: NDK | javah
Verify the machine generated file was created:
MyNDKLibApp/app/src/main/jni/com_example_mycompany_myndklibapp_MyNativeClass.h
Create:
// file: MyNDKLibApp/app/src/main/jni/com_example_mycompany_myndklibapp_MyNativeClass.cpp
#include "com_example_mycompany_myndklibapp_MyNativeClass.h"
#include "../../../../MyNDKModule/src/main/cpp/MyCppLib/src/MyCppClass.h"
#ifdef __cplusplus
extern "C"
{
#endif
JNIEXPORT jint JNICALL Java_com_example_mycompany_myndklibapp_MyNativeClass_fourtyTwo
(JNIEnv *, jclass)
{
const auto result = MyCppClass::fourtyTwo(); // Call native method.
return result;
}
#ifdef __cplusplus
}
#endif
Note the implementation is not yet part of the project.
<Run Checkpoint>
Read the general overview on how to include native code in a project using CMake:
Create a CMake build script
Link Gradle to your native library
Using the embeded tags
### <Add>
and
### </Add>
modify the file:
# File: MyNDKLibApp/app/CMakeLists.txt
# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.
cmake_minimum_required(VERSION 3.4.1)
### <Add>
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../distribution)
### </Add>
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
# Associated headers in the same location as their source
# file are automatically included.
### <Add>
src/main/jni/com_example_mycompany_myndklibapp_MyNativeClass.cpp
### </Add>
src/main/cpp/native-lib.cpp )
### <Add>
add_library(lib_mycpplib SHARED IMPORTED)
set_target_properties(lib_mycpplib PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/MyCppLib/lib/${ANDROID_ABI}/libMyCppLib.so)
### </Add>
# Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
### <Add>
# libMyCppLib.so
lib_mycpplib
### </Add>
# Links the target library to the log library
# included in the NDK.
${log-lib} )
<Sync Checkpoint>
<Build Checkpoint>
Note the error: '../../../../../../../../distribution/MyCppLib/lib/mips64/libMyCppLib.so', needed by '../../../../build/intermediates/cmake/debug/obj/mips64/libnative-lib.so', missing and no known rule to make it
This error is expected and indicates the project has not yet been configured to build the library.
Create:
# file: MyNDKLibApp/MyNDKModule/src/main/cpp/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(lib_build_DIR $ENV{HOME}/tmp)
file(MAKE_DIRECTORY ${lib_build_DIR})
add_subdirectory(${lib_src_DIR}/MyCppLib ${lib_build_DIR}/MyCppLib)
Create:
# file: MyNDKLibApp/MyNDKModule/src/main/cpp/MyCppLib/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
add_library(MyCppLib SHARED src/MyCppClass.cpp)
# copy out the lib binary and remove generated files
set(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../distribution)
set_target_properties(MyCppLib
PROPERTIES
LIBRARY_OUTPUT_DIRECTORY
"${distribution_DIR}/MyCppLib/lib/${ANDROID_ABI}")
add_custom_command(TARGET MyCppLib POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E
copy "${CMAKE_CURRENT_SOURCE_DIR}/src/MyCppClass.h"
"${distribution_DIR}/MyCppLib/include/MyCppClass.h"
COMMENT "Copying MyCppLib to output directory")
Right-click the "MyNDKModule" module in the Android view and select menu option "Link C++ Project with Gradle".
Link C++ Project with Gradle
Build System: CMake
Project Path: FULL_PROJECT_PATH/MyNDKLibApp/MyNDKModule/src/main/cpp/CMakeLists.txt
You can select the full path using the [...] button.
Verify the dialog box shows:
Path to be saved into the build.gradle file: "src/main/cpp/CMakeLists.txt"
Confirm there are 3 CMakeLists.txt files listed in the "External Build Files" of the Android view.
Using the embeded tags
// <Add>
and
// </Add>
modify the file:
// MyNDKLibApp/app/build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "com.example.mycompany.myndklibapp"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -fexceptions"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// <Add>
sourceSets {
main {
// Package the shared JNI library into apk
jniLibs.srcDirs = ['../distribution/MyCppLib/lib']
}
}
// </Add>
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.1'
compile 'com.android.support:design:25.1.1'
testCompile 'junit:junit:4.12'
// <Add>
compile project(':MyNDKModule')
// </Add>
}
<Sync Checkpoint>
Using the embeded tags
// <Add>
and
// </Add>
modify the file:
// MyNDKLibApp/MyNDKModule/build.gradle
apply plugin: 'com.android.library'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// <Add>
externalNativeBuild {
cmake {
arguments '-DANDROID_PLATFORM=android-9',
'-DANDROID_TOOLCHAIN=clang'
// explicitly build library
targets 'MyCppLib'
}
}
// </Add>
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.1'
testCompile 'junit:junit:4.12'
}
<Sync Checkpoint>
<Run Checkpoint>
Modify:
// MyNDKLibApp/app/src/main/java/com/example/mycompany/myndklibapp/MainActivity.java
//
// ...
//
// protected void onCreate(Bundle savedInstanceState) {
//
// ...
//
// *** Change the following:
// Example of a call to a native method
// TextView tv = (TextView) findViewById(R.id.sample_text);
// tv.setText(stringFromJNI());
//
// *** To the following:
//
// Example of calls to native methods
TextView tv = (TextView) findViewById(R.id.sample_text);
final int fourtyTwo = MyNativeClass.fourtyTwo();
if (fourtyTwo == 42) {
tv.setText(stringFromJNI());
}
<Run Checkpoint>
The resulting application should display the same output if and only if native MyCppClass.fourtyTwo() is successfully built, packaged and executed.
Read: Java Native Interface Specification
Static Library Example: android-ndk/hello-libs (see: gmath)