Android中的JNI技术

简介

JNI,全名 Java Native Interface,Java代表的使java语言,Native代表当前程序运行的本地环境,一般是Windows和Linux,这些操作系统都是通过C/C++实现的,所以Native通常也指C/C++语言,Interface代表java跟native两者之间的通信接口,JNI可以实现java和C/C++通信。

Java虽然跨平台,但仍然运行在具体平台(windows,linux)之上,对于需要操作硬件的功能,必须通过系统的C/C++方法对硬件进行直接操作;在一些拥有复杂算法的场景(音视频编解码,图像绘制等),Java的执行效率远低于C/C++的执行效率。鉴于上述情况,JNI技术就有了发挥的地方,Java通过JNI技术就可以调用C/C++来帮助自己取执行一些它所不擅长的事情。

JNI下一共涉及到三个角色:C/C++代码、本地方法接口类、Java层中具体业务类。

JNI命名方法

JNIExport jstring JNICALL Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz ) 

jstring返回值类型 Java_com_example_hellojni包名 MainActivity类名 stringFromJNI方法名

其中JNIExportJNICALL是不固定保留的关键字不要修改

JNI的使用流程

第1步:在Java中先声明一个native方法

第2步:编译Java源文件javac得到.class文件

第3步:通过javah -jni命令导出JNI的.h头文件

第4步:使用Java需要交互的本地代码,实现在Java中声明的Native方法(如果Java需要与C++交互,那么就用C++实现Java的Native方法。)

第5步:将本地代码编译成动态库(Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib)

第6步:通过Java命令执行Java程序,最终实现Java调用本地代码。

示例代码

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s)
{
     const char *str = (*env)->GetStringUTFChars(env, s, 0); 
     (*env)->ReleaseStringUTFChars(env, s, str); 
     return 10;
}

传入参数分析

*env:一个接口指针
obj:在本地方法中声明的对象引用
i和s:用于传递的参数

关于obj、i和s的类型大家可以参考下面的JNI数据类型,JNI有自己的原始数据类型和数据引用类型如下:

在Android Studio中的安装

在Android SDK中安装NDK和Cmake

安装完成之后,创建工程选择Native C++

创建完成之后,会出现一个cpp文件夹

运行该工程出现如下结果,则标识创建成功

JNI中常用函数

创建Java中的对象
jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, ...)
jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args)
jobject NewObjectV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args)

第一个参数jclass class 代表你要创建哪个类的对象,第二个参数jmethodID methodID代表你要使用那个构造方法ID来创建这个对象。只要有jclass和jmethodID,我们就可以在本地方法创建这个Java类的对象。

创建Java类中的String对象
jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len)

通过Unicode字符的数组来创建一个新的String对象。 env是JNI接口指针;unicodeChars是指向Unicode字符串的指针;len是Unicode字符串的长度。返回值是Java字符串对象,如果无法构造该字符串,则为null。

创建类型为基本类型PrimitiveType的数组
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
指定一个长度然后返回相应的Java基本类型的数组

用于构造一个新的数组对象,类型是原始类型。基本的原始类型如下:

创建类型为elementClass的数组
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);

造一个新的数据组,类型是elementClass,所有类型都被初始化为initialElement。

获取数组中某个位置的元素
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);

返回Object数组的一个元素

获取数组的长度
jsize GetArrayLength(JNIEnv *env, jarray array);

获取array数组的长度

例程

package com.example.as_jni_project;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.example.as_jni_project.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

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

    private String name = "Derry";
    private ActivityMainBinding binding;

    public native String stringFromJNI();   //实现在C++层
    public native void changeName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());

        System.out.println("name修改前是:" + name);
        changeName();
        System.out.println("name修改后是:" + name);
    }
}

上述例程实现的是一个更改变量内容的操作,其中更改内容的函数实在C++层实现的,选中函数名,alt+enter创建该函数的实现,在native-lib.cpp中。

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

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_as_1jni_1project_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
extern "C"  //下面的代码采用C的编译方式,因为JNINativeInterface这个结构体中所使用的C语言比较多
JNIEXPORT void JNICALL //JNIEXPORT标识JNI中的重要关键字,不能少;void表示java中的void;JNICALL也是一个关键字
Java_com_example_as_1jni_1project_MainActivity_changeName(JNIEnv *env, jobject thiz) {
    // TODO: implement changeName()
}

jobject == MainActivity thiz实例调用的 jclass(函数为static的时候,不用jobject) == MainActivity class类调用 如果是native-lib.c的情况,(*env)->xxx函数,因为是二级指针;如果是native-lib.cpp的情况,env->xxx函数,因为是一级指针。

如果是C++
    struct _JNIEnv
    typedef _JNIEnv JNIEnv
    JNIEnv *env
    一级指针
如果是C
	struct _JNIEnv
	typedef const struct JNINativeInterface* JNIEnv
	JNIEnv *env
	二级指针

JNIEnv这个类中,是用来决定C和C++的编译方式。

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

changName()函数如下

extern "C"  //下面的代码采用C的编译方式
JNIEXPORT void JNICALL  //JNIEXPORT标识JNI中的重要关键字,不能少;void表示java中的void;JNICALL也是一个关键字
Java_com_example_as_1jni_1project_MainActivity_changeName(JNIEnv *env, jobject thiz) {
    // TODO: implement changeName()
    //String为引用类型,JNI全部命名为object
    jclass mainActivityCls = env->FindClass("com/example/as_jni_project/MainActivity");

    jfieldID nameFid = env->GetFieldID(mainActivityCls, "name", "Ljava/lang/String;");

    jstring value = env->NewStringUTF("Beyond");

    env->SetObjectField(thiz, nameFid, value);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值