Android零基础进阶 | JNI

JNI背景

Java的一个显著的特点就是跨平台,但是正因如此,Java和本地机器的交互就很少。但是在某些情况下,比如提升性能或实现特定需求,需要我们不得不使用调用C/C++来实现,因此JNI应运而生。

JNI和NDK的关系

Jni全称:Java Native Interface,Java原生接口,是为Java和C/C++等语言相互访问提供的接口。
NDK全称:Native Develop Kit ,原生开发工具集,为了实现JNI所需要使用的工具。
一般都是编写C/C++ Code,通过NDK生成.so文件,然后再通过JAVA调用其中的接口来实现相应的功能。
如果是IOS的小伙伴,个人感觉这种关系有点类似于Swift调用OC?

JNI的实现流程

在这里插入图片描述

Jni编写大致过程

  1. 首先声明一个native方法
  2. 通过javah生成相应的头文件(有的会先javac生成class文件,试了直接javah好像也没有问题= 。=)
  3. 实现头文件中声明函数的方法
  4. 编译生成so文件
  5. 在java中进行调用so文件
    在这里插入图片描述

声明Native方法

    public class Native{
        public static native void Hello();
    }

通过javah生成相应的Native头文件

进入main目录下。执行javah -encoding utf-8 包名命令,会看到生成了一个.h的头文件

实现相应的方法

创建一个C/Cpp文件,然后将头文件赋值到里面进行少许修改,例如:

public static native int send_message(byte[] buffer, int size);
//.h 自动生成
JNIEXPORT jint JNICALL Java_com_vpn_NativeClass_send_1message
  (JNIEnv *, jclass, jbyteArray, jint);
//.c
JNIEXPORT jint JNICALL Java_com_vpn_NativeClass_send_1message
  (JNIEnv * env, jclass, jbyteArray byteData, jint size) {

    return 0;
}

关于JNIEnv
JNIEnv 是一个与线程相关的结构体,是c和java相互调用的桥梁,由于线程相关,所以线程B中不能使用线程A中的JNIEnv函数。
在这里插入图片描述
JNIEnv 与 JavaVM : 注意区分这两个概念;

– JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局仅仅有一个该虚拟机映射,所有线程共用一个;

– JNIEnv : JavaVM 在线程中的代表, 每一个线程都有一个, JNI 中可能有非常多个 JNIEnv;

JNIEnv 作用 :

– 调用 Java 函数 : JNIEnv 代表 Java 执行环境, 能够使用 JNIEnv 调用 Java 中的代码;

– 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 须要使用 JNIEnv 来操作这个 Java 对象;

数据类型
基础数据类型

在这里插入图片描述

类型签名

在这里插入图片描述

与JNIEnv相关的常用函数

//获取jclass对象,参数:this的意思,就是native方法所在的类

1.GetObjectClass(jobject)

//获取普通属性id,第一个参数:类对象, 第二个参数:属性名,第三个参数:属性签名

2.GetFieldID(jclass clazz, const char* name, const char* sig)

//设置int属性的值, 第一个参数:this的意思, 第二个参数:获取属性id, 第三个参数:要设置的值

3.SetIntField(jobject obj, jfieldID fieldID, jint value)

当然这里就只列举SetIntField函数了,同理还有很多,比如:SetCharField,SetFloatField,SetObjectField …。有Set函数肯定也会有Get函数,与之对应的就是GetIntField(jobject obj, jfieldID fieldID),这个函数是获取指定属性的值,参数含义同SetIntField函数

//获取静态属性Id, 第一个参数:类对象, 第二个参数: 属性名, 第三个参数: 属性签名

4.GetStaticFieldID(jclass clazz, const char* name, const char* sig)

//设置静态属性的值, 第一个参数: 类对象, 第二个参数: 属性id, 第三个参数: 要设置的值

5.SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)

//获取函数id, 第一参数:类对象, 第二个参数:函数名, 第三个参数: 函数签名

6.GetMethodID(jclass clazz, const char* name, const char* sig)

//调用java中的无返回值函数, 第一个参数: this的意思, 第二个参数: 函数id, 第三个参数:需要传入的实参

7.CallVoidMethod(jobject obj, jmethodID methodID, …)

//获取静态函数id, 第一个参数: 类对象, 第二个参数: 函数名, 第三个参数: 函数签名

8.GetStaticMethodID(jclass clazz, const char* name, const char* sig)

//调用java中无返回值的静态函数, 第一个参数: 类对象, 第二个参数: 函数id, 第三个参数: 需要传入的实参

9.CallStaticVoidMethod(jclass clazz, jmethodID methodID, …)

//生成一个jstring类型的方法转换,该方法会返回一个jstring类型

10.NewStringUTF(const char* bytes)

//调用java中的对象类型(String类型被认为对象类型),第一参数:this的意思, 第二个参数:函数Id, 第三个参数:需要传入的实参

11.CallObjectMethod(jobject, jmethodID, …);

//获取类中的对象属性,第一个参数:this的意思 , 第二个参数:属性id

12.GetObjectField(jobject obj, jfieldID fieldID)

//根据子类的类对象,获取父类的类对象, 第一参数:子类类对象

13.GetSuperclass(jclass clazz)

//调用java中父类的方法,第一个参数:子类的对象, 第二个参数:父类的类对象, 第三个参数:父类的函数id, 第四个参数:需要传入的实参

14.CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, …)

NDK生成so的流程

编译生成so的方式有:ndk-build和cmake,本处只介绍NDK的方式。
在这里插入图片描述
从上图可知,如果只是使用SDK即只可以使用JAVA,如果要调用底层则需要NDK通过JNI来实现。

so生成具体流程如下:

1、在defaultConfig中添加NDK配置代码:

externalNativeBuild {
	   ndk {
	       abiFilters "x86", "armeabi-v7a"	//最终要生成的.so所要支持的架构。
	   }
		externalNativeBuild {
		        ndkBuild {
		            path "src/main/jni/Android.mk"
		        }
		    }
		}

各CPU和ABI之间的关系:

CPU\ABIarmeabiArmeabi-v7aArm64-v8aX86X86_64MipsMips_64
Armeabi支持
Armeabi-v7a支持支持
Arm64-v8a支持支持支持
X86支持
X86_64支持支持
Mips支持
Mips_64支持支持

64位设备(arm64-v8a, x86_64, mips64)能够运行32位的函数库,但是是以32位模式运行,在64位平台上运行32位版本的ART和Android组件,将丢失专为64位优化过的性能(ART,webview,media等等)。
向后兼容(向下兼容):即armeabi-v7a CPU支持armeabi-v7a 和armeabi ABI,兼容老版本。

Android如何加载So库?
每一个CPU架构对应一个ABI目录,会自动到相应目录下进行查找。比如Armeabi-v7a的设备会先找armeabi-v7a目录,如果目录存在但是so不存在则会直接报错。如果目录存在且so也存在,则会适配成功。如果没有armeabi-v7a目录,则会向下查找armeabi目录,过程同上。

2、在jni目录下创建Android.mk

LOCAL_PATH := $(call my-dir)	//设置本地路径,my-dir即包含Android.mk的目录,当前目录

include $(CLEAR_VARS)	//负责清理很多LOCAL_xxx,再进行新的设置前需要先清除

LOCAL_MODULE    := myTest	//生成库的名字libmyTest.so
LOCAL_SRC_FILES := native-lib.cpp	//包含需要编译的源文件

LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog

include $(BUILD_SHARED_LIBRARY)	//生成动态库

3、clean->build后在build\intermediates\ndkBuild下查看

Java调用.so步骤:

1、将so放到jniLibs目录下或者放在libs目录下然后在build.gradle中添加:

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

2、在java中加载库后进行调用相应的接口。

static {
        System.loadLibrary("myTest");
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值