11

I'm using the Android Studio with NDK and JNI in a project with a large amount of C++ files. When I make changes in a single C++ file it won't apply in the code unless I rebuild the whole project and refresh the entire C++ files so they have to recompile.

The compilation process takes more than 3 minutes for every small change. Make it 20 times a day and you have lost an hour.

According to today, after I make a change to a file I go to menu BuildRefresh Linked C++ Projects and then run the project, resulting in a full, redundant compilation of all files.

I'm looking for way for the compiler to refresh only the changed file, and as a result shorten the build process.

Note: This problem only occurs in Windows. When I run Android Studio on a Mac, the compiler recompiles only the relevant files.

This is my CMakeLists.txt file:

cmake_minimum_required(VERSION 3.4.1)

FILE(GLOB CPP_SRC
     "src/main/cpp/*.c"
     "src/main/cpp/*.h"
     "src/main/cpp/*.cpp"
     "src/main/cpp/*.hpp"
)

add_library(MyLib
             SHARED
             ${CPP_SRC})

find_library(log-lib
              log)

target_link_libraries(MyLib
                       ${log-lib})

target_link_libraries(MyLib
                      android
                      log
                      EGL
                      GLESv2)

And my build.gradle file:

apply plugin: 'com.android.library'

android {
    signingConfigs {
        config {
            keyAlias '*****'
            keyPassword '*****'
            storeFile file(*****)
            storePassword '*****'
        }
    }
    compileSdkVersion 27
    buildToolsVersion '27.0.3'

    defaultConfig {

        minSdkVersion 16
        targetSdkVersion 27
        versionCode 1

        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        externalNativeBuild {
            cmake {
                arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=c++_shared", "-DCMAKE_BUILD_TYPE=Release", "-DANDROID_CPP_FEATURES=rtti exceptions"
                cppFlags "-D__STDC_FORMAT_MACROS", '-Ofast', '-fsigned-char', "-std=c++14", "-frtti", "-fexceptions", "-mtune=arm7", "-mfpu=vfpv3-d16", "-mfloat-abi=softfp", "-Wall",
                        "-DCOMPILE_EUROPE_ID_AND_FACE_OCR_MANAGER",
                        "-DCOMPILE_FRENCH_PASSPORT_SIGNATURE",
                        "-DCOMPILE_FRENCH_ID_BACK_OCR",
                        "-DCOMPILE_FRENCH_PASSPORT_SIGNATURE_MANAGER",
                        "-DCOMPILE_PASSPORT_AND_FACE_OCR_MANAGER",
                        "-DCOMPILE_MRZ_OCR",
                        "-DCOMPILE_FRENCH_ID_BACK_OCR_MANAGER"
            }

            ndk {
                abiFilters 'x86', 'armeabi-v7a'
            }
        }

        splits {
            abi {
                enable true
                reset()
                include 'x86', 'armeabi-v7a'
                universalApk true
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
            'proguard-rules.pro'
            signingConfig signingConfigs.config
        }
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

    libraryVariants.all { variant -> variant.outputs.all { output ->
                          outputFileName = "${"libScanovateImaging"}.aar" }
    }

}

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}

dependencies {
    implementation 'com.google.android.gms:play-services-vision:15.0.0'
    implementation 'com.android.support:recyclerview-v7:27.1.1'
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta6'
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:design:27.1.1'
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ariel Mann
  • 340
  • 2
  • 13
  • Might be a problem with your project's [Application.mk](https://developer.android.com/ndk/guides/application_mk) or [Android.mk](https://developer.android.com/ndk/guides/android_mk) files. Don't know enough about Android Studio to know for sure. – ImprobabilityCast May 01 '18 at 05:10
  • If this misbehavior on Windows only happens in Android Studio, but command-line **ndk-build** or **cmake** do partial recompile correctly, you can run the native build in the Terminal window (make sure that you use exactly same paths and parameters as AS does). – Alex Cohn May 01 '18 at 07:39
  • Do you have `minifyEnabled true` property in your `build.gradle` file? – Somesh Kumar May 04 '18 at 06:33
  • Could this be because of `GLOB` ? I mean, if you list the source files explicitly, this may be resolved. And I don't think you need `.h` and `.hpp` files in `${CPP_SRC}` – Alex Cohn May 08 '18 at 15:00
  • You have a typo in your `build.gradle` file (`libraryVariants.all` has one too many curly end brace). I have tested two `ABIs` and `ndkBuild` does it correctly (see updated answer -> Structure). `splits` is ok. It must be your `CMakeLists.txt`, as the [link](https://stackoverflow.com/questions/49285443/androidstudio-rebuilds-everything-every-time-if-multiple-abis-are-supported) suggests. Show your Structure as I have (it will be in a different place). – Jon Goodwin May 08 '18 at 20:56

2 Answers2

3

Introduction

I think this has been a problem for a long time (judging by the complaints) in Android Studio (more specifically the NDK build system). Now, in the latest version, it is finally ready and working (hopefully).

Note: gradlew builds are significantly faster than using the Android Studio IDE, and can be built using a batch file script (.bat).

CMake possible problems(P = problem, C = cause, S = solution)

(P1) Android Studio rebuilds everything every time if multiple ABIs are supported See this SO Q/A.
(C1) Build variants all had the same output directory for the .o files.
(S1) You need to split out the build directory into release/debug and into the different ABIs.

My Setup (ndkBuild)

Android Studio 3.1, NDK release 10, gradle version 4.4 (plugin 3.1.0)

I have 2 libraies to build (libhello_world.so with 1 c++ file, and libjni_photoeditor.so with 24 c++ files).

I am using externalNativeBuild -> ndkBuild to build my native code (you could use CMake also, I have not tested that).

app level build.gradle:

apply plugin: 'com.android.application'
        android {
            compileSdkVersion 25

            defaultConfig {
                applicationId "xxxxxxxxxxxxxxxxxxxxx"
                minSdkVersion 16
                targetSdkVersion 16
                versionCode 1
                versionName "1.0"
                ndk {
                }//ndk
            }//defaultConfig

            buildTypes {
                release {
                    minifyEnabled true
                    proguardFiles.add(file('proguard-android-optimize.txt'))
                    proguardFiles.add(file('proguard-rules.pro'))
                }//release
                debug {
                    minifyEnabled false
                    jniDebuggable true
                    renderscriptDebuggable true
                }//debug
            }//buildTypes
            
            externalNativeBuild {
                // Encapsulates your CMake build configurations.
//              cmake {
//                // Provides a relative path to your CMake build script.
//                path "src/main/cpp/CMakeLists.txt"
//              }
                ndkBuild {
                //****this is the working build****
                    path 'src/main/cpp/Android.mk'
                }//ndkBuild
            }//externalNativeBuild
        }//android
        
        dependencies {
        }//dependencies

Application.mk:

APP_ABI := armeabi-v7a
APP_PLATFORM := android-19
APP_STL := stlport_static

Android.mk:

#================================================
LOCAL_PATH := $(call my-dir) #only call it ONCE !
#================================================
include $(CLEAR_VARS)
LOCAL_MODULE      := hello_world
LOCAL_MULTILIB := 32
LOCAL_SRC_FILES :=  hello_world.cpp
include $(BUILD_SHARED_LIBRARY)
#================================================
include $(CLEAR_VARS)
LOCAL_MODULE      := libjni_photoeditor
LOCAL_MODULE_TAGS := optional
LOCAL_SHARED_LIBRARIES := libm liblog libjnigraphics
LOCAL_LDLIBS := -lm -llog -ljnigraphics -lbcc
LOCAL_LDLIBS := -lm -llog -ljnigraphics
LOCAL_SRC_FILES := _jni.cpp utils.cpp quantize.cpp #etc.. 24 files
LOCAL_CFLAGS := -Werror \
    -I$(OUT)/../../../../frameworks/compile/libbcc/include
LOCAL_LDFLAGS := -L$(OUT)/system/lib
include $(BUILD_SHARED_LIBRARY)

Test 1. Android Studio Build -> Build APK(s) build

(A) Modify one of my projects 24 C++ files (quantize.cpp) and rebuild.

(B) Build -> Build APK(s)

(C) Only the C:\Android\PhotoRend1\app\build\intermediates\ndkBuild\debug\obj\local\armeabi-v7a\objs-debug\jni_photoeditor\quantize.o timestamp changed (and the library).

Test 2. gradlew terminal window (command line) build

From the project root I use gradlew in a terminal window to assemble my apk.

(1) Every thing is up-to-date build:

C:\Android\PhotoRend1>gradlew assembleDebug

Starting a Gradle Daemon, 1 busy and 1 incompatible and 1 stopped Daemons could not be reused, use --status for details
> Configure project :app
> Task :app:externalNativeBuildDebug
Build hello_world armeabi-v7a
make.exe: `C:/Android/PhotoRend1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libhello_world.so' is up to date.
Build jni_photoeditor armeabi-v7a
make.exe: `C:/Android/PhotoRend1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libjni_photoeditor.so' is up to date.
BUILD SUCCESSFUL in 3m 46s

(2) Modify one of my projects 24 C++ files (quantize.cpp) and re-assemble.

(3) One file changes is up-to-date build:

C:\Android\PhotoRend1>gradlew assembleDebug

> Configure project :app
> Task :app:externalNativeBuildDebug
Build hello_world armeabi-v7a
make.exe: `C:/Android/PhotoRend1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libhello_world.so' is up to date.
Build jni_photoeditor armeabi-v7a
[armeabi-v7a] Compile++ thumb: jni_photoeditor <= quantize.cpp
[armeabi-v7a] SharedLibrary  : libjni_photoeditor.so
BUILD SUCCESSFUL in 59s

(4) As you can see it did an incremental build.

Some links: Manual NDK updates

Jon Goodwin
  • 9,053
  • 5
  • 35
  • 54
  • 1
    I have updated my gradle to 4.4. I am using android 3.1.2 and NDK 16. I am obliged to use CMake build for native code. I have built the project from the cmd with gradlew assemblerelease but the problem continues. Can your solution be tested with CMake? – Ariel Mann May 06 '18 at 14:38
  • I have added my CMake and gradle files. I will also try to look into your latest answer – Ariel Mann May 08 '18 at 12:30
0

Finally I have found the cause to the problem.

In my project I have worked with symlink C++ files. The NDK cannot immediately apply the changes made in these files and they won't be expressed in your app unless you rebuild the whole project.

The solution is to work with hard copy files.

If I find some way to work correctly with symlink files it would be better for my needs. Until then I can at least edit single or multiple files without recompiling the whole project.

This is a relatively esoteric situation but might be helpful for someone in the future.

Ariel Mann
  • 340
  • 2
  • 13