前面篇章Android Framework层和Native层通过原生socket实现通信介绍了使用原生Socket实现Android Framework层和Native层之间的通信方式,这种方式虽然简单,但是效率并不是很高(但是也并不是一无是处,可以实现跨终端跨网络的通信)。所以Android的妈咪谷歌为我们带来了LocalSocket,比Java本身的socket效率要高,没有经过协议栈,是Android自己实现的类似共享内存一样的东东,在传输大量数据的时候就需要用到,比如Rild电话,,在创建应用进程和zygote通信,在应用安装过程中和installd通信等等就不一一枚举了。既然这是Android妈咪推荐使用的,并且在Android项目中也有多处用到的,那么我们有啥理由不来学习一把,撸一发呢。
在正式介绍前,先奉上最终效果演示图,正所谓眼见为实耳听为虚,不放个大招,各位读者咋有一鼓作气看下去的勇气呢。
(1) 服务端 :
root@xxx:/ # ps | grep LocalSocketServer
root 5034 1 3872 936 ffffffff b6ef3328 S /system/bin/LocalSocketServer
λ adb llogcat -s LocalSocket
--------- beginning of main
--------- beginning of system
E/LocalSocket( 5034): android_get_control_socket success
E/LocalSocket( 5034): listen success
E/LocalSocket( 5034): Accept_fd 4
E/LocalSocket( 5034): Waiting for receive
E/LocalSocket( 5034): receive msg from client : hello server
E/LocalSocket( 5034): send to client msg : hello server
E/LocalSocket( 5034): Waiting for receive
(2) Android客户端 :
130|root@xxx:/ # ps | grep com.xxx.android2native
system 5079 345 981160 38088 ffffffff b6dccf58 S com.xxx.android2native
root@xxx:/ #
上面的图示为我们演示了Android应用进程PID号5079 和Native C++进程PID号5034 实现了通信。好了演示效果已经OK了,下面我们一步步的来讲解怎么实现这种通信方式。
配置init.rc脚本,在init.rc里面添加如下native service服务配置:
service local_server /system/bin/LocalSocketServer
class main
socket local_socket_server stream 600 system system
这里配置一个名为"local_server"的服务(注意这里的服务名字符串长度不能超过16,别问我为什么知道,你懂的,),终端在开机后通过解析init.rc然后启动并运行/system/bin目录下的脚本LocalSocketServer,同时这里还配置了一个名为 "local_socket_server " 的socket,并且只有拥有system权限的应用才允许连接这个socket。完成了init.rc的配置,然后就可以编译bootimage,将生成的boot.img烧入终端。
有了前面的的铺垫,大家应该知道得开始Socket通信经典的C/S模式了,不过这里使用的是Android特别版LoclaSocket,在这里我们将C/C++端实现的Native程序作为服务端,Android端作为客户端。下面直接上服务端代码,如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SOCKET_NAME "local_socket_server"
#define LOGE(TAG,...) if(1) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define TAG "LocalSocket"
static void* recever_thread_exe(void *arg)
{
int receive_fd = *(int *)arg;
int numbytes ;
char buff[1024] = {0};
while(1){
//循环等待Socket客户端发来消息
//__android_log_write(ANDROID_LOG_DEBUG,"FTM_JNI","Waiting for receive");
LOGE(TAG,"Waiting for receive\n");
if((numbytes = recv(receive_fd,buff,sizeof(buff),0))==-1){
LOGE(TAG,"recv %d\n", errno);
perror("recv");
continue;
}
LOGE(TAG,"receive msg from client : %s\n", buff);
//发送消息回执给Socket客户端
if(send(receive_fd,buff,strlen(buff),0)==-1)
{
perror("send\n");
LOGE(TAG,"send error\n");
close(receive_fd);
exit(0);
}
LOGE(TAG,"send to client msg : %s\n", buff);
}
close(receive_fd);
return ((void *)0);
}
int main()
{
int max_connect_number = 6;
int listen_fd = -1;
int receive_fd = -1;
int result;
struct sockaddr addr;
socklen_t alen;
alen = sizeof(addr);
//获取init.rc里面配置的名为local_socket_server的socket
listen_fd = android_get_control_socket(SOCKET_NAME);
if(listen_fd < 0)
{
LOGE(TAG,"Failed to get socket '" SOCKET_NAME "' errno:%d\n", errno);
exit(-1);
}
LOGE(TAG,"android_get_control_socket success\n");
//开始监听local_socket_server,并设置最大监听数
result = listen(listen_fd, max_connect_number);
if(result < 0 )
{
perror("listen\n");
LOGE(TAG,"listen error\n");
exit(-1);
}
LOGE(TAG,"listen success\n");
fcntl(listen_fd, F_SETFD, FD_CLOEXEC);
for(;;)
{
//等待Socket客户端发启连接请求
receive_fd= accept(listen_fd, &addr, &alen);
LOGE(TAG,"Accept_fd %d\n",receive_fd);
if (receive_fd < 0 ) {
LOGE(TAG,"%d\n",errno);
perror("accept error");
exit(-1);
}
fcntl(receive_fd, F_SETFD, FD_CLOEXEC);
pthread_t id;
//通过pthread库创建线程
if(pthread_create(&id, NULL, recever_thread_exe, &receive_fd) )
{
LOGE(TAG,"rece thread create fail\n");
}
}
close(listen_fd);
return 0;
}
有了具体的实现代码,还得有相关的编译脚本,编译脚本Android.mk的实现如下:
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS :=optional
LOCAL_C_INCLUDES := $(KERNEL_HEADERS)
LOCAL_SHARED_LIBRARIES := libcutils liblog libutils libicuuc
LOCAL_LDLIBS := -lm -llog
LOCAL_MODULE:= LocalSocketServer
LOCAL_SRC_FILES:= LocalSocketServer.cpp
LOCAL_PRELINK_MODULE := false
include $(BUILD_EXECUTABLE)
编译成功以后,将LocalSocketServer打包进入system.img,或直接push到终端/system/bin里面也是ok的,重启终端然后查看该Native service是否成功运行,并且是否在 /dev/socket 下面生成了我们配置的socket节点。
在前面的章节里面,我们Linux C/C++服务端已经愉快的跑了起来,正在等待着客户端的连接,我们的Android客户端要来连接了,具体核心代码如下:
package com.xxx.android2native;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class LocalSocketClientManager {
private final String SOCKET_NAME = "local_socket_server";
private LocalSocket client;
private LocalSocketAddress address;
private boolean isConnected = false;
private int connetTime = 1;
private static final int HANDMESSAGE = 0;
private static final String TAG = "LocalSocketClientManager";
private ConnectListener mListener;
private static LocalSocketClientManager mLocalSocketClientManager = null;
public interface ConnectListener {
void onReceiveData(String data);
}
public void setOnConnectListener(ConnectListener listener) {
this.mListener = listener;
}
public static LocalSocketClientManager getInstance() {
if (mLocalSocketClientManager == null) {
synchronized (LocalSocketClientManager.class) {
if (mLocalSocketClientManager == null) {
mLocalSocketClientManager = new LocalSocketClientManager();
}
}
}
return mLocalSocketClientManager;
}
public void connectLocalSocketServer() {
client = new LocalSocket();
address = new LocalSocketAddress(SOCKET_NAME,
LocalSocketAddress.Namespace.RESERVED);
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (!isConnected && connetTime <= 10) {
try {
Thread.sleep(1000);
Log.i(TAG, "Try to connect socket;ConnectTime:"
+ connetTime);
client.connect(address);
isConnected = true;
if (client.isConnected()) {
sendMesssage("Connect Server success\n");
}
} catch (Exception e) {
connetTime++;
isConnected = false;
Log.i(TAG, "Connect fail");
}
}
}
}).start();
}
public void send(String data) {
try {
OutputStream outputStream = client.getOutputStream();
outputStream.write(data.getBytes());
InputStream inputStream;
inputStream = client.getInputStream();
if (inputStream == null) {
Log.e("TAG", "inputStream error");
return;
}
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1) {
String recedata = new String(buffer, 0, len);
sendMesssage(recedata);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case HANDMESSAGE:
if (mListener != null) {
mListener.onReceiveData(msg.getData().getString("data"));
}
break;
}
}
};
private void sendMesssage(String msg) {
Message message = Message.obtain();
message.what = HANDMESSAGE;
Bundle bundle = new Bundle();
bundle.putString("data", msg);
message.setData(bundle);
mHandler.sendMessage(message);
}
/**
* 关闭Socket
*/
public void disconnect() {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
如上就是使用LocalSocket实现Android和C/C++通信的全部过程,这种方式在Android中使用的非常普遍,譬如Rild,installd,进程启动通知zygote等等都是使用这种机制的。既然这是Android的妈咪谷所推荐的,那么就一定有它的优点,譬如高效,且通信双方可以进行全双工通信,这个要比Android通用的IPC通信Binder有了可取之处。当然这里并不是说BInder不好,术有专攻吗。所以具体选择那种通信方式得根据具体的业务逻辑,和使用场景来确定了。