First step, we set up a C++ project in Android Studio for the smaller NDK so dynamic library to run easily,

Gradle files need to be configured in a small way

android {
    compileSdkVersion 29

    defaultConfig {
        applicationId "com.dr.demo.componentapp"
        minSdkVersion 14
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
            ndk{
                abiFilters "armeabi-v7a"}}}}Copy the code


Step 2: In the hands-on writing project, I need to think about how to design my own Base class. BaseActivity first needs to abstract the business layer,

Public abstract class BaseActivity extends AppCompatActivity implements JNILogMessageCall {// The JNI service structure abstractions the interface protected JNIManager jniManager; // We can add a private TextView to our business logic to print logslogTextView;
    
    
    protected abstract JNIManager getJNIManager();
    
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        jniManager = getJNIManager();
        if(jniManager ! = null) { jniManager.setJNILogMessageCall(this); }logTextView = findViewById(R.id.logView);
        initView();
    }
    
    public abstract void initView();
    
    public abstract int getLayoutId();
    
    @Override
    public void logMessage(String message) {
        Log.i("ydr", message);
        if (logTextView ! = null) {logTextView.append(message);
            logTextView.append("\n"); }}}Copy the code

In order to add JNI business layer more elegantly, we design the interface JNILogMessageCall (JNI layer Log printing), JNIManager (JNI abstract interface).

public interface JNILogMessageCall {
    
    void logMessage(String message);
}

public interface JNIManager {
   
    void setJNILogMessageCall(JNILogMessageCall messageCall);
}
Copy the code

A real JNI business class could be designed this way

public class TestJNIManager implements JNIManager {
    
    JNILogMessageCall messageCall;
    
    static {
        System.loadLibrary("demo01-lib");
    }
    
    @Override
    public void setJNILogMessageCall(JNILogMessageCall messageCall) { this.messageCall = messageCall; } public native String stringFromJNI(); // We use this method in JNI layer to implement C to call Java layer method private voidsetLogMessage(String message) {
        if(messageCall ! = null) { messageCall.logMessage(message); }}}Copy the code

The editor below CPP automatically builds the native. CPP we can design a little bit and create a folder demo01 in CPP, and create a cmakelist.txt to organize the dynamic library compiled by the current project

Aux_source_directories (.demo01) // Specify header directories(.. /includeAdd_library (demo01-lib SHARED ${demo01}) // Link dynamic library target_link_libraries(demo01-lib log)Copy the code

Then cmakelist. TXT under CPP specifies the demo directory,

Cmake_minimum_required (VERSION 3.4.1 track) add_subdirectory (demo01)Copy the code

This allows us to structure multiple file directories demo01 demo02…. under the CPP project structure , etc.


How do we organize our C++ business code? We can do it in two ways: one is that the compiler locates our JNI functions by initially locating the Java full class name + the method name, and the other is that we can do it dynamically


First we’ll introduce the <jni.h> header file

Define two members:

const char *className = "com/dr/demo/componentapp/jni/TestJNIManager";

Copy the code
static JNINativeMethod methods[] = {
        {"stringFromJNI"."()Ljava/lang/String;", (void *) stringFromJNI}, }; // this member dynamically links the C++ and Javanative methods in JNICopy the code

Then implement two methods in JNI:

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) 
Copy the code
JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) Copy the code

Register and unregister native methods defined in our Java

jint registerMethod(JNIEnv *env, jint version) {

    jclass clazz = env->FindClass(className);

    if (clazz == nullptr) {
        return JNI_ERR;
    }
    jint size = sizeof(methods) / sizeof(methods[0]);
    jint res = env->RegisterNatives(clazz, methods, size);
    if (res == 0) {
        return version;
    }
    return JNI_ERR;


}


void unRegisterMethod(JNIEnv *env, jint version) {
    jclass clazz = env->FindClass(className);
    if(className ! =nullptr) { env->UnregisterNatives(clazz); }}/ / register
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = nullptr;

    jint res = vm->GetEnv((void **) &env, JNI_VERSION_1_4);
    if (res == 0) {
        return registerMethod(env, JNI_VERSION_1_4);
    } else if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == 0) {
        return registerMethod(env, JNI_VERSION_1_6);
    }

    return JNI_ERR;


}

/ / the registration
JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {
    JNIEnv *env = nullptr;
    if ((vm->GetEnv((void **) &env, JNI_VERSION_1_4))) {
        unRegisterMethod(env, JNI_VERSION_1_4);
    } else if ((vm->GetEnv((void**) env, JNI_VERSION_1_6))) { unRegisterMethod(env, JNI_VERSION_1_6); }}Copy the code



The specific JNI business implementation is implemented in this way

Static jmethodID methodID = NULL; static voidlogInfo(JNIEnv *env, jobject _clazz, const char *format, ...) {

    if (NULL == methodID) {
        jclass clazz = env->GetObjectClass(_clazz);

        methodID = env->GetMethodID(clazz, "setLogMessage"."(Ljava/lang/String;) V");

        env->DeleteLocalRef(clazz);
    }

    if(NULL ! = methodID) {// Format the log message char buffer[MAX_LOG_MESSAGE_LENGTH]; va_list ap; va_start(ap, format); vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, format, ap); va_end(ap); Jstring message = env->NewStringUTF(buffer);if(NULL ! Env ->CallVoidMethod(_clazz, methodID, message); Env ->DeleteLocalRef(message); }} // The code I really want to implement is extern"C" JNIEXPORT jstring JNICALL stringFromJNI(
        JNIEnv *env,
        jobject thiz) {
    std::string hello = "Hello from C++";
    logInfo(env, thiz, "log info");
    return env->NewStringUTF(hello.c_str());
}Copy the code


Project test has passed, welcome to leave a message to discuss attached source code

Github.com/tank2014gz/…