Compile native library that relies on other native libraries to run using Android NDK?

UPDATE: with AGP 4.0 (still not released), you can consume OpenSSL from maven.

dependencies {
    implementation "com.android.ndk.thirdparty:openssl:1.1.1d-alpha-1"
}

In Android.mk, you simply add (at the end)

$(call import-module, prefab/curl)

Here are full details: https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html


Original answer:

  • 0. Build openssl libraries for Android; you must choose the ABI (armeabi-v7a and x86 are usually enough). You can find the official tutorial long and boring. In this case, you can find prebuilt binaries on GitHub or elsewhere. Decide whether you want shared libs or static libs.

  • 1. With Android Studio 2.3 you can build your library by the integrated externalNativeBuild gradle task. But you can build it separately, using ndk-build command.

  • 2. If you choose not to use gradle, copy the built shared libs to app/src/main/jniLibs directory of your Android project.

  • 3. You need a JNI wrapper to connect your Java code with C code. On the Java side, declare necessary native methods in one or more classes. These methods must be implemented on the C/C++ side, as described in the Android JNI walkthrough. Your Java code must explicitly load the shared library that includes these native method implementations. Note that you cannot directly call native functions in your library, or in opensssl library, even if these functions are exported.

The working Android.mk:

include $(CLEAR_VARS)

LOCAL_MODULE := vrazovpn
LOCAL_SRC_FILES := src/myc_files.c src/myother_c_files.c
LOCAL_STATIC_LIBRARIES := openssl libcrypto
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := openssl
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libssl.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libcrypto
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libcrypto.a
include $(PREBUILT_STATIC_LIBRARY)

few comments to clarify the above:

  • LOCAL_SRC_FILES paths are relative to LOCAL_PATH.
  • LOCAL_C_INCLUDE paths are relative to the working directory, that's why we often prefix them with $(LOCAL_PATH).
  • It's OK to use LOCAL_C_INCLUDES to refer to headers that belong to some library that we use, but it is much safer and cleaner to export these headers with the library, using the power of LOCAL_EXPORT_C_INCLUDES; there are other settings that can be exported similarly.
  • Android.mk should not (and cannot) set the target ABI; it receives the target ABI from ndk-build. You can control which ABIs to include in build either by listing them in your Application.mk file or on the command line, e.g.

    ndk-build APP_ABI="x86 armeabi-v7a"

If you build your library in Android Studio 2.3 or above, using the gradle plugin, the APP_ABI setting is ignored. You must specify the list in abiFilters.


There are thousands of operations you can do with native development (I mean C/C++). These languages are so light and you can carry some heavy operations from Java to C/C++. For example, Camera hardware is one of the heaviest and baddest hardware in Android devices. So, it really sucks, when you try to handle this and add your own operations. When it comes to native development, this operations working on CPU and don't need translating via Dalvik or something else. That's why you need all CPU architecture of android devices. Because, each arch has its own calculation speed or technology. If there is any Co-Processors, you should take into consideration this also. This was a short overview about NDK.

For building or rebuilding native libraries you have two ways, CMake and NDK build. There are not lots of differences between these two building system, so i'd like to explain my answer with NDK building.

For native development, you should have main/jni folder which is you should put your libraries and native sources inside it and build them in this folder. Before you should create two files, Android.mk and Application.mk. Android.mk is for building native libs (modules), adding flags and etc. You can build static and shared libraries in Android.mk. When you build your C sources, it creates static library (.a). But these libraries not for using in Java side. You can only use them for creating shared libraries (.so). For this, you should build your C++ sources, and if you want, you can add your static libraries to this.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

ifneq (,$(filter $(TARGET_ARCH_ABI), armeabi-v7a x86 arm64-v8a x86_64))

LOCAL_MODULE := myLibrary

LOCAL_SHARED_LIBRARIES := cpufeatures opencv_imgproc opencv_core 

LOCAL_C_INCLUDES := $(LOCAL_PATH)/c-files

LOCAL_SRC_FILES := $(LOCAL_PATH)/myJni.cpp

include $(BUILD_SHARED_LIBRARY)

--- 1st line returns main/jni path.

--- 2nd line clear all old LOCAL_*** variables.

--- 3th line build modules for each arch's which are declared there.

--- 4th line shows module name, when you build it, NDK automatically add lib and .so extensions to it (libmyLibrary.so).

--- 5th line adds other shared libraries, which you need their sources in your native sources.

--- 6th line adds C files to your module.

--- 7th line shows your C++ sources which you try to build.

--- 8th line is building command.

In Application.mk you can give commands something like, your project in release mode or debug mode. And you should give arch's in this file. For example APP_ABI := armeabi-v7a x86 arm64-v8a x86_64 and etc.

When you try to build libraries and use them in java side, you should do some operations.

  1. Check your ndk path in local.properties file in project folder. It shows ndk-bundle path for building makefiles.

    ndk.dir=/Users/../Library/Android/sdk/ndk-bundle

  2. Check build scripts in gradle file. This script shows where shared libs should be located:

    sourceSets.main {
        jni.srcDirs = []
        jniLibs.srcDir 'src/main/libs'
    }
    
  3. And show your makefile path to gradle.

    externalNativeBuild {
       ndkBuild {
          path 'src/main/jni/Android.mk'
       }
    }
    

I think my answer will help you for understanding some details. For addition, you can take a look these answers also.

  1. For calling native methods: Enter

  2. For linking shared libraries: Enter

Enjoy!