Android实现本机应用层与linux程序通信

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源码后编译并刷机,重启系统才能顺利运行。

相关推荐