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

本文使用最新的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

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CMakeLists.txt中,我们可以使用以下方式设定so的输出路径。 首先,我们需要在CMakeLists.txt文件中添加以下代码来设置so的输出路径: ```cmake set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}) ``` 上述代码设置了CMake生成的so文件的输出路径为`${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}`,其中`${CMAKE_SOURCE_DIR}`代表项目的根目录,`${ANDROID_ABI}`代表当前的Android ABI(Application Binary Interface)。 在这个例子中,我们假设你的项目使用了多个不同的ABI,比如armeabi-v7a、arm64-v8a和x86等。在这种情况下,`${ANDROID_ABI}`将根据当前编译的ABI自动设置为相应的值。 此外,我们还需要在Android.mk文件中添加以下代码,以确保编译生成的so文件被正确复制到相应的输出路径。 ```makefile $(call import-add-path,$(LOCAL_PATH)/../../libs/$(TARGET_ARCH_ABI)) ``` 上述代码将`${PROJECT_PATH}/libs/${TARGET_ARCH_ABI}`路径添加到了编译路径中,其中`${TARGET_ARCH_ABI}`代表当前编译的ABI。 最后,我们需要在app的build.gradle文件中添加以下代码,以确保生成的so文件被正确复制到APK的libs目录下。 ```groovy android { // ... sourceSets { main { jniLibs.srcDirs = ['libs'] } } // ... } ``` 上述代码将项目的libs目录设置为so文件的源目录,这样在构建APK时,so文件就会被复制到APK的libs目录下。 通过以上步骤,我们就可以成功设置CMake生成的so文件的输出路径为项目的libs目录下的相应ABI目录。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值