这段时间负责公司一款车载检测产品的平台开发和维护工作。这款产品的工作模式是通过手机端的应用与插在车上的车机设备通信,来实现一些功能。手机端代码实现上是将公司的C代码的应用移植到手机应用上,以动态链接库的方式,通过JNI层与java层交互。
产品研发和测试已经接近尾声,但是应用在测试中还是发现存在极个别的情况下接收数据失败的问题。通过几天的查找,已经完全排除了接头端的问题,也排除了C应用端的问题,那么主要问题就可能出在JNI层了,通过在jni层中加log,发现确实是存在问题,具体问题如下:
1、java通过jni层存储到消息队列中的消息,应用层通过jni层获取不到或获取不完整,一般是掉了最后一帧。
查找一圈,发现问题的根本如下:
//之前的代码
void QUEUE_MSG::getSendMsg(MSGDLLTOEXE *msg) {
if (send_msg.msg.empty()) {
//重置返回一个无效消息
msg->msgtype = 0;
msg->action_tp = 0;
msg->ui_act = 0;
msg->db_act = 0;
msg->cal_act = 0;
msg->cmd_rlen = 0;
} else {
*msg = send_msg.msg.front(); //获取消息队列中最前面的消息
send_msg.msg.pop(); // 删除消息队列中最前面的消息
}
我的理解是:这种将队列顶部的消息取出之后再删除队列中消息的操作,一般是没有什么问题的,但是如果消息队列中的指针变量指向了一块数据区域,在pop()的动作做出之后,连带着对已取出的消息造成的影响。导致已取出的消息是空消息,后续将代码修改如下之后,问题得到解决:
//之后的代码
void QUEUE_MSG::getReceiveMsg(MSGDLLTOEXE *msg) {
if(msg->msgtype == 300) {
while(receive_msg_cmd_lock == 1)
{
Delay(1);
}
receive_msg_cmd_lock = 1;
if (receive_msg_cmd.msg.empty()) {
//重置返回一个无效消息
msg->msgtype = 0;
msg->action_tp = 0;
msg->ui_act = 0;
msg->db_act = 0;
msg->cal_act = 0;
msg->cmd_rlen = 0;
} else {
if(firstQueueDataFlag == 1)
{
int size = getReceiveMsgCount();
if(size > 1) {
receive_msg_cmd.msg.pop(); // 将消息队列中最前面删除上一次的消息
*msg = receive_msg_cmd.msg.front();//将消息队列中最前面的消息给到msg
}
else
{
msg->msgtype = 0;
msg->action_tp = 0;
msg->ui_act = 0;
msg->db_act = 0;
msg->cal_act = 0;
msg->cmd_rlen = 0;
}
}
else
{
int size = getReceiveMsgCount();
firstQueueDataFlag = 1;
*msg = receive_msg_cmd.msg.front(); //将消息队列中最前面的消息给到msg
}
}
receive_msg_cmd_lock = 0;
}
else
{
if (receive_msg.msg.empty()) {
//重置返回一个无效消息
msg->msgtype = 0;
msg->action_tp = 0;
msg->ui_act = 0;
msg->db_act = 0;
msg->cal_act = 0;
msg->cmd_rlen = 0;
} else {
*msg = receive_msg.msg.front(); //将消息队列中最前面的消息给到msg
receive_msg.msg.pop(); // 将消息队列中最前面的消息删除
}
}
}
这种写法的思想是将删除消息队列的操作,从这一次取出消息之后,改到下一次取出消息之前,这就保证了每一个消息都经过了应用的处理之后才走入到删除的流程。
2、应用层通过jni层获取到的消息队列的数据,是前几次交互过的数据。
出现这个问题,让我感叹到,对于共享资源,锁的应用真的是无处不在,不用锁,隐藏的问题真的是好痛苦。
这其实就是一个消息队列共享资源未加锁的问题导致的。java线程和应用线程都在操作同一个消息队列,一个在写,一个在存,在java还未写完的时候,应用就检测到有新的消息来了,去队列里面取,结果取到的消息,消息中的指针还指向前几次指向的数据区域,就导致了这个问题。
代码如下:
//之前的代码
void QUEUE_MSG::getSendMsg(MSGDLLTOEXE *msg) {
if (send_msg.msg.empty()) {
//重置返回一个无效消息
msg->msgtype = 0;
msg->action_tp = 0;
msg->ui_act = 0;
msg->db_act = 0;
msg->cal_act = 0;
msg->cmd_rlen = 0;
} else {
*msg = send_msg.msg.front();
send_msg.msg.pop();
}
}
void QUEUE_MSG::setSendMsg(MSGDLLTOEXE msg) {
send_msg.msg.push(msg);
}
//之后的代码
static int receive_msg_cmd_lock = 0;
static int send_msg_cmd_lock = 0;
void QUEUE_MSG::getReceiveMsg(MSGDLLTOEXE *msg) {
if(msg->msgtype == 300) {
while(receive_msg_cmd_lock == 1) //检测资源是否被锁
{
Delay(1);
}
receive_msg_cmd_lock = 1; //锁住该资源
if (receive_msg_cmd.msg.empty()) {
//重置返回一个无效消息
msg->msgtype = 0;
msg->action_tp = 0;
msg->ui_act = 0;
msg->db_act = 0;
msg->cal_act = 0;
msg->cmd_rlen = 0;
} else {
if(firstQueueDataFlag == 1)
{
int size = getReceiveMsgCount();
if(size > 1) {
receive_msg_cmd.msg.pop(); // 将消息队列中最前面删除上一次的消息
//Delay(1);
*msg = receive_msg_cmd.msg.front();//将消息队列中最前面的消息给到msg
}
else
{
msg->msgtype = 0;
msg->action_tp = 0;
msg->ui_act = 0;
msg->db_act = 0;
msg->cal_act = 0;
msg->cmd_rlen = 0;
}
}
else
{
int size = getReceiveMsgCount();
firstQueueDataFlag = 1;
*msg = receive_msg_cmd.msg.front();//将消息队列中最前面的消息给到msg
}
}
receive_msg_cmd_lock = 0; //解锁该资源
}
else
{
if (receive_msg.msg.empty()) {
//重置返回一个无效消息
msg->msgtype = 0;
msg->action_tp = 0;
msg->ui_act = 0;
msg->db_act = 0;
msg->cal_act = 0;
msg->cmd_rlen = 0;
} else {
*msg = receive_msg.msg.front(); //将消息队列中最前面的消息给到msg
receive_msg.msg.pop(); // 将消息队列中最前面的消息删除
}
}
}
void QUEUE_MSG::setReceiveMsg(MSGDLLTOEXE msg) {
if(msg.msgtype == 300) {
while(receive_msg_cmd_lock == 1) //检测资源是否被锁
{
Delay(1);
}
receive_msg_cmd_lock = 1; //锁住该资源
receive_msg_cmd.msg.push(msg);
LOGW( "setReceiveMsg id = %d, data = %s", msg.msgid, HextoASCII(msg.cmd_rlen,msg.ans_data));
receive_msg_cmd_lock = 0; //解锁该资源
}
else
{
receive_msg.msg.push(msg);
}
}