Android Studio project using Conan and C++ Boost libraries
In the previous blog post Cross building Boost C++ libraries to Android with Conan we cross built the Boost C++ libraries to Android and explained how to upload them to a conan server. That way, developers in our organization can reuse them without having to build from sources again the same binaries.
In this post we will describe how to use these prebuilt Boost libraries in an Android Studio project.
Note: This blog post assume that you followed the previous blog post and you have:
- Profile created for target armveabi-v7, api level 21 and clang:
~/.conan/profiles/android_21_armeabi-v7_clang
- Boost package uploaded to a remote server (or in our local conan cache).
- The conan client installed with a remote configured to point to the server where we uploaded the package (not necessary if we are using our local conan cache).
Open the Android Studio and create a new app called MyBoostApp including the c++ support:
Select the API level (we are using 21), to match the one that was used to cross build the Boost libraries.
Create an empty activity:
And name it MainActivity:
Finally, in the wizard’s last dialog, select the defaults:
Change to the project view:
And in the app folder create a conanfile.txt
with the following contents:
[requires]
boost/1.66.0@conan/stable
[generators]
cmake
Open the CMakeLists.txt
file from the app folder and replace the contents with:
cmake_minimum_required(VERSION 3.4.1)
include(${CMAKE_CURRENT_SOURCE_DIR}/conan_build/conanbuildinfo.cmake)
set(CMAKE_CXX_COMPILER_VERSION "5.0") # Unknown miss-detection of the compiler by CMake
conan_basic_setup(TARGETS)
add_library(native-lib SHARED src/main/cpp/native-lib.cpp)
target_link_libraries(native-lib CONAN_PKG::boost)
The CMakeLists.txt
above is:
-
Including the file “conanbuildinfo.cmake”. This file will be generated by conan, and contains all the information about how to link with our dependencies, in this case with Boost, but we could add other dependencies in the
conanfile.txt
. -
Adding a native library native-lib and linking it with the Boost libraries.
Open the app/build.gradle
file:
-
In the android/defaultConfig section, specify the usage of armeabi-v7a and the c++_shared standard library.
ndk { abiFilters 'armeabi-v7a' stl 'c++_shared' }
-
In the android/defaultConfig/externalNativeBuild section add the -DANDROID_STL=c++shared argument to force CMake to use the right stl:
externalNativeBuild { cmake { cppFlags "" arguments "-DANDROID_STL=c++_shared" } }
Finally we are adding a new task after the android block that will call Conan to retrieve the needed
Boost packages and generate the mentioned conanbuildinfo.cmake
file.
We are going to use the android_21_armeabi-v7a_clang profile created in the previous post to build our application for ARM, corresponding to the specified abiFilter: armeabi-v7a
task conanInstall {
def buildDir = new File("app/conan_build")
buildDir.mkdirs()
// if you have problems running the command try to specify the absolute
// path to conan (Known problem in MacOSX) /usr/local/bin/conan
def conan_path = ""; // "/usr/local/bin/"
def cmd = conan_path + "conan install ../conanfile.txt --profile android_21_armeabi-v7a_clang"
print(">> ${cmd} \n")
def sout = new StringBuilder(), serr = new StringBuilder()
def proc = cmd.execute(null, buildDir)
proc.consumeProcessOutput(sout, serr)
proc.waitFor()
println "$sout $serr"
if(proc.exitValue() != 0){
throw new Exception("out> $sout err> $serr" + "\nCommand: ${cmd}")
}
}
The full build.gradle
file should look like this:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.jfrog.myboostapp"
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
arguments "-DANDROID_STL=c++_shared"
}
}
ndk {
abiFilters 'armeabi-v7a'
stl 'c++_shared'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
task conanInstall {
def buildDir = new File("app/conan_build")
buildDir.mkdirs()
// if you have problems running the command try to specify the absolute
// path to conan (Known problem in MacOSX) /usr/local/bin/conan
def conan_path = "" // "/usr/local/bin/"
def cmd = conan_path + "conan install ../conanfile.txt --profile android_21_armeabi-v7a_clang"
print(">> ${cmd} \n")
def sout = new StringBuilder(), serr = new StringBuilder()
def proc = cmd.execute(null, buildDir)
proc.consumeProcessOutput(sout, serr)
proc.waitFor()
println "$sout $serr"
if(proc.exitValue() != 0){
throw new Exception("out> $sout err> $serr" + "\nCommand: ${cmd}")
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
Open the default example cpp library in app/src/main/cpp/native-lib.cpp
and include some lines
using your library. Be careful with the JNICALL name if you used other app name in the wizard:
#include <jni.h>
#include <string>
#include <boost/regex.hpp>
#include <boost/version.hpp>
#include <sstream>
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_jfrog_myboostapp_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string line;
std::stringstream output;
std::string sentence_with_space = "Boost Rules";
output << "Using Boost " << (BOOST_VERSION / 100000) << "." << ((BOOST_VERSION / 100) % 1000) << "." << (BOOST_VERSION % 100) << std::endl;
boost::regex expr("\\w+\\s\\w+");
if (boost::regex_match(sentence_with_space, expr)) {
output << "The regex matches! '" << sentence_with_space << "'" << std::endl;
}
else{
output << "The regex doesn't match!" << std::endl;
}
return env->NewStringUTF(output.str().c_str());
}
Then run the app in an ARM emulator, it takes a while because emulating this ARM is quite slow:
Speeding up emulation & building multi-conf apps
Running an Android emulator for ARM architecture is very slow, so if we want to test or debug our app is always a good idea to build the application to Android/x86. Also, applications can be distributed for different architectures in a bundle, so we are going to repeat the process we saw in the first blog post for more architectures:
-
Build another standalone toolchain for x86:
python make_standalone_toolchain.py --arch=x86 --api=21 --stl=libc++ --install-dir=/myfolder/x86_21_toolchain
-
Create a new profile
.conan/profiles/android_21_x86_clang
to target the new toolchain for x86:standalone_toolchain=/myfolder/x86_21_toolchain target_host=i686-linux-android cc_compiler=clang cxx_compiler=clang++ [settings] compiler=clang compiler.version=5.0 compiler.libcxx=libc++ os=Android os.api_level=21 arch=x86 build_type=Release [env] CONAN_CMAKE_FIND_ROOT_PATH=$standalone_toolchain/sysroot PATH=[$standalone_toolchain/bin] CHOST=$target_host AR=$target_host-ar AS=$target_host-as RANLIB=$target_host-ranlib CC=$target_host-$cc_compiler CXX=$target_host-$cxx_compiler LD=$target_host-ld STRIP=$target_host-strip CFLAGS= -fPIC -I$standalone_toolchain/include/c++/4.9.x CXXFLAGS= -fPIC -I$standalone_toolchain/include/c++/4.9.x LDFLAGS=
-
In the
app/build.gradle
file add x86 to the abiFilters, to build the app also for x86ndk { abiFilters 'armeabi-v7a', 'x86' stl 'c++_shared' }
-
Adjust the task conanInstall to iterate all the declared abiFilters and install several versions of Boost using the different profiles, this new version of the task is generic and valid for any architecture we could add to the abiFilters:
task conanInstall { android.defaultConfig.ndk.abiFilters.each { def arch = it; def buildDir = new File("app/conan_build_" + arch) buildDir.mkdirs() // if you have problems running the command try to specify the absolute // path to conan (Known problem in MacOSX) /usr/local/bin def conan_path = ""; // "/usr/local/bin/" def cmd = conan_path + "conan install ../conanfile.txt --profile android_21_" + arch + "_clang" print(">> ${cmd} \n") def sout = new StringBuilder(), serr = new StringBuilder() def proc = cmd.execute(null, buildDir) proc.consumeProcessOutput(sout, serr) proc.waitFor() println "$sout $serr" if (proc.exitValue() != 0) { throw new Exception("out> $sout err> $serr" + "\nCommand: ${cmd}") } } }
-
Also adjust CMake to find the correct
conanbuildinfo.cmake
according to the architecture being built:cmake_minimum_required(VERSION 3.4.1) include(${CMAKE_CURRENT_SOURCE_DIR}/conan_build_${ANDROID_ABI}/conanbuildinfo.cmake) set(CMAKE_CXX_COMPILER_VERSION "5.0") # Unknown miss-detection of the compiler by CMake conan_basic_setup(TARGETS) add_library(native-lib SHARED src/main/cpp/native-lib.cpp) target_link_libraries(native-lib CONAN_PKG::boost)
-
Create a new conan package for the configuration:
conan install boost/1.66.0@conan/stable --build missing --profile=android_21_x86_clang
-
If you want to share the binaries with your team upload them to a server:
conan upload boost/1.66.0@conan/stable --all -r=myremote conan upload zlib* --all -r=myremote conan upload bzip2* --all -r=myremote # or just conan upload * --all -r=myremote
-
Run the application in a x86 emulator!
Now your application accepts more architectures in the abiFilters, for example arm64-v8a to target armv8, follow the same steps to create the standalone toolchain and a new profile android_21_arm64-v8a_clang
Building (manually or by a CI server) all packages that your team is going to use and uploading them to a server will save a lot time in your development workflow.
You can find the example project in this repository