KarinaCristall 2015-01-20
本人目前参与的一个项目,其Android系统是高度本地化定制的,APP上的数据主要来自本机硬件,这就需要一种机制来实现底层数据与上层APP之间进行交互。最初的想法使用JNI方式封装一个C程序库供APP调用,此种方式的好处就是APP程序相对简单,但坏处很明显:程序之间的耦合很大,未来维护与扩充比较困难,而且APP会显得庞大,运行效率不高。
既然JNI方式坏处这么明显,只能另想办法,比较常用的是采用中间件方式。基本架构就是增加一个中间件,用C实现,运行时为Linux下的一个单独进程,作为APP与底层各硬件模块之间的信使,用来传输与解析(封装)信息。
确定采用中间件方式后,首先要解决进程间通信问题。如果在应用层,APP与APP之间有多种进程间通信方式,主要是Ibinder,还有其它简单易用的,比如Intent、Broadcast、ContentProvider等,但我们要实现的是APP层与底层C进程的通信,所以不能采用应用层这些方式,首先能想到的就是采用Socket方式,虽然Socket主要是网络通信用的,但是在本机内实现进程间通信也是可以的。
现在思路很明显了,采用本机的Socket方式来实现进程间通信,我们知道Socket分为TCP与UDP,既然是本机,当然采用效率高的UDP方式。方案定下来后,就开始写demo进行验证此种方式的可行性与可靠性了。APP作为客户端,中间件作为服务器端,经过测试,发现两台机器之间,APP与中间件能通信,但是在本机内不能通信。测试时将APP装在Android模拟器上,如果中间件也运行于Android模拟器中,则不能通信,如果中间件运行于其它机器上则能进行通信。经过测试发现此种方式不可行(具体原因尚不知,希望有人能指导一下),所以采用SocketUDP方式也被排除。
最后经过研究,Android已经给出了方案,就是LocalSocket方式,此处方式不同于一般的Socket,没有TCP与UDP之分,是专门提供应用程序与Linux层通信的方式,里面封装了Socket的相关接口,使用起来也类似于Socket方式。
最后经过几次测试,终于实现了应用层与Linux层的通信,APP作为客户端,中间件作为服务器端,中间件能接收来自多个APP端的消息。
一、客户端关键代码:
1.生成LocalSocketAddress对象,传入服务名
public static final String SERVICE_NAME = "LocalSocketName"; LocalSocketAddress address = new LocalSocketAddress(SERVICE_NAME,Namespace.RESERVED);注意:因为C程序会作为一个Socket服务随系统启动,此服务有一个服务名,所以要正确连接到服务器端,SERVICE_NAME两边要一致,同时第二个参数一定要为Namespace.RESERVED,否则连接不成功(注意:APP层也可以实现LocalSocket服务器端,主要可以用来测试客户端,如果用APP实现服务器端,测试连接时,第二个参数省略)。
2.使用LocalSocket对象主动连接服务器端
LocalSocket clientSocket = new LocalSocket(); clientSocket.connect(address);
注意:如果产生异常,比如服务不存在或LocalSocketAddress的参数不对,则连接不成功。
3.通过OutputStream发送数据
public static final String CHAR_SET = "GBK"; //字符编码格式 String sendStr = "发送给服务器端"; OutputStream outputStream = clientSocket.getOutputStream(); PrintWriter os = new PrintWriter(new OutputStreamWriter(outputStream,CHAR_SET), false); os.print(sendStr); if ( os.checkError() ) { return false; //发送不成功 } return true; //发送成功
注意:为了使中文不为乱码,应该采用GBK编码;本代码不能在UI线程中
4.通过InputStream接收来自服务器的数据
char[] dataBuf = new char[1024]; //接收数据缓存 InputStream inputStream = clientSocket.getInputStream(); InputStreamReader isr = new InputStreamReader(inputStream,CHAR_SET); int count = isr.read(dataBuf); if (count > 0) { System.out.println(“接收来自服务器:”+new String(dataBuf,0,count)); }
注意:为了使中文不为乱码,应该采用GBK编码;本代码不能在UI线程中
二、服务器端配置:
1.在init.rc加入LocalSocket服务
service myLocalService /system/bin/LocalSocketName socket LocalSocketName stream 666 system system oneshot
三、服务器端关键代码:
1.监听来自客户端的连接
#define SOCKET_NAME "LocalSocketName" ... int fdSockt = -1; int fd = -1; fdSockt = android_get_control_socket(SOCKET_NAME); if (fdSockt >= 0) { int ret = listen(fdListen, 10); if ( ret >=0 ) { struct sockaddr_un addr; socklen_t socketlen = sizeof (addr); fd = accept(fdSockt, (struct sockaddr *) &addr, &socketlen); } }
2.接收来自客户端的消息
int bytenumber ; char receiveBuff[1024]; memset(receiveBuff, 0, 1024); if ( fd >=0 ) { if((bytenumber = recv(fd,receiveBuff,sizeof(receiveBuff),0))>0){ printf("接收消息:%s\n",receiveBuff); } }
3.给客户端返回消息
char sendBuff[1024] = "发送给客户端"; if (fd >=0) { if(send(fd,sendBuff,strlen(sendBuff),0)> 0) { printf("发送消息:%s\n",sendBuff); } }
四、将配置与服务器端源码编译进系统
由于LocalSocket服务需要编译进源码里才能启动,故需要加入到Android源码后编译并刷机,重启系统才能顺利运行。