📜 ⬆️ ⬇️

Android JNI + Intelij Idea + Gradle. Full process automation

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

Prehistory


Recently, I changed jobs and got a job at a company that develops my own mobile software. My new colleagues and I decided to switch from Eclipse (which all development was done before) 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.

Those who are engaged in the development of Android projects using the Android NDK in the Intellij Idea + Gradle environment I ask for cat.

Hastily


As for the java code, we rather easily transferred the whole development process to a new IDE and build system, I will not delve into this process. With the transfer of native code, everything was much more complicated.
')
Since we didn’t have enough time to search for a suitable solution right away, so we just collected all the modules of the native project for the platforms we needed, put them 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 we are absolutely satisfied. Libraries were tested a long time ago, and we didn’t have to make frequent changes to the native code, until recently, when we needed to add another module. Then we began to look for a solution on how to compile all of our source code, including the native one.

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

That's why we remembered that Gradle provides great functionality for the build and tried to directly call the ndk-build script provided by the Android NDK.

In order for this to happen automatically, a separate Gradle-task was written and added to the dependencies of the task on automatic packaging of native libraries. Here is a cut 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 the native sources in automatic mode and connect them to the project. But we work in a team and it’s not good if everyone will modify the main build file for themselves, because everyone will most likely have his own 'path / to / ndk /'. Therefore, it was decided to place the path to the NDK in the local settings file of the project assembly.

 #    NDK ndk.dir=path/to/ndk #    SDK sdk.dir=path/to/sdk 


The local.properties file must be in the project root. 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 corresponding warning and refuse to build your project.

Now we are changing our Gradle task, adding the use of a local path to the 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 in the build process, so let's use this functionality. If in the local settings, the developer does not specify the path to the Android NDK then we remind him to do it.

And lastly, we must remember that 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 compilation of native code using all the advantages of Android.mk files and compiling it into an apk file. Well, as a plus, now it is not necessary to store the compiled libraries in the repository.

I hope this approach will be useful.

Source: https://habr.com/ru/post/227541/


All Articles