Android so library development - using Studio to call so library (2)

1. JNI specification so library call

        You have created your own so library in Android Studio to generate your own so library. This is a JNI-standard so library. You can directly put the so library in libs and use it according to the calling method in MainActivity in the above article.

1. build.gradle (app) configuration

android {

    defaultConfig {
        // 加载so类型
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
    }
    // so库路径
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

2. Load the method in the so library

public class NativeImpl {
    // 加载so库
    static {
        System.loadLibrary("native_xiaoxu");
    }

    /**
     * 添加 native 方法
     */
    public static native String getUserName();
}

        Although the method name is displayed in red in Sudio, it does not affect the use.

3. Call method in Activity

NativeImpl.getUserName();

2. Non-JNI standard so library call

         When Android Studio generates its own so library, if we don't use native-lib.cpp to standardize the naming, how can we directly call the method in cpp? At this time, at this time, we need to create our own so library, call the third-party so library in our own so library, and then call our own so library in our own program.

        We still make modifications on the basis of the previous project and use the previously generated so library.

1. Put it into the so library

        Since gradle 4.0, the precompiled dependency reference method of jni has been modified. The original method of directly placing it in "src/main/jniLibs" has been abolished, so the precompiled library in jniLibs should be replaced. For example, put Change jniLibs to mylibs. Of course, the IMPORTED path in CMakeLists.txt should also be modified accordingly.

 2、CMakeLists.txt

# 设置 Cmake 最小版本
cmake_minimum_required(VERSION 3.10.2)

# 项目名称
project("nativetestdemo")

# 创建并命名一个库,将其设置为STATIC或SHARED,并提供其源代码的相对路径。
add_library( # 设置库名称
        new_xiaoxu
        # 设置库的类型
        SHARED
        # 源代码路径
        native-lib.cpp)

# 声明一个变量 distribution_DIR 并且赋值
# ${CMAKE_SOURCES_DIR} 是 cmake 自带的参数,可以直接使用
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../mylibs)

# 添加一个库,在这里可以理解为声明了一个变量,来承载需要引入的库
add_library(
        sotest # 表示的是模块名称,可以自己任意取
        SHARED # 这个是固定的,基本上表示共享库
        IMPORTED # 这个也基本上是固定的,表示当前是导入的,跟我们 java 的 import 差不多含义
        )

# 给sotest这个库设置so文件链接的位置
set_target_properties(sotest # 库的名称
        PROPERTIES IMPORTED_LOCATION # 表示当前库是导入的方式 
        ../../../../mylibs/${ANDROID_ABI}/libnative_xiaoxu.so # so动态库的具体路径,这里也可以使用上面的变量表示:${distribution_DIR}/${ANDROID_ABI}/libnative_xiaoxu.so
        )

# 如果第三方库有头文件的,为了在编码的时候可以有提示,使用下面的指令进行指定
# 这里是指定头文件那些的目录,这样指定后,编译器会扫描这个目录,编码的时候就可以提示到这里的方法了,不加这个也不会报错
include_directories(${distribution_DIR}/includes)

# 表示从系统中查找库,其中 log-lib 表示我们为这个库的别名(可以随便取),log 就是真实的库名称
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)

# 连接所有的库.
target_link_libraries( # 指定目标库.
        new_xiaoxu
        sotest
        # 将目标库链接到包含在NDK中的日志库。
        ${log-lib})

        The main code is in the comments section.

3. Add the .h file

        Put the NativeImpl.h in the previous article under the cpp folder.

4、native-lib.cpp

#include <jni.h>
#include <string>
#include "NativeImpl.h"

/* 获取NativeImpl */
NativeImpl nativeImpl;
NativeImpl* getNativeImpl(){
    return &nativeImpl;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_wm_auto_nativetestdemo_NativeImpl_getUserName(
        JNIEnv* env,
        jclass clazz) {
    char* c = getNativeImpl()->getUserName();
    return env->NewStringUTF(c);
}

        The code here is basically unchanged, but before getUserName() is the method in NativeImpl.cpp under cpp, and here is the method in NativeImpl.cpp in the so library.

5. Java code NativeImpl

public class NativeImpl {
    // 加载so库
    static {
        System.loadLibrary("new_xiaoxu");
    }

    /**
     * 添加 native 方法
     */
    public static native String getUserName();
}

        Only the loaded library has been modified

overall code structure

Summarize:

1. Two usage scenarios for referencing the so library

        First of all, both calling methods are feasible, mainly depending on whether the JNI specification so library is provided, if not, you can only choose the second solution. Secondly, it depends on whether you want this SO library to be directly exposed to the JAVA layer. If the answer is no, you can only choose the second option.

Project source code

Reference: Android NDK development: calling third-party so libraries through C/C++

PS: In the reference code, remember to modify the so path.

Three, JNI callback

1. Add a callback method to NativeImpl

public class NativeImpl {
    // 加载so库
    static {
        System.loadLibrary("new_xiaoxu");
    }

    /**
     * 添加 native 方法
     */
    public static native String getUserName();

    public native String onCallback() {
        // JNI回调成功
    }
}

2、 native-lib.cpp

#include <jni.h>
#include <string>
#include "NativeImpl.h"

JNIEnv *gEmv = NULL;
jobject gJavaObj = NULL;
jmethodID nativeCallback = NULL;
void setCallBack();

/* 获取NativeImpl */
NativeImpl nativeImpl;
NativeImpl* getNativeImpl(){
    return &nativeImpl;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_wm_auto_nativetestdemo_NativeImpl_getUserName(
        JNIEnv* env,
        jclass clazz) {
    char* c = getNativeImpl()->getUserName();
    
    // 映射Java方法
    jclass clazz = env->GetObjectClass(thiz);
    nativeCallback = env->GetMethodID(clazz,"onCallback","()V");
    // 保存到全局变量
    gEmv = env;
    gJavaObj = thiz;
    // 调用回调方法,正常应该在其他方法中调用,这里为了方便直接调用了
    setCallBack();

    return env->NewStringUTF(c);
}

void setCallBack() {
    gEmv->CallVoidMethod(gJavaObj, nativeCallback);
}

        Here, some parameters are saved globally and mapped to Java methods, which can be transferred to the Java layer. But there will be problems when calling back in the child thread, because the jobject we saved is a local reference, once our function returns the jobject will be recycled and destroyed by GC, so although our gJavaObj saves the global reference at this time, it Now it points to an illegal address, of course we use the illegal address to directly report crash. That is to create global references based on local references, so that they will not be destroyed by GC recycling.

3. Sub-thread call

#include <jni.h>
#include <string>
#include "NativeImpl.h"

JavaVM *gJavaVM = NULL;
jobject gJavaObj = NULL;
jmethodID nativeCallback = NULL;
static void* setCallBack(void *arg);

/* 获取NativeImpl */
NativeImpl nativeImpl;
NativeImpl* getNativeImpl(){
    return &nativeImpl;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_wm_auto_nativetestdemo_NativeImpl_getUserName(
        JNIEnv* env,
        jclass clazz) {
    char* c = getNativeImpl()->getUserName();
    
    gJavaObj = env->NewGlobalRef(thiz);
    jclass clazz = env->GetObjectClass(thiz);
    nativeCallback = env->GetMethodID(clazz,"onClientEvent_native","()V");
    env->GetJavaVM(&gJavaVM);

    pthread_t id;
    //通过pthread库创建线程
    pthread_create(&id,NULL,setCallBack,NULL);

    return env->NewStringUTF(c);
}

static void* setCallBack(void *arg){
    JNIEnv *env;
    //从全局的JavaVM中获取到环境变量
    gJavaVM->AttachCurrentThread(&env,NULL);
	//跨线程回调Java层函数
    env->CallVoidMethod(gJavaObj, nativeCallback);
    gJavaVM->DetachCurrentThread();
	return ((void *)0);
}

        In this way, it can be called in the child thread. Note: If this is used in a non-child thread, another exception "attempting to detach while still running code" will appear. This is an error reported when calling DetachCurrentThread(). The place where the DetachCurrentThread function is called is in the java thread, that is, when java calls the C++ code, the AttachCurrentThread method is called in the C++ code to obtain JNIEnv. At this time, JNIEnv has been passed in through parameters, and you don’t need to AttachCurrentThread again to get. An error will be reported when released.


        It is no problem to use the self-generated so library for the above two solutions, but in the actual project development, our so library is provided by the underlying development colleagues. Using the above solution, an error will be reported when calling System.loadLibrary("lib"), indicating that the library cannot be found. Finally, with the help of colleagues, the reference to so was modified.

1. Modify the path of the so library file

        Create so library storage path under cpp

2. Modify CMakeLists.txt

cmake_minimum_required(VERSION 3.18.1)

project("jniServer")

add_library( # Sets the name of the library.
        jniServer

        # Sets the library as a shared library.
        SHARED

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

project("jniClient")

add_library( # Sets the name of the library.
        jniClient

        # Sets the library as a shared library.
        SHARED

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

target_include_directories(jniServer
        PRIVATE
        include)

target_include_directories(jniClient
        PRIVATE
        include)

add_library(client SHARED IMPORTED)

set_target_properties(client
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH}/client_lib.so)

add_library(server SHARED IMPORTED)
set_target_properties(server
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH}/server_lib.so)


include_directories(
        include
)

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)

target_link_libraries( # Specifies the target library.
        jniServer
        server
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

target_link_libraries( # Specifies the target library.
        jniClient
        client
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

install(TARGETS jniServer
        LIBRARY DESTINATION .
        )

install(TARGETS jniClient
        LIBRARY DESTINATION .
        )

        This configuration file rewrites Server and Client for two so libraries, and deletes a set of corresponding codes for one so library. It is the same as above when used.

Guess you like

Origin blog.csdn.net/c19344881x/article/details/128580889