Android JNI + Intelij Idea + Gradle. Full process automation

Good day!
This post is a small guide on automating the compilation of native code in Intellij Idea using Gradle. Gradle provides quite a lot of functionality for auto-automation of project builds. But even connecting native libraries to an Android project requires additional efforts from the developer.

Background


Recently, I changed my job and got a job at a company developing my own mobile software. My new work colleagues and I decided to switch from Eclipse (which had been undergoing all the development) to Intellij Idea, and in addition from Ant to Gradle. We have a fairly large project, with a decent amount of code, including using native C and C ++ code, both self-written and ready-made libraries.

For those involved in the development of Android projects using the Android NDK in the Intellij Idea + Gradle environment, I ask for cat.

Hastily


As for java code, we quite easily transferred the entire development process to the new IDE and build system, I will not go into this process. With porting native code, it was much more complicated.

Since we were not given enough time to search for a suitable solution immediately, so we just collected all the modules of the native project for the platforms we needed, put it in the source folder of our project and used the quickly found solution to automatically include them in our apk file, this approach is described in this article .

Finding the right solution


In fact, the first approach for some time absolutely suited us. Libraries have been tested for a long time, and we did not have to often make changes to the native code, until recently, when we needed to add another module. Then we began to look for a solution how to compile all our source code, including native.

It turned out that Gradle ignores Android.mk files and creates its own. To do this, it provides great functionality for transferring various flags and ndk properties. This is well written about in this article . But we liked to use compilation capabilities using * .mk files.

Therefore, we recalled that Gradle provides great functionality for building and tried to directly call the ndk-build script that Android NDK provides.

In order for this to happen automatically, a separate Gradle task was written and added to the dependencies of the task of automatically packing native libraries. Here is a clipping from the build.gralde file of our module:

task('compileNative') {
    exec {
        executable = 'path/to/ndk/ndk-build'
        args = ["NDK_PROJECT_PATH=src/main"]
    }
}
task nativeLibsToJar(type: Zip, description: 'create a jar archive of the native libs', dependsOn: 'compileNative') {
    destinationDir file("$buildDir/native-libs")
    baseName 'native-libs'
    extension 'jar'
    from fileTree(dir: 'jniLibs', include: '**/*.so')
    into 'lib/'
}
tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn(nativeLibsToJar)
}


In principle, this code is already enough to compile native sources in automatic mode and connect them to the project. But we work as a team and it’s not good if everyone changes the main assembly file for themselves, because 'path / to / ndk /' everyone will most likely have their own. Therefore, it was decided to transfer the path to the NDK to the local project build settings file.

# Настройка локального расположения NDK 
ndk.dir=path/to/ndk
# Настройка локального расположения SDK
sdk.dir=path/to/sdk


The local.properties file should be located in the root of the project. If you add this file, you will need to specify not only the NDK directory, but also the SDK directory, otherwise Gradle will issue a warning and refuse to build your project.

Now we change our Gradle task, adding use of a local path to NDK.

task('compileNative') {
    def $ndkProjectRootFolder = 'src/main'
    def $ndkDirPropertyName = 'ndk.dir' 
    // переменная для хранения настроек
    Properties properties = new Properties()
    // загружаем файл с настройками проекта
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    // получаем путь до корневой директории ndk
    def $ndkDir = properties.getProperty($ndkDirPropertyName)
    // если не установлен то посоветуем это сделать в локальных настройках
    if( $ndkDir == null)
        throw new RuntimeException("Property 'ndk.dir' not found. Please specify it. \n" +
                " It must be something like this in your local.properties file \n" +
                " ndk.dir=path/to/your/ndk")
    // вызываем на выполнение скрипт для компиляции native исходников
    exec {
        executable = $ndkDir + '/ndk-build' 
        args = ["NDK_PROJECT_PATH=" + $ndkProjectRootFolder]
    }
}


Gradle allows you to define variables and throw exceptions during the build process, so we will use this functionality. If in the local settings, the developer did not specify the path to the Android NDK, then we remind him to do this.

And last but not least, some developers are sitting on Windows.

task('compileNative') {
    def $ndkBuildScript = //путь то файла ndk-build (linux) / ndk-build.cmd (windows) относительно корня ndk
            System.properties['os.name'].toLowerCase().contains('windows') ?
                    'ndk-build.cmd' :
                    'ndk-build'
    def $ndkProjectRootFolder = 'src/main'
    def $ndkDirPropertyName = 'ndk.dir' 
    // переменная для хранения настроек
    Properties properties = new Properties()
    // загружаем файл с настройками проекта
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    // получаем путь до корневой директории ndk
    def $ndkDir = properties.getProperty($ndkDirPropertyName)
    // если не установлен то посоветуем это сделать в локальных настройках
    if( $ndkDir == null)
        throw new RuntimeException("Property 'ndk.dir' not found. Please specify it. \n" +
                " It must be something like this in your local.properties file \n" +
                " ndk.dir=path/to/your/ndk")
    // вызываем на выполнение скрипт для компиляции native исходников
    exec {
        executable = $ndkDir + '/' + $ndkBuildScript
        args = ["NDK_PROJECT_PATH=" + $ndkProjectRootFolder]
    }
}


The result of our work is the automatic execution of compiling native code using all the advantages of Android.mk files and compiling it into an apk file. Well, as a plus, now you do not need to store compiled libraries in the repository.

I hope this approach will be useful.

Also popular now: