JNI/NDK:CMakeLists.txt 构建so库,导入三方so库,以及遇到的坑

懵懂的女人 提交于 2020-03-06 12:40:19

https://blog.csdn.net/dengweijunkedafu/article/details/89218863?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

本文使用最新的JNI构建工具CMake完成

通过这篇文章,你讲学习到:

    camke构建自己的三方so库
    学会使用cmake管理自己jni文件
    学会使用cmake调用三方的so库
    最后分析自己开发过程中遇到的坑

1.CMakeLists.txt 构建so库

    创建jni的工具类JNI :
        这个类的原本用途是,在工程里用来管理jni的方法,和加载so库用的
        但是在这里仅仅只是为了加载so库

    package com.bendeng.jnindk;
     
    /**
     * @author: dwj<br>
     * @date: 2019/4/10 15:39<br>
     * @desc: <br>
     */
    public class JNI {
     
        // Used to load the 'test-lib' library on application startup.
        static {
            // 一定要加这一句,否不会生成so库
            System.loadLibrary("test-lib");
        }
    }

    创建自己的cpp文件(test.cpp)和头文件(test.h)

    test.cpp

    #include "../header/test.h"
     
    int return_value(void)
    {
        return 5;
    }
    void print_value()
    {
        cout<<"successful call this method"<<endl;
    }
    int add(int x, int y)
    {
        return x+y;
    }
    int mul (int x, int y)
    {
        return x*y;
    }

    test.h

    #include <iostream>
    using namespace std;
     
    int return_value(void);
    void print_value();
    int add(int x, int y);
    int mul (int x, int y);

    配置CMakelist.txt文件,构建goodutil库,两步如下:

    cmake_minimum_required(VERSION 3.4.1)
     
    #so库的输出路径
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
        ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})
     
    #1. 添加自己的so库test-lib
    add_library( # Sets the name of the library.
                 test-lib
     
                 # Sets the library as a shared library.
                 SHARED
     
                 # Provides a relative path to your source file(s).
                 src/main/cpp/include/test.cpp )
     
    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 )
    #2.添加链接
    target_link_libraries( # Specifies the target library.
                           test-lib
                           # Links the target library to the log library
                           # included in the NDK.
                           ${log-lib} )

    设置需要生成的CPU平台,市面上的机型基本都设置了

    Make Project,就会在libs目录下生成so库

      
2.管理Android工程的jni文件

    上面的JNI.java 在正式的工程中使用如下:包含so库的加载,和本地方法的声明;
    其中native-lib是由native-lib.cpp构建的so库,native-lib.cpp是按照jni接口的规范写的。
    native-lib.cpp内可以引用三方的so库,三方的库不用安装jni的规范些,因为三方的库是用 native-lib.cpp封装后提供给java调用的
    JNI.java可以直接调用,调用是根据(包名_类名_方法)名寻找的

    package com.bendeng.jnindk;
     
    /**
     * @author: dwj<br>
     * @date: 2019/4/10 15:39<br>
     * @desc: <br>
     */
    public class JNI {
     
        // 此参数被C++赋值
        public int number;
     
        // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("test-lib");
        }
     
        /**
         * 通过在C++里面改变field参数值number
         */
        public native void changeFieldValue();
     
        /**
         * 通过在C++里面调用java方法
         */
        public native double callJavaMethod();
     
        // 此方法被C++调用
        public double max(double d1, double d2) {
            return d1 > d2 ? d1 : d2;
        }
     
        /**
         * 调用C++封装的so方法
         */
        public native int returnValue();
     
    }

    native-lib.cpp,按jni接口规范写的,供java层调用,这里也可以调用.cpp封装的so库文件limit

    #include <jni.h>
    #include <string>
    #include <iostream>
    #include "header/test.h"
     
    extern "C" JNIEXPORT void
     
    JNICALL
    Java_com_bendeng_jnindk_JNI_changeFieldValue(JNIEnv *env, jobject jobj) {
     
        // 获取jobj中class对象
        jclass clazz = env->GetObjectClass(jobj);
        // 获取字段number的ID,最后一个参数是签名
        jfieldID id_number = env->GetFieldID(clazz, "number", "I");
        // 获取字段的值
        jint number = env->GetIntField(jobj, id_number);
        // 打印默认的初始值
        std::cout << "number=" + number << std::endl;
        // 给number赋新的值
        env->SetIntField(jobj, id_number, 100);
     
    }
     
    extern "C" JNIEXPORT jdouble
    JNICALL
    Java_com_bendeng_jnindk_JNI_callJavaMethod(JNIEnv *env, jobject jobj) {
     
        // 获取jobj中class对象
        jclass clazz = env->GetObjectClass(jobj);
        // 获取方法max的ID,最后一个参数是签名
        jmethodID id_max = env->GetMethodID(clazz, "max", "(DD)D");
        // 获取字段的值
        jdouble max_value = env->CallDoubleMethod(jobj, id_max, 1.2, 3.4);
        return max_value;
     
    }
    extern "C" JNIEXPORT jint
    JNICALL
    Java_com_bendeng_jnindk_JNI_returnValue(JNIEnv *env, jobject jobj) {
        // 此方法是调用test.cpp封装的so里面的方法
        return return_value();
    }

    配置CMakelist.txt文件,构建goodutil库,两步如下:

    cmake_minimum_required(VERSION 3.4.1)
     
    #so库的输出路径
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
        ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})
     
    #1. 添加自己的so库native-lib
    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 )
     
    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 )
    #2.添加链接
    target_link_libraries( # Specifies the target library.
                           native-lib
                           # Links the target library to the log library
                           # included in the NDK.
                           ${log-lib} )

3.导入第三方的so库

    把第三方的库文件放到libs目录下面,包含上面生成的7中CPU文件
    然后配置 CMakeLists.txt ,一共4步,如下:

    cmake_minimum_required(VERSION 3.4.1)
    #1.配置第三方so库.h头文件路径
    include_directories(src/main/cpp/header)
     
    #指定so库输出路径
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
        ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})
     
    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 )
     
    #2.添加第三方库
    add_library(test-lib SHARED IMPORTED)
    #3.添加库的路径
    set_target_properties(test-lib
            PROPERTIES IMPORTED_LOCATION
            ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/libtest-lib.so)
     
    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 )
     
    #4.添加链接
    target_link_libraries( # Specifies the target library.
                           native-lib
                           test-lib
                           # Links the target library to the log library
                           # included in the NDK.
                           ${log-lib} )

    Make Project,会在libs下生成新的libnative-lib.so库文件,图就不贴了,文章上面有贴出过
    最后运行的时候,记得把build.gradle文件中的生成cmake相关的代码注释掉,同时需要添加一段代码,否则会报错:couldn't found "libtest-lib.so"。如下图所示:

4.做jni调用过程中遇到的坑

     公司一个项目,需要做jni编程,来调用算法模型里面的方法,算法那边给到的是so库文件,里面都是C++方法,不是按照jni接口规范写的,因此java没法直接调用。所以需要在算法so库的基础上,再封装一个so库,采用jni接口规范编写;
    可能算法工程师也不懂so库的封装,给到我这边的so库和.h文件,按照上面的方法,一直编译不通过。后来拿到cpp文件后,自己通过上面的方法,先封装算法的so库,然后通过jni接口规范来调用,发现可以编译通过,原来搞了半天,是算法so库封装有问题;
    编译运行都ok,但是一启动APP就闪退,还是报错找不到“libtest-lib.so”库,网上说需要加上下面这段代码:

    sourceSets {
                main {
                    jni.srcDirs = []
                    jniLibs.srcDirs = ['libs']
                }
            }

    但是加上后,又报出新的错误,大概意思是so库重复,这样一想,肯定是又会生成新的so库文件,导致重复的,最后把build.gradle中cmake相关代码注释掉,跑起来就正常工作了:

    apply plugin: 'com.android.application'
     
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.bendeng.jnindk"
            minSdkVersion 18
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    //        externalNativeBuild {
    //            cmake {
    //                cppFlags ""
    //            }
    //        }
     
    //        ndk{
    //            abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips', 'mips64'
    //        }
     
            sourceSets {
                main {
                    jni.srcDirs = []
                    jniLibs.srcDirs = ['libs']
                }
            }
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    //    externalNativeBuild {
    //        cmake {
    //            path "CMakeLists.txt"
    //        }
    //    }
    }
     
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.+'
        implementation 'com.android.support.constraint:constraint-layout:1.0.2'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.1'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    }

导入第三方so库(非jni接口规范封装的so库),并在此基础上做jni开发,总算高一段落了,但是技术宅男不能满足于此,下一阶段将为大家输出jna的编程原理,使用起来将更加简单方便。

 

参考资料:https://blog.csdn.net/hukou6335490/article/details/83687419

https://blog.csdn.net/hjj378315764/article/details/79834352

https://blog.csdn.net/yuanzhihua126/article/details/78992068

https://blog.csdn.net/wzhseu/article/details/79683045
————————————————
版权声明:本文为CSDN博主「嗨摔得漂亮」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dengweijunkedafu/article/details/89218863

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!