您的位置:首页 > 移动开发 > Android开发

Android Jni 多线程 蓝牙串口收发 实例 一

2015-11-10 18:02 435 查看
在工作有一个这样的需求:在一个Android App上,通过串口对一个蓝牙进行操作,其中包括发送消息,接收消息,并进行处理。

=========================项目心得和遇上的问题总结=========================

要实现这些功能,有很多种:多线程可以放在Jni层,这样接收和消息的整理逻辑都在Jni层,这样程序就会变得复杂一些,因为你不仅要Java调用C,还要C调用Java。我们也可以把这些逻辑层放在App层处理,Jni层只负责打开串口文件,并fd组织成FildDescriptor返回给App。其实如果按照程序的封装设计,Jni不应该有过多的逻辑处理,逻辑处理都应该交给App,用Java来写,这样的优点在于,相对于C来说,Java比较好写,不用考虑指针和垃圾回收,内存溢出,线程安全等问题也会少很多。最重要的是,把逻辑处理放在App容易提高整个程序的维护性,和Jni层的复用性,只要把Jni打包成库给别人使用即可。但是我们考虑到,C的效率更高,最主要C对字符处理和位的处理更容易。然后就很任性地选择了用C来实现一些杂乱的处理,当然,也是想挑战一下Jni下的编程。这里面也确实遇上了很多问题,这里就做一些简单总结吧。

首先,我们Jni还是用C++写比较好。因为如果你比较懒,在Jni 编程里面提供的接口,一个同名的函数C++比C会少一些传参,具体比较一下jni.h就可发现。然后还有一个问题,在Android系统的Frameworks里会使用Jni会做一些工具,如果我们把我们写好的Jni放进系统里编译就可以很方便地利用这些工具,如用AndroidRuntime 可以很方便地获得Jnv(运行时环境)和JavaVm(当前App的虚拟机)。用C++还有一个优点是,如果你是C++编程高手,你可以很容易用C++写一个架构很好的Jni,如果不是,你也可以当C来用。

但是,用C++来写会带来一个很致命的问题。就是编译好后在Java调用会Jni会提示,无法找到库,和无法找到对应的Jni函数。解决方法是,在源文件和头文件里加入如下语句

在这个功能App的设计中,对串口信息的接收发往上层通知的逻辑是连接整个框架的逻辑,所以,在哪和怎么样接收接收和通知会成为这整个App的关键。然而串口收到的命令时间是不确定性,和收到的多少也是无法确定的,再加上串口接收命令的简单,这给这些线程带来了不少问题。

一开始,我们的线程设计为如下



这样的设计有一个好处就是,可以保证发送和接收的同步进行和想匹配,意思就是说,我发送出去的消息会等待接收到的消息,这样就能保证我发什么就会接收到相应的回复。这时就可以根据回复做出相对应的动作和错误处理。但是这样就有一个问题就是这两个线程的设计,有太多的假设,我们假设SendMsg后会先跑到Wait Ack等接收线程,但有可能时在从SendMsg跑到Wait Ack的过程中,就把时间片让给了子线程,有可能这时候子线程已经收到消息并Ack了,这时主线程就会错失这个消息。还有可能主线程的Lock跑到Handle中,子线程已经从新开始,执行clear
Buffer了。

我们可以通过加多几个信号量来解决这些同步问题。但是这逻辑之间的相互作用就会变得非常多,也会非常杂乱,所以我们就没有往下走,开始想新的方案





在新方案中,我们把线程之间的功能都独立开来,尽量让各个线程之间的关联和Wait更少一点,这样,逻辑就很清晰,各种逻辑问题就会少很多,每个线程我只管把数据丢出去,而不管丢出去后后会如何,在这里我们设计了一个函数包含一个interface让线程调用,interface该做什么动作就让看具体实现了,或许interfack还会把消息丢到别一个线程呢。在这里这样的设计越是到后面,要处理的回复越多,信息回复越得杂的时候越能体现优越性。还有个优点是上层不用阻塞,而是很舒服地被调用,这里就省下很多什么监听啊,阻塞等带来的烦恼。而这种方法有一个很大的缺点就是,我们发送了消息后我们无法立即根据我们发送的消息做发判断处理, 意思是我无法if(
SendMsg() < 0 ){ ..... }。源码如下,线程收到消息并整理好后就会调用notifyAck了

这种方案中有一个问题要非常注意,在上层App中,UI线程和处理线程是分开的,即UI的更新最好不要在处理线程中,逻辑最好不要在UI线程中,如果不这样,有可能会出现一些很奇怪的问题,而不报错。我们就遇上了一个问题:在Jni中 callvoidmethod 不执行,callvoidmethod调用了notifyAck,但上面的Log怎么调都不打印,而编译器就不报错。出现这个问题的原因是callvoidmethod
在jni的线程中调用,而notifyAck里面又实现了UI的更新。所以导致了这个问题发生,而没有报错。这样的问题很头疼。

项目上还有一个问题是比较纠结是,开出来的多线程一定要回收。如果你不回收当你的程序退出后马上再开,接收到的消息会在任意一时候乱入,还有可能打不开,或者直接造成死机。

下面的源码是如果通知一个阻塞的线程退出,原理是用一个管道,然后在Poll数据的地方,同时poll这个管道,如果要退出则在这个管道时写入数据

=========================技术实现难点总结=========================

好了,不多说了。接下来结合源码,看一下一些技术上的问题:

一. Java调用C

这个是通用的写法,网上有很多资料,也可以参考一下之前的文章《Android Jni 基础笔记》。

二. C调用Java

三. Jni多线程

四. Jni多个目录的Android.mk 编译

有两种情况会把源码分为多个目录,一个是多个源码在不同的目录,但是生成的是同一个模块。二是不同的目录下的源码生成一个模块

第一种情况:

这种情况下是要把所有的源文件都加入到Android.mk LOCAL_SRC_FILES这个宏里。

把源码文件加入这个宏可以用几个脚本函数:

以下脚本在alps\build\core\definitions.mk中定义

#找出子目录的所有Java文件

LOCAL_SRC_FILES := $(call all-subdir-java-files)

#找出指定目录的所有Java文件

LOCAL_SRC_FILES := $(call all-java-files-under,src tests)

#同样还有C的脚本函数,可以到definitions.mk查找相应的函数

all-c-files-under

但是definitions.mk并没有cpp的脚本函数那该怎么写呢?

假如我有源码在bt文件夹和当前文件下,写法如下:

第一句话的意思查找出这个路径下所有的cpp文件,得出的结果是$(bt_sources)
的值为:绝对路径+目录下所有的cpp文件

jni/bt/xxxa.cpp jni/bt/xxxb.cpp jni/bt/xxxc.cpp #注意:我们是在apk的源目录下用ndk-build的,jni的源码在jni目录下

第二种情况:

只要在总的Android.mk里include目标目录的Android.mk即可。include $(LOCAL_PATH)/xxxx/Android.mk

目标目录的Android.mk如基础写法。但源码的路径已经变了,要使用如下方法:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: