Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add JNI (C/C++ native code) to existing Android Studio project

Like the title says - how to add native code to existing Android Studio project, without breaking the current project, including gradle and proguard settings?

like image 711
Vladimir Avatar asked Jul 15 '17 12:07

Vladimir


People also ask

How does an Android APK execute compiled native code using the Java native interface?

Using Android Studio 2.2 and higher, you can use the NDK to compile C and C++ code into a native library and package it into your APK using Gradle, the IDE's integrated build system. Your Java code can then call functions in your native library through the Java Native Interface (JNI) framework.

How do I open a .C file on Android?

To Install and Use C/C++ compiler in Termux (in Termux clang is the C/C++ compiler) , Download & Install Termux from : Play Store. After Installing execute this command pkg install clang. After Successfully installing clang you can compile C/C++ scripts.

What is JNI layer in Android?

JNI is the Java Native Interface. It defines a way for the bytecode that Android compiles from managed code (written in the Java or Kotlin programming languages) to interact with native code (written in C/C++).


2 Answers

Since Android Studio 3.1 its possible easy way:

1. Create cpp folder inside app\src\main.

2. Create <YOUR_FILE_NAME>.cpp file in app\src\main\cpp path (e.g. native-lib.cpp)

3. Add CMakeLists.txt file to app folder.

In that file name of the library, .cpp file path and some other settings should be defined, e.g. (from new, empty Android Studio Project with C++ support):

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )
                          ^^^^^^^^^^^^^^
                          YOUR_CPP_FILE_NAME

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

4. Add to build.gradle (Module app) externalNativeBuild tag with reference to CMakeLists.txt into android section:

android {
    compileSdkVersion 26
    defaultConfig {
        ...
    }
    buildTypes {
        ...
    }
    externalNativeBuild {               <--- these lines should be added
        cmake {                         <--- these lines should be added
            path "CMakeLists.txt"       <--- these lines should be added
        }                               <--- these lines should be added
    }                                   <--- these lines should be added
}

5. Add to build.gradle (Module app) externalNativeBuild tag with cmake tag into defaultConfig section:

...
defaultConfig {
    applicationId "<YOUR_APP_ID>"
    minSdkVersion 26
    targetSdkVersion 26
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"


    externalNativeBuild {   <--- these lines should be added
        cmake {             <--- these lines should be added
            cppFlags ""     <--- these lines should be added
        }                   <--- these lines should be added
    }                       <--- these lines should be added
}
...

(example of "basic" build.gradle file also available in new, empty Android Studio project with C++ support)

6. Resync Project with Gradle files

By clicking Sync Project Sync Project button on Toolbar in the toolbar. NB! In Android Studio 3.3, the icon is  Android Studio 3.3 Sync Project button on Toolbar.

Also, take a look at Official Tutorial.

PS. If files not shown in cpp folder:

try File/Invalidate Caches & Restart as Thinh Vu mentioned in his comment.

like image 54
Andrii Omelchenko Avatar answered Oct 17 '22 07:10

Andrii Omelchenko


Follow this steps from your existing project:

1. Modify build.gradle (Module app) to look like this (a lot changes!):

    apply plugin: 'com.android.model.application'

    model {
        android.signingConfigs {
            create ("myConfig") {
                keyAlias '--your-key-alias--'
                keyPassword '--key-password--'
                storeFile file('--/path/to/keystore.jks--')
                storePassword '--store-password--'
            }
        }
        android {
            compileSdkVersion 25
            buildToolsVersion '25.0.2'

            defaultConfig {
                applicationId "--your.app.name--"
                minSdkVersion.apiLevel 19
                targetSdkVersion.apiLevel 25
                versionCode 1
                versionName "1.0"
            }
            buildTypes {
                release {
                    minifyEnabled true
                    proguardFiles.add(file('proguard-android-optimize.txt'))
                    proguardFiles.add(file('proguard-rules.pro'))
                    signingConfig = $("android.signingConfigs.myConfig")
                }
            }
            ndk {
                moduleName "--c-file--"
                ldLibs.addAll(["android", "log"])
            }

        }
        android.dexOptions {
            javaMaxHeapSize "2048m"
        }
    }

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:25.3.1'
    }

You can copy/paste the above code and modify at least the values with "--value--" to match yours.

2. Modify build.gradle (Project)

where it says something like this:

dependencies {
    classpath 'com.android.tools.build:gradle:2.3.3'
}

to this:

dependencies {
    classpath 'com.android.tools.build:gradle-experimental:0.9.3'
}

The number in my example 0.9.3 is the latest version of gradle-experimental to be found here. Eventually change your gradle version in gradle-wrapper.properties to the version recommended by Android Studio if you did not already.

3. Move your proguard settings file

proguard-android-optimize.txt to app/proguard-android-optimize.txt

4. Add the call from java

like this

static {
    System.loadLibrary("--c-file--");
}
private native byte my_jni(Context context, byte[] mByte, int i);

changing to your needs. The example above loads the c-file (write it without the extension) - the same one declared in the gradle file, and calls the function my_jni, passing the application's Context, some byte array and some int, expecting that the functions returns a byte.

5. Create the function in JNI:

Now the name of your function is highlighted in red - allow Android Studio to create it Create function ... with clicking on the red lamp on the row. This creates the function in your c file and changes focus to it.

Done

Further reading here.

Tips:

  • Take care to free everything you malloc, ReleaseByteArrayElements for every GetByteArrayElements and so on

  • Take care how to properly return some dangerous values from C to Java, like arrays and Strings

like image 22
Vladimir Avatar answered Oct 17 '22 07:10

Vladimir