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:

  1. Build another standalone toolchain for x86:

     python make_standalone_toolchain.py --arch=x86 --api=21 --stl=libc++ --install-dir=/myfolder/x86_21_toolchain
    
  2. 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=
    
  3. In the app/build.gradle file add x86 to the abiFilters, to build the app also for x86

     ndk {
        abiFilters 'armeabi-v7a', 'x86'
        stl 'c++_shared'
     }
    
  4. 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}")
            }
        }
     }
    
  5. 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)
    
  6. Create a new conan package for the configuration:

     conan install boost/1.66.0@conan/stable --build missing --profile=android_21_x86_clang
    
  7. 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
    
  8. 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