Hand-in-hand teaching you how to build NDK environment

Keywords: Android cmake Java Gradle

This paper is based on Android Studio 3.4.2, gradle:3.2.1

1. What are JNI and NDK?

JNI is the abbreviation of Java Native Interface (Java Local Interface) and a bridge between Java and other languages. The main applications in Android are: audio and video development, hot repair, plug-in, reverse development and system source code call. In order to facilitate the use of JNI technology, Android provides a set of NDK tools, which is essentially no different from JNI development. NDK is a means to implement JNI in Android.

NDK has two main functions:
  • Help developers develop C/C++ dynamic libraries quickly
  • NDK uses cross-compiler to develop binary code for one platform and another.

2. The Use of NDK in Android

1) First download the NDK installation package

Download NDK, LLDB, CMake in SDK Tools

  • NDK: The tools we need to download will be generated in the ndk-bundle directory under the SDK root directory
  • CMake: A cross-platform compilation and construction tool that can describe the installation process of all platforms in simple statements
  • LLDB: An Efficient C/C++ Debugging Tool
2) Writing Interface

The interface here is simple, a TextView and a Button, click on Button and call JNI's method to modify the value of TextView.

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".JNIDemo.JNIActivity">

    <TextView

        android:id="@+id/jni_tv"

        android:layout_marginTop="20dp"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintEnd_toEndOf="parent"

        android:text="I'm a TextView"

        android:textAllCaps="false"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" />

    <Button

        android:id="@+id/jni_btn"

        app:layout_constraintTop_toBottomOf="@id/jni_tv"

        android:layout_marginTop="20dp"

        app:layout_constraintStart_toStartOf="parent"

        android:text="Call Method From JNI"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>
3) Writing Activity Code
/*

* Copyright (c) 2019\. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.

* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.

* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.

*                                              Vestibulum commodo. Ut rhoncus gravida arcu.

*/

package com.learnandroid.learn_android.JNIDemo;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

import android.widget.Toast;

import com.learnandroid.learn_android.R;

public class JNIActivity extends AppCompatActivity {

    private TextView jni_tv;

    private Button jni_btn;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_jni);

        jni_tv = findViewById(R.id.jni_tv);

        jni_btn = findViewById(R.id.jni_btn);

        jni_btn.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                jni_tv.setText(JNIUtils.sayHelloFromJNI());

            }

        });

    }

}
4) Writing JNIUtils
package com.learnandroid.learn_android.JNIDemo;

public class JNIUtils {

    static {

        System.loadLibrary("MyJNIHello");

    }

    public static native String sayHelloFromJNI();

}

The static code block here first loads the dynamic libraries that need to be used, and then creates the native method.

5) Generate header files

In Terminal of Android Studio, cd goes to the root directory of the current project and executes the javah command

(Note that the class containing the local method is written with the full package name)

# binguner @ binguner in ~/AndroidStudioProjects/learn_android/app/src/main/java [10:12:17]

$ javah -d ../cpp com.learnandroid.learn_android.JNIDemo.JNIUtils

- D. / cpp specifies where the header file is generated: in the cpp folder at the top level of the current directory.

Then open com_learn android_learn_android_JNIDemo_JNIUtils.h in the Project directory to see the method prototype it generated for us.

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class com_learnandroid_learn_android_JNIDemo_JNIUtils */

#ifndef _Included_com_learnandroid_learn_android_JNIDemo_JNIUtils

#define _Included_com_learnandroid_learn_android_JNIDemo_JNIUtils

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class:     com_learnandroid_learn_android_JNIDemo_JNIUtils

* Method:    sayHelloFromJNI

* Signature: ()Ljava/lang/String;

*/

JNIEXPORT jstring JNICALL Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

  (JNIEnv *, jclass);

#ifdef __cplusplus

}

#endif

#endif
6) Implementing C++ Code

Create a new C++ file named MyJNIDemo in the cpp directory

At this time, if you write code directly in the C++ file, there is no code prompt, then you need to use the CMake tool.

First edit the build.gradle file of app and add the following

Add under Android - > defaultConfig

// Using the Cmake tool

externalNativeBuild {

    cmake {

        cppFlags ""

        //Generate multiple versions of so files, specifying the cpu architecture to be compiled

        abiFilters "armeabi-v7a"

    }

}

Android - > Add

// Configure CMakeLists.txt path

externalNativeBuild {

    cmake {

        //Name of compiled so file

        **path "src/main/cpp/CMakeLists.txt"**

    }

}

Then edit the CMakeLists.txt file (cmake script configuration file, cmake compiles the relevant C/C++ source files according to the instructions in the script file, generates shared libraries or static blocks of the compiled products, and Gradle packages them into APK)

# Setting the minimum version of CMake

cmake_minimum_required(VERSION 3.4.1)

# The first parameter: Create and name a lib, which automatically generates the so Library of the lib.

# The second parameter: Set it to STATIC (static library ending in. a) or SHARED (dynamic library ending in. so).

# Last parameter: Number provides a relative source path

# Multiple LIBS can be set up with add_library, and CMake automatically builds and packages lib packages into apk s.

add_library( # Sets the name of the library.

        MyJNIHello

        # Sets the library as a shared library.

        SHARED

        # Provides a relative path to your source file(s).

        MyJNIHello.cpp

        )

# Search for a preset lib and save its path as a variable

# CMake includes the Lib of the system in the search path by default. We just need to set a name for the NDK lib we want to add.

# CMake checks whether it exists between completion of the 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)

# Associate the specified Libraries

target_link_libraries( # Specifies the target library.

        MyJNIHello

        # Links the target library to the log library

        # included in the NDK.

        ${log-lib}

        )

At this point, click Sync Now and write a C++ file with code hints.

The C++ code is as follows

//

// Created by Binguner on 2019-08-13.

//

#include <jni.h>

//#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h"

extern "C"

JNIEXPORT jstring JNICALL

Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

        (JNIEnv *env, jclass jclass1) {

    return env->NewStringUTF("JNIHELLO");

}

The Java_com_learnandroid_learn_1 android_JNIDemo_JNIUtils_sayHelloFromJNI method is implemented here, and a string named "JNIHELLO" is returned.
Why did I comment out # include "com_learn android_learn_android_JNIDemo_JNIUtils.h"?

Because the header file generated by JNI is designed to help us get the prototype of the native method. If we write the prototype easily, the naming may be wrong. You can also see that the name of the method is very complex. But if we can write this name by ourselves, we can do it without including the header file. CMake will generate this method automatically.

For example, I added a test1() method to JNIUtils

After clicking on the code prompt, it will automatically generate the corresponding method in the C++ code. We just need to implement the method in it.

7) Running App

After clicking the button,

8) Print logs in JNI

Add the following to the C++ file:

//

// Created by Binguner on 2019-08-13.

//

#include <android/log.h>

#include <jni.h>

//#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h"

#define LOG_TAG "System.out.c"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

extern "C"

JNIEXPORT jstring JNICALL

Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

        (JNIEnv *env, jclass jclass1) {

    LOGD("TAGD,a=%d,b=%d",1,2);

    return env->NewStringUTF("JNIHELLO");

}

Welcome to the author of this article:

Sweep code to pay attention to and reply to "dry goods" to get the 1000G Android, iOS, Java Web, big data, artificial intelligence and other learning resources I organized.

Posted by aaronrb on Mon, 12 Aug 2019 23:28:04 -0700