HAL中通过jni调用java方法的问题

本文介绍了一款特定的车载蓝牙模块goc-md-102,详细讲解了其AT指令集与HAL层的串口通信过程。通过JNI与Java层建立连接,实现了电话号码等信息的有效上报。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明本文出处:http://www.cnblogs.com/xl19862005

作者:Xandy

 

由于工作的需要,最近一直在研究HAL、JNI、java方法之间互调的问题,并做了如下一些记录和大家一起分享!

 

工作背景:所调试的是一款叫goc-md-102的车载蓝牙模块,由于这款蓝牙模块无法直接用HCI的方式控制,而它已经有了现成的一套AT命令集进行控制,所以我在HAL中直接通过串口读写的方式进行通信,然后通过JNI和java层建立联系。

考虑到效率的问题,我在HAL中用回调函数的方式通过JNI与java层交换数据,看了一下GPS数据上报的方法正和我用的这个方法一样!

1、首先是在HAL中串口的开启、初始化和读写,这些都比较简单,主要看看初始化这个函数中的代码,如下:

 

/***************************************************************
** fun: init gocmd102_init(/dev/ttymxc1);
** in:
** out: fd sucess, -1 false;
** gocmd102_init
**************************************************************
*/
static int gocmd102_init(BluetoothCallback *callBack)
{
int fd,var;
btportinfo pPort_info;
int err;

pReceiveCmdPackage = malloc(sizeof(bluetooth));

memset(pReceiveCmdPackage,0,sizeof(bluetooth));
memset(recCmdBuf,0,RECCMDBUFLEN);
//clear message buf
memset(&pPort_info,0,sizeof(btportinfo));

fd=open_bluetoothPort();

if(fd < 0)
{
LOGE("gocmd102_init open port error!");
return -1;
}

pReceiveCmdPackage->fd= fd;
FD = fd;

pPort_info.baud_rate=GOCMD102_BAUD;
pPort_info.data_bits=GOCMD102_DATABIT;
pPort_info.flow_ctrl=GOCMD102_CTRL;
pPort_info.stop_bit=GOCMD102_STOPBIT;
pPort_info.parity=GOCMD102_PARITY;
pPort_info.port_fd=fd;

//pthread_mutex_lock(&pPort_info.portlock);
var = set_btportLocked(&pPort_info);
//pthread_mutex_unlock(&pPort_info.portlock);

if(var < 0)
{
LOGE("set_portLocked error!");
return -1;
}

//在这里将获得输入的函数结构体指针,在后继数据上报的时候将通过这个函数结构体指针来实现
if(callBack != NULL)
pReceiveCmdPackage->callback = *callBack;
else
{
LOGE("BluetoothCallback struct is empty!");
return -1;
}

//uart receive message thread and analyze it
sem_init(&pReceiveCmdPackage->uart_end, 0, 0);
pReceiveCmdPackage->uart_inited = true;

//err = pthread_create(&pReceiveCmdPackage->thread_id, NULL, &BTuartDownloadData, (void *)pReceiveCmdPackage);

  
//在这这里,通过callback的方式创建了一个线程,用于串口数据的读取和上报,这个线程是在VM中创建的一个java线程,一定要用这个,而不能用pthread_create,否则会出问题!
pReceiveCmdPackage->thread_id = callBack->bluetooth_thread("gocmd102_bluetooth", BTuartDownloadData, pReceiveCmdPackage);

if (!pReceiveCmdPackage->thread_id)
{
LOGE("could not create bluetooth thread: %s", strerror(errno));
return -1;
}

return fd;
}

 

当蓝牙打开时,上层app通过JNI调用到这个init函数完成串的初始,同时将JNI中调用java方法的函数结构体地址传入了进来,这个函数结构体如下:

typedef struct
{
size_t size;
void (*callIn_bt)(telephoneIn *callIn);
void (*state_bt)(int state);
void (*getVol_bt)(BtVol *vol);
void (*connect_bt)(matchDev *btDevice);
void (*match_bt)(matchDev *btDevice);
void (*downPHBook_bt)(phoneNumber *phoneNum);
void (*callOut_bt)(phoneNumber *dailNum);
pthread_t (* bluetooth_thread)(const char* name, void (*start)(void *), void* arg);
}BluetoothCallback,*pBluetoothCallback;

这个函数结构体里的回调函数在JNI中实现,我们来看看电话打入的时候上报电话号码的这个回调函数:

static void telephoneIn_callback(telephoneIn *callIn)
{
JNIEnv* env = AndroidRuntime::getJNIEnv();
jstring number = env->NewStringUTF(callIn->number);

dbg(DBG_DEBUG," JNI telephoneIn_callback");
//调用java方法上报数据
env->CallVoidMethod(mBTCallbackObj,method_reportCallIn,callIn->Len,number);

if(number)
env->DeleteLocalRef(number);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}

这里mBTCallbackObj(jobject)是在java调用jni初始化的时候赋值的,应该是获得对应的java类,而method_reportCallIn(jmethodID)是获得的java中对应的java方法ID,如下:

static void android_location_BlueToothLocationProvider_class_init_native(JNIEnv* env, jclass clazz) 
{
method_reportCallIn = env->GetMethodID(clazz, "telephoneCallIn", "(ILjava/lang/String;)V");
method_reportState = env->GetMethodID(clazz, "bluetoothState", "(I)V");
method_reportVol = env->GetMethodID(clazz, "reportVol", "(II)V");
method_reportConnect = env->GetMethodID(clazz,"reportConnect","(Ljava/lang/String;[I)V");
method_reportMatch = env->GetMethodID(clazz,"reportMatch","(ILjava/lang/String;[I)V");
method_reportPhoneBook = env->GetMethodID(clazz,"reportPhoneBook","(IILjava/lang/String;Ljava/lang/String;)V");
method_reportDailNum = env->GetMethodID(clazz,"reportDailNumber","(Ljava/lang/String;)V");
}

再来看看jni中创建java线程的回调函数:

static pthread_t bluetooth_thread_callback(const char* name, void (*start)(void *), void* arg)
{
return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
}


在相应的java类中电话打入的时候,号码上报的方法如下:

    /**
* called from native code to update call in telephone number
*/
private void telephoneCallIn(int numberLen, String number)
{
if(DEBUG)
Log.v(TAG, "telephoneCallIn number: " + number);

if(numberLen <= 0)
Log.e(TAG,"telpphone call in,but the phone number is null");

if(number != null)
{
// send an intent to notify there is a telephone call in.
Intent intent = new Intent(TELEPHONE_CALLIN_ACTION);
intent.putExtra(EXTRA_BT_PHONENUMBER, number);
mContext.sendBroadcast(intent);
}

}

最后再来看看HAL中是如何通过这个回调函数上报数据的,当解析得到电话打入时,将会进入到如下黄色标注的这部分代码上报打入的电话号码:

static void processCharacterI(pBluetooth bt,const uuint8 *data)
{
const uuint8 *pdata = data;

dbg(DBG_DEBUG,"processCharacterI : %s",pdata);

switch(*pdata)
{
case 'D':
{
telephoneIn callIn;

memset(&callIn,0,sizeof(telephoneIn));

callIn.Len = (*(++pdata)-0x30)*10;
callIn.Len += *(++pdata)-0x30;
callIn.number = ++pdata;
BLUETOOTH_CALLIN_CB(bt->callback,callIn);
}
break;

case 'S':
BLUETOOTH_STATE_CB(bt->callback,uartInitOK);
break;

case 'C':
{
phoneNumber CallOut;
uuint32 tmp=0;

memset(&CallOut,0,sizeof(phoneNumber));

tmp = (*(++pdata)-0x30)*10;
tmp += *(++pdata)-0x30;
CallOut.nameLen = tmp;

CallOut.number = ++pdata;

BLUETOOTH_CALLOUT_CB(bt->callback,CallOut);
}
break;

default:
LOGW(" Unknow command : I%s",pdata);
break;
}
}

其中BLUETOOTH_CALLIN_CB这个是如下定义的:

#define BLUETOOTH_CALLIN_CB(_cb,_in)    \
if((_cb).callIn_bt){ \
(_cb).callIn_bt(&(_in)); \
}

这样完整的数据上报链路就已经打通了,当有相应的数据从串口发送出来时就会通过这套回调函数将数据上报至java方法中,最后在java方法中通过广播发给对应的监听者!







 

转载于:https://www.cnblogs.com/xl19862005/archive/2012/03/10/2388800.html

<think>我们已经在之前的回答中讨论了如何在JNI调用带有三个参数的Java回调函数,但用户现在特别强调参数是三个int类型,并且需要在Java线程中回调。我们需要调整之前的示例,将参数类型改为三个int,并确保线程安全。步骤:1.定义Java回调函数:接受三个int参数。2.在JNI中获取方法ID(注意方法签名变化)。3.在Native线程中附加到JVM,然后调用回调函数,传递三个int参数。注意:由于参数都是基本类型(int),处理起来比对象类型更简单,不需要创建局部引用,也不需要进行额外的内存管理(除了方法调用本身)。具体实现:一、Java层定义:假设我们有一个类包含回调方法:```javapublicclassCallbackHelper{//回调方法,接受三个int参数publicvoidonIntCallback(inta,intb,intc){System.out.println("Callback:"+a+","+b+","+c);}}```二、JNI初始化(保存全局引用和方法ID):```c#include<jni.h>JavaVM*gJavaVM=nullptr;jobjectgCallbackObj=nullptr;//全局引用,指向CallbackHelper实例jmethodIDgOnIntCallbackMethodID=nullptr;JNIEXPORTjintJNICALLJNI_OnLoad(JavaVM*vm,void*reserved){gJavaVM=vm;JNIEnv*env;if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){returnJNI_ERR;}//获取CallbackHelper类jclassclazz=env->FindClass("com/example/CallbackHelper");if(clazz==nullptr){returnJNI_ERR;}//获取方法ID,方法签名:(III)V(三个int,返回void)gOnIntCallbackMethodID=env->GetMethodID(clazz,"onIntCallback","(III)V");if(gOnIntCallbackMethodID==nullptr){returnJNI_ERR;}//创建CallbackHelper实例并创建全局引用jmethodIDconstructor=env->GetMethodID(clazz,"<init>","()V");jobjectlocalObj=env->NewObject(clazz,constructor);gCallbackObj=env->NewGlobalRef(localObj);env->DeleteLocalRef(localObj);returnJNI_VERSION_1_6;}//在JNI_OnUnload中释放全局引用JNIEXPORTvoidJNICALLJNI_OnUnload(JavaVM*vm,void*reserved){JNIEnv*env;if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){return;}env->DeleteGlobalRef(gCallbackObj);gCallbackObj=nullptr;}```三、在Native线程中调用回调函数(三个int参数):```c#include<pthread.h>void*native_thread_function(void*arg){JNIEnv*env;//将当前线程附加到JVMif(gJavaVM->AttachCurrentThread(&env,nullptr)!=JNI_OK){//处理错误returnnullptr;}//调用回调方法,传递三个int参数env->CallVoidMethod(gCallbackObj,gOnIntCallbackMethodID,1,2,3);//检查异常if(env->ExceptionCheck()){env->ExceptionDescribe();//打印异常信息env->ExceptionClear();//清除异常}//分离线程gJavaVM->DetachCurrentThread();returnnullptr;}//启动线程voidstart_thread(){pthread_tthread;pthread_create(&thread,nullptr,native_thread_function,nullptr);}```注意点:-方法签名:三个int参数的方法签名为`(III)V`。-参数传递:直接传递jint(在C/C++中,jint就是int的typedef,所以可以直接用整数值)。-异常处理:在调用后检查异常,避免因Java层异常导致Native崩溃。如果用户希望在Java层创建线程(即在Java线程中调用Native方法,然后Native方法回调Java方法,且回调也在同一个线程),则不需要附加线程(因为已经附加了)。但这里的情况是Native自己启动的线程(非Java线程),所以需要附加。用户问题中提到了“Java线程回调函数”,但根据上下文,应该是指从Native线程回调Java方法,并且该方法需要三个int参数。因此,上述代码满足要求。另外,用户提供的引用[1]中提到了`IsInstanceOf`函数,但在这个场景中并不需要,因为我们已经在初始化时创建了CallbackHelper的实例,并且调用的是它的方法。如果用户需要动态检查对象的类型,可以使用该函数,但这里不是必须的。引用[2][3][4]主要涉及Java线程池、锁、可见性、线程阻塞工具以及AndroidHAL层回调,与当前问题关联不大,但我们可以从中获取一些多线程安全的知识。在JNI多线程回调中,我们同样需要注意线程安全,比如确保全局引用的使用是安全的(没有多个线程同时修改同一个全局引用),但这里我们只使用了一个全局引用,并且在初始化后只读,所以是安全的。如果用户希望回调发生在特定的Java线程(如UI线程),则需要在Java层使用Handler等机制,但这超出了当前问题的范围。因此,我们给出了针对三个int参数的回调实现。生成相关问题:1.在JNI中如何传递多个基本类型参数给Java方法?2.JNI多线程回调中如何避免全局引用的竞争条件?3.如何在JNI回调中处理Java层的异常?</think>### 在JNI调用带三个int参数的Java线程回调函数 #### 关键步骤实现 1. **Java层定义回调函数** ```java public class CallbackHandler { // 带三个int参数的回调函数 public void onThreadCallback(int param1, int param2, int param3) { System.out.printf("Callback: %d, %d, %d\n", param1, param2, param3); } } ``` 2. **JNI层初始化全局引用** ```cpp JavaVM* gJavaVM = nullptr; jobject gCallbackObj = nullptr; // 全局回调对象引用 jmethodID gCallbackMethodID = nullptr; // 回调方法ID JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { gJavaVM = vm; JNIEnv* env; if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // 获取回调类和方法ID(签名:(III)V 表示三个int参数) jclass clazz = env->FindClass("com/example/CallbackHandler"); gCallbackMethodID = env->GetMethodID(clazz, "onThreadCallback", "(III)V"); // 创建全局引用 jobject localObj = env->AllocObject(clazz); gCallbackObj = env->NewGlobalRef(localObj); env->DeleteLocalRef(localObj); return JNI_VERSION_1_6; } ``` 3. **Native线程中触发回调** ```cpp void* native_thread_func(void* args) { JNIEnv* env; gJavaVM->AttachCurrentThread(&env, nullptr); // 准备三个int参数 int param1 = 100; int param2 = 200; int param3 = 300; // 调用Java回调(三个int参数) env->CallVoidMethod( gCallbackObj, gCallbackMethodID, static_cast<jint>(param1), static_cast<jint>(param2), static_cast<jint>(param3) ); // 异常检查 if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } gJavaVM->DetachCurrentThread(); return nullptr; } // 启动线程 void trigger_callback() { pthread_t thread; pthread_create(&thread, nullptr, native_thread_func, nullptr); } ``` #### 关键注意事项 1. **方法签名** - 三个int参数的签名格式:`(III)V` - 基本类型签名对照: - `I` → int - `Z` → boolean - `F` → float - `J` → long 2. **线程安全** - 每个Native线程必须通过`AttachCurrentThread`获取独立的JNIEnv[^1] - 结束时必须调用`DetachCurrentThread`防止内存泄漏 3. **参数传递** - 基本类型直接转换:`static_cast<jint>(value)` - 对象类型需要特殊处理(如`NewStringUTF`创建jstring) 4. **错误处理** - 调用后检查异常:`env->ExceptionCheck()` - 及时清除异常:`env->ExceptionClear()` #### 工作流程 ```mermaid sequenceDiagram participant Java as Java线程 participant JNI as JNI层 participant Native as Native线程 Java->>JNI: 初始化全局引用 activate JNI JNI-->>Java: 存储方法ID和对象 deactivate JNI Native->>JNI: 创建Native线程 activate Native Native->>JNI: AttachCurrentThread() JNI-->>Native: 返回JNIEnv Native->>Java: CallVoidMethod(三个int参数) activate Java Java-->>Native: 执行回调逻辑 deactivate Java Native->>JNI: DetachCurrentThread() deactivate Native ``` #### 性能优化建议 1. **缓存方法ID** 在`JNI_OnLoad`中缓存方法ID,避免每次调用查找 2. **使用局部引用池** 当需要创建临时对象时: ```cpp env->PushLocalFrame(10); // 创建局部引用池 // 创建临时对象... env->PopLocalFrame(nullptr); // 自动释放所有局部引用 ``` 3. **异步回调队列** 对于高频回调,建议在Native层实现任务队列,通过单一线程触发Java回调 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值