2020-12-17

JNI 的使用(callback)

一:定义
JNI是 Java Native Interface的缩写
使用JNI可以使Java代码和其他语言写的代码(如C/C++代码)进行交互。

二:JNI 使用之前的配置(AS3.0以上)

  1. 首先在src/main 文件夹下创建jni文件夹
  2. 创建 .cpp文件Android.mk和Application.mk文件(如果配置JNI需要引入第三方的so库,需要在存放第三方so库的文件夹下创建一个Android.mk文件)
    配置结束如图,其中libnative-radio.so为第三方库。
    在这里插入图片描述libs中的mk文件:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := native-radio
LOCAL_SRC_FILES := libnative-radio.so
include $(PREBUILT_SHARED_LIBRARY)

jni中的mk文件
Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := RadioJni
LOCAL_SRC_FILES := RadioJni.cpp
LOCAL_LDLIBS := -lm -llog
LOCAL_SHARED_LIBRARIES := native-radio
include $(BUILD_SHARED_LIBRARY)
include $(LOCAL_PATH)/libs/Android.mk

Application.mk

APP_MODULES := RadioJni
APP_ABI := armeabi-v7a
APP_PLATFORM := android-14

三:JNI的使用
 1. 在JAVA程序中,首先需要在类中声明所调用的库名称,并且可以不用写出扩展名,系统会自己判定,如下:
  static {
   System.loadLibrary(“RadioJni”);
  }

并且程序入口调用

        mRadioJni = new RadioJni();
        mRadioJni.open(_seekState, _muteState);

2.定义 native 方法
这里是将要调用的方法做本地声明,关键字是 :native

package com.neusoft.optimus.blaster.specific;

import android.util.Log;
import com.neusoft.optimus.blaster.specific.receiver.RadioEventReceiver;

public class RadioJni {
    private static final String cTag = "RadioJni";
    //定义的native方法  open(RadioSeekState state);
    public native boolean open(RadioSeekState state);
    
    public RadioEventReceiver radioEventReceiver;

    public RadioEventReceiver getRadioEventReceiver() {
        return radioEventReceiver;
    }

    public void setRadioEventReceiver(RadioEventReceiver radioEventReceiver) {
        this.radioEventReceiver = radioEventReceiver;
    }

//此方法虽然不是native方法,但是却是实现jni   callback的关键方法,此方法的方法名可任意定义。
    public void seekCallback(RadioSeekState state){
        Log.i(cTag,"seekCallback RadioSeekState:" + state);
        if (state != null){
            if (radioEventReceiver != null){
                radioEventReceiver.registerFreqChangeReceiver(state);
            }

3.使用JAVAH命令生成 .h 文件(也可以自己手写.cpp文件,一步到位),并且实现。
  手写 .cpp时,可以这样写:将声明native方法的类,包名中所有的 . 替换成_,并且在开头加上Java_,结尾加上_native的方法名

#include <jni.h>
#include "Radio.h"
#include <string.h>
#include <dlfcn.h>
#include <cstddef>
#include <stddef.h>

#ifndef _Included_com_neusoft_optimus_blaster_specific_RadioJni
#define _Included_com_neusoft_optimus_blaster_specific_RadioJni

#ifdef __cplusplus
extern "C" {
#endif

#include <android/log.h>
#define LOG_TAG    "RadioJniTAG" // 这个是自定义的LOG的标识
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)

using  namespace xui::adaptapi::radio;

Radio* radio;
static jobject object;
static jobject seekStateObject;
SeekCallback seekCallback;
JavaVM *g_JavaVM;

//Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数,所以在JNI_OnLoad()里面进行一些初始化工作,如注册JNI函数等等。注册本地函数,可以加快java层调用本地函数的效率。
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env = NULL;
    g_JavaVM=vm;
    jint result = -1;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        goto fail;
    }
    if(env == NULL){
        goto fail;
    }
    result = JNI_VERSION_1_4;
    fail:
    return result;
}

void seekResultCallback(RadioSeekState state)  {
    JNIEnv* jniEnv = NULL;
    LOGI("seekResultCallback state frequency %d", state.frequency);
        int status = g_JavaVM->GetEnv((void**)&jniEnv, JNI_VERSION_1_4);
        if(status < 0)
        {
            g_JavaVM->AttachCurrentThread(&jniEnv,NULL);
        }
        jclass RadioJniClass = jniEnv->GetObjectClass(object); //返回对象的类。
        if (RadioJniClass == NULL) {
            LOGI("RadioJniClass == NULL");
            return;
        }
        jmethodID seekCallback = jniEnv->GetMethodID(RadioJniClass, "seekCallback", "(Lcom/neusoft/optimus/blaster/specific/RadioSeekState;)V");   //  第一参数是Java 类对象。第二个参数是参数(或方法名),第三个参数是该参数(或方法)的签名 
        if (seekCallback == NULL) {
            LOGI("seekCallback == NULL");
            return;
        }
        jclass RadioStateClass = jniEnv->GetObjectClass(seekStateObject);//返回对象的类。
        if (RadioStateClass == NULL) {
            LOGI("RadioStateClass == NULL");
            return;
    }
    //下面就是通过jni,将值赋值给java层的代码,实现callback
    jmethodID construction_id = jniEnv->GetMethodID(RadioStateClass,"<init>", "()V");// 构造方法的方法名使用<init>代替
    jobject radioState = jniEnv->NewObject(RadioStateClass, construction_id);// NewObject需要指明调用的构造方法,构建一个新的类对象,并初始化成员变量,调用指定的构造方法
    jfieldID frequency = jniEnv->GetFieldID(RadioStateClass,"frequency","I");// 第一个参数:Java类对象;第二个参数:参数名(或方法名);第三个参数:该参数(或方法)的签名;
    jfieldID seekFrequencyStatus = jniEnv->GetFieldID(RadioStateClass,"seekFrequencyStatus","I");
    jfieldID audioChannelCfg = jniEnv->GetFieldID(RadioStateClass,"audioChannelCfg","I");
    //到下面才是真正的赋值,第一个参数:java中对应类对象 第二个参数:这个是java层级的参数 第三个参数:这个是变化的参数,通过该方法将变化的参数实时设置给java层,实现callback
    jniEnv->SetIntField(radioState, frequency, state.frequency);
    jniEnv->SetIntField(radioState, seekFrequencyStatus, state.isStation);
    jniEnv->SetIntField(radioState, audioChannelCfg, state.audioChannelCfg);
    //这里是callback“调用”java的方法seekCallback,到此结束了jni 的 callback
    jniEnv->CallVoidMethod(object, seekCallback, radioState);
    jniEnv->DeleteLocalRef(RadioJniClass);
    jniEnv->DeleteLocalRef(radioState);
    jniEnv->DeleteLocalRef(RadioStateClass);
}

/*
 * Class:     com_neusoft_optimus_blaster_specific_RadioJni
 * Method:    open
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_com_neusoft_optimus_blaster_specific_RadioJni_open(JNIEnv * env, jobject ob ,jobject seekStateobj){
    object = env->NewGlobalRef(ob);
    seekStateObject = env->NewGlobalRef(seekStateobj); //这里将java层调用 open方法传递的参数进行保存,用于seekResultCallback方法寻找java中类
    seekCallback = seekResultCallback;            //此处是实现callback 的关键,
    radio = Radio::get();
    bool openResult = radio->open(seekCallback);  //这里会执行到c++的代码中
    return (jboolean) openResult;
}

//当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后清除动作。
//在此方法中手动释放不用的对象
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved)
{
    LOGI("JNI_OnUnload");
    JNIEnv* jniEnv = NULL;
    vm->GetEnv((void**)&jniEnv, JNI_VERSION_1_4);
    jniEnv->DeleteGlobalRef(object);
    jniEnv->DeleteGlobalRef(seekStateObject);
    delete radio;
    delete object;
    delete seekStateObject;
}
#ifdef __cplusplus
}
#endif
#endif

4.用到的类

public class RadioSeekState {
    // 搜台时,此频率状态
    public int frequency;          // 频率
    public int seekFrequencyStatus;        // 频率状态
    public int audioChannelCfg;    // 声音通道
    public int band;               // band
public class RadioState {
    public int frequency;          // 频率
}

四:总结
jni的实现最中的就是
1.首先需要在类中声明所调用的库名称
2.定义native方法

第一次写,上面是自己的一些理解,如果有不对的地方,希望大神支出!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值