JNI开发专题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010782846/article/details/84252239

Android JNI开发(以Android Studio2.2以上为例)#

Android 开发JNI的基础概念和工具

  • JNI:Java Native Interface的java本地接口语言,在Android虚拟机提供若干API以实现JAVA和其他语言的通信(主要为C\C++).
  • NDK:Android开发JNI的工具包。
  • ndk-build:Android Studio2.2之前使用的打包.so文件的命令。
  • Cmake:Android Studio2.2为了快速继承JNI进行改善ndk-build繁琐操作的新工具。

Android JNI常见用途

  • 扩展JAVA虚拟机的能力,解决JAVA无法调用底层硬件驱动的短板
  • C/C++效率高,常应用于音视频处理、数学运算、实时渲染等情况
  • 使用C/C++沉淀的优秀代码,比如人脸识别、文件压缩
  • 应用于对内存要求较高的场景
  • 特殊的业务场景

Android 开发JNI环境集成

参考自:https://blog.csdn.net/yao_94/article/details/79151804

1、如果Android studio之前没有进行过NDK相关的配置的话,那么首先就要在Android studio的File->Project Structure窗口下进行NDK的相关下载;如下图没有下载之前

2、点击Download下载,下载好之后as会自动将ndk加载到项目中,默认情况下ndk下载到了sdk所在的目录下,如下图所示:

3、下载完成时候也可以通过local.properties文件查看sdk和ndk在电脑上的保存路径,local.properties文件的内容如下:

# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Wed Jan 24 15:14:42 CST 2018
ndk.dir=D\:\\develop\\android_studio_sdk\\ndk-bundle
sdk.dir=D\:\\develop\\android_studio_sdk

4、下载Cmake 所需要的工具Cmake、LLDB.具体流程为File->System Settings->Android SDK 然后点选如下图:

选择后可以点选 Apply下载。

5、创建支持C++的Android工程:

参考资料:https://www.jianshu.com/p/4eefb16d83e3

方法一 新项目直接创建:

创建一个新项目(Create New Project)

点击File — New — New Project,把Include C++ Support前面的CheckBook勾上

配置C++支持功能(Customize C++ Support)

在Customize C++ Support界面默认即可。

  • C++ Standard

指定编译库的环境,其中Toolchain Default使用的是默认的CMake环境;C++ 11也就是C++环境。两种环境都可以编库,至于区别,后续会跟进,当前博文使用的是CMake环境。

  • Exceptions Support

如果选中复选框,则表示当前项目支持C++异常处理,如果支持,在项目Module级别的build.gradle文件中会增加一个标识 -fexceptions到cppFlags属性中,并且在so库构建时,gradle会把该属性值传递给CMake进行构建。

  • Runtime Type Information Support

同理,选中复选框,项目支持RTTI,属性cppFlags增加标识-frtti

认识自动生成的CMakeLists.txt构建脚本

CMakeLists.txt文件用于配置JNI项目属性,主要用于声明CMake使用版本、so库名称、C/CPP文件路径等信息,下面是该文件内容:

# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK.

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).
         # Associated headers in the same location as their source
         # file are automatically included.
         src/main/cpp/native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its 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 )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                   native-lib

                   # Links the target library to the log library
                   # included in the NDK.
                   ${log-lib} )
  • cmake_minimum_required(VERSION 3.4.1)

CMake最小版本使用的是3.4.1。

  • add_library()

配置so库信息(为当前当前脚本文件添加库)

  • native-lib

这个是声明引用so库的名称,在项目中,如果需要使用这个so文件,引用的名称就是这个。值得注意的是,实际上生成的so文件名称是libnative-lib。当Run项目或者build项目是,在Module级别的build文件下的intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下会生成相应的so库文件。

  • SHARED

这个参数表示共享so库文件,也就是在Run项目或者build项目时会在目录intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下生成so库文。此外,so库文件都会在打包到.apk里面,可以通过选择菜单栏的Build->Analyze Apk…*查看apk中是否存在so库文件,一般它会存放在lib目录下。

  • src/main/cpp/native-lib.cpp

构建so库的源文件。

JNI类型和JAVA基本类型映射关系

图片引用自:https://www.cnblogs.com/mingfeng002/p/6595047.html

基本数据类型:

引用数据类型:

JNI方法签名

参考:https://www.cnblogs.com/mingfeng002/p/6595047.html

JNI的编写思路

  • 拿到C/C++的源代码,阅读C工程师交付的API了解业务需求
  • 配置好Module的CMakeLists.txt脚本文件
  • 定义好对外开放的Native方法和XX.cpp文件
  • 通过native方法生成.cpp文件对外的方法

阅读C工程师对外的.h文件API

比如:我们需要把一些私密的计算方法封装到C语言中,以防止用户反编译获知算法的核心,.h文件定义对外的接口和定义自定义类型,比如c的.h文件如下:

typedef unsigned char		cs_uint8_t;

typedef struct _bias_data_v235_t
{
	cs_double_t		BFP;    //体脂率
	cs_double_t		SLM;    //肌肉量
	cs_double_t		BMC;    //骨盐量

} bias_data_v235_t;

cs_bias_v235(cs_uint8_t uSex, cs_uint8_t uAge, cs_uint16_t uHeight, 
cs_uint16_t uWeight, cs_uint16_t uImpedance, bias_data_v235_t * data_ptr);

typedef struct定义的是自定义结构体,类似Java的对象Class

typedef 重命名类型的名字

配置好Module的CMakeLists.txt脚本文件

CMakeLists.txt是Cmake的配置脚本,build.gradle需要配置好路径:

android{
	externalNativeBuild {
    	cmake {
        	path file('CMakeLists.txt')
    	}
	}
}

另外,build.gradle在defaultConfig中还可以配置兼容的abi系统:

defaultConfig{
	externalNativeBuild {
        cmake {
            cFlags "-DSTDC_HEADERS"
            cppFlags ""
            abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
        }
    }
}

以上,我们来看看CMakeLists.text的配置:

add_library( # Sets the name of the library.
        weight

         # Sets the library as a shared library.
         SHARED

         # Provides a relative path to your source file(s).
         src/main/cpp/native-lib.cpp
         src/main/cpp/cs_bias_v235.cpp)

add_library表示的是添加的绑定.cpp的路径,可以多个文件绑定,weight是生成的so库名称,此处生成的so文件名称是weight.so,src/main/cpp/native-lib.cpp是.cpp文件的相对路径,

  • SHARED:表示动态库,会被动态链接,在运行时被加载

  • STATIC:静态库,是目标文件的归档文件,在链接其它目标的时候使用

  • MODULE:模块库,是不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数动态链接

    find_library( # Sets the name of the path variable.
    #android系统每个类型的库会存放一个特定的位置,而log库存放在log-lib中
    log-lib

            # Specifies the name of the NDK library that
            # you want CMake to locate.
      	  # android系统在c环境下打log到logcat的库
            log )
    

表示从系统中查找依赖库.

	# 配置库的链接(依赖关系)
	target_link_libraries( # Specifies the target library.

                   # 目标库
                   native-lib

                   # Links the target library to the log library
                   # included in the NDK.
                   # 依赖于
                   ${log-lib} )

定义好对外开放的Native方法和XX.cpp文件

创建 XXXmUtils类

首先加载本地lib包:

 static {
    	System.loadLibrary("weight");
 }

然后定义结构体对应的类:

public class BiasData {
	//体脂率
	private double BFP;
	//肌肉量
	private double SLM;
	//骨盐量
	private double BMC;
}	

定义Native方法:

    public static native BiasData calWeighingAligorithm(char uSex, char uAge, char uHeight, char uWeight, char uImpedance);

定义Native方法后,会报红,然后鼠标移动到方法上,按alt+enter就出现一个 create function ,选择就会在.cpp文件中自动生成方法:

	extern "C"
	JNIEXPORT jobject JNICALL
	Java_com_chipsea_weighingalgorithmmodule_WeighingAlgorithmUtils_calWeighingAligorithm(JNIEnv *env,
                                                                                  jobject instance,
                                                                                  jchar uSex,
                                                                                  jchar uAge,
                                                                                  jchar uHeight,
                                                                                  jchar uWeight,
                                                                                  jchar uImpedance) {
	}

JNIEXPORT jobject JNICALL 表示返回值是 jobject
方法的格式为:JAVA_JAVA包路径(.修改为_)+方法名
方法参数多出 JNIEnv *env 表示jni的环境,可以拿到该指针调用jni.h对外扩展的方法, jobject instance表示,调用方法的JAVA类。

从.h文件中,我们会遇到两个问题:

  • typedef 重命名的基本类型如何处理
  • C语言中的自定义结构体如何处理?

typedef在C语言中,是为了标识重命名,所以当我们调用时,只需要强制转换即可。

比如:typedef unsigned char cs_uint8_t,定义的是一个无符号char,对象Java 就是char基本类型,传入的时候我们可以使用jchar强制转换为cs_uint8_t:

cs_bias_v235((cs_uint8_t) (uSex), (cs_uint8_t) uAge,
                                    (cs_uint16_t) (uHeight),
                                    (cs_uint16_t) uWeight, (cs_uint16_t) uImpedance, &t);

data_ptr需要的是一个结构体的地址,自定义结构体对应的是Java中的类,我们可以映射到类中,比如&t是bias_data_v235_t自定义结构体的地址,当计算完成后我们得到这样的一个数据引用,保存了计算的值,所以我们需要读取&t的值,赋值到我们返回的对象中

引入#include "cs_bias_v235.h"头文件:

1、创建我们需要的JAVA对象类在C中的指针引用:

jclass objectClass = (env)->FindClass("com/chipsea/weighingalgorithmmodule/BiasData");

if (objectClass == 0) {
    return NULL;
}

2、结构体初始化:

 struct _bias_data_v235_t t = {0};

3、调用.h文件
CSBIAS_RESULT result = cs_bias_v235((cs_uint8_t) (uSex), (cs_uint8_t) uAge,
(cs_uint16_t) (uHeight),
(cs_uint16_t) uWeight, (cs_uint16_t) uImpedance, &t);
4、获取jclass的方法ID、然后创建类引用:
//获取objectClass类的结构方法
jmethodID objCon = (env)->GetMethodID(objectClass, “”, “()V”);

jobject obj = (env)->NewObject(objectClass, objCon);

5、获取objectClass属性ID:

jfieldID fileBFP = (env)->GetFieldID(objectClass, "BFP", "D");

其中objectClass为我们需要调用的jclass,"BFP"是属性名,"D"是方法签名。

6、给新建的obj对象赋值:

(env)->SetDoubleField(obj, fileBFP, t.BFP);

其中obj是对象的引用地址,fileBFP是属性ID,t是经过调用.h文件后,c方法处理后得出的结果。

7、最后 return obj;

JNI的转换调用

从上面例子中,我们知道,Android工程师想要实现C工程师提供给我们的提供的方法,我们需要做一下的转换:

1、Java对象转换成自定义结构体。

2、自定义结构体转换成JAVA对象返回。

3、String对象转换到char对象

4、基本类型的转换

5、数组的处理

第二种转换,其实就是我们上一节所说的那种,通过(env)->FindClass(xxx)查找一个对象的jclass字节码对象,然后生成jobject对象。
第二种类似,差别在于是native传入的对象参数,比如:

    jclass objectClass=(env)->GetObjectClass(instance);

也今天获取到一个Class对象。

最后一种需要转换的原因是C中没有String对象,所以需要转换成char指针

const *char c=(env)->GetStringUTFChars(jString);

第四种,就是JNI会自定转换的,比如String 转成 jstring

常见问题

1、JNI error: expected unqualified-id

extern “C” 之后 执行#include 导入会出错。

猜你喜欢

转载自blog.csdn.net/u010782846/article/details/84252239
今日推荐