Search code examples
androidandroid-studioandroid-ndknativendk-build

Compile standalone binaries with NDK 13


With NDK 10 releases, I used to use ndk-build to compile standalone binaries for many different ABIs and multiple API levels. These binaries were to be included in an app. However, I installed the NDK on a new development machine as described in this article. This resulted in a folder ndk-bundle within my Android SDK directory. I used to compile the code from a command line and then copy the binaries to the resources of my Android Studio project, but I could not figure out how to do this with NDK 13 so I tried to follow the tutorial to include my native code in the Android Studio project. However, almost all recent instructions assume that one wants to build a library, not a standalone binary, so I did not get far.

I would switch to CMake if I figured out how to get it to work. My native project has the following (simplified) structure:

  • native

    • Android.mk

      LOCAL_PATH := $(call my-dir)/my_tool/src
      include $(CLEAR_VARS)
      LOCAL_MODULE    := my_tool
      LOCAL_SRC_FILES := main.c
      include $(BUILD_EXECUTABLE)
      
    • Application.mk

      APP_ABI := all
      APP_PLATFORM := android-21
      
    • my_tool

      • src
        • main.c

How can I compile this using either Android Studio or the NDK from the command line on our Windows 10 development machines?

Edit:

I am using this in build.gradle:

externalNativeBuild {
    ndkBuild {
        path "../native/Android.mk"
    }
}

Gradle creates a directory .externalNativeBuild which contains build configurations, but I cannot find out how to actually build the native code. No binaries are created when I run gradle.

I cannot find any information about gradle configuration for ndk-build.


Solution

  • I tried to follow your simplified structure as closely as possible.

    Here is the file app/build.gradle:

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion 24
        buildToolsVersion "25.0.1"
        defaultConfig {
            minSdkVersion 21
            targetSdkVersion 24
            externalNativeBuild {
                ndkBuild {
                    targets "my_tool"
                    abiFilters "armeabi-v7a"
                }
            }
        }
        externalNativeBuild {
            ndkBuild {
                path "../native/Android.mk"
            }
        }
    }
    

    The file native/Android.mk is identical to yours:

    LOCAL_PATH := $(call my-dir)/my_tool/src
    include $(CLEAR_VARS)
    LOCAL_MODULE    := my_tool
    LOCAL_SRC_FILES := main.c
    include $(BUILD_EXECUTABLE)
    

    I also have the files native/main.c and a minimal app/src/main/AndroidManifest.xml:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="my.tool" />
    

    I did not touch the root build.gradle script generated by Android Studio wizard:

    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    
    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.3.0-alpha3'
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            jcenter()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    Now I can build the project and here is what I get:

    $> file ./app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/my_tool
    ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
    

    Android Studio shows my main.c in the cpp folder in default view:

    enter image description here

    Update: to have the executable stripped and packaged in the APK, native/Android.mk must be changed:

    LOCAL_PATH := $(call my-dir)/my_tool/src
    
    install: LIB_PATH := $(call my-dir)/libs
    
    include $(CLEAR_VARS)
    LOCAL_MODULE    := my_tool
    LOCAL_SRC_FILES := main.c
    include $(BUILD_EXECUTABLE)
    
    install: $(LOCAL_INSTALLED)
        -mkdir $(LIB_PATH)
        -rm -r $(LIB_PATH)
        mv $< $(<:my_tool=lib-my_tool-.so)
        mv $(realpath $(dir $<)..) $(LIB_PATH)
    
    .PHONY: install
    

    Also, app/build.gradle needs some tweaking:

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 24
        buildToolsVersion "25.0.1"
        defaultConfig {
            minSdkVersion 21
            targetSdkVersion 24
            externalNativeBuild {
                ndkBuild {
                    targets "my_tool"
                    abiFilters "armeabi-v7a"
                    arguments 'V=1', 'install'
                }
            }
        }
        externalNativeBuild {
            ndkBuild {
                path "../native/Android.mk"
            }
        }
        sourceSets {
            main {
                jniLibs.srcDirs = ['../native/libs']
            }
        }
    }
    

    This relies on the old hack which depends on undocumented behavior of NDK, and may break without notice on a future NDK upgrade.