Android中的进程通信主要有以下种
1.广播
广播是比较常用的进程间通信的方式,而且实现起来也比较简单
缺点:某些手机对于静态注册的广播的发送和接收延迟非常严重,例如一些配置很低的手机,华硕,联想的部分低配手机就对静态注册的广播有较长的延迟,我曾经遇到过的有延迟1到2分钟的;广播的数据传输是有限的,如果传输的数据量太大会报错
2.AIDL
AIDL是用在Service和外部进程之间的通信,如果是Service单独进程和App主进程进行通信的话可以考虑采用AIDL来实现;
第一步:定义AIDL接口
package com.xtc.sync;
import com.xtc.sync.ICloseCallback;
// Declare any non-default types here with import statements
interface IConnectionService {
/**
* 连接IM服务器
*/
int connect(String hostname, int port);
/**
* 发送数据
*/
int send(in byte[] data);
/**
* 心跳包
*/
void heartbeat();
/**
* 关闭TCP连接并停止服务
*/
void close();
void closeForBack(ICloseCallback icloseCallback);
/**
*连接是否关闭
*/
boolean isClose();
/**
*把服务器ip和端口信息传进来
*/
void initServerInfo(in String[] serverInfo);
/**
*切换连接主机
*/
void switchHost();
}
有些方法参数前面有“in”修饰,例如send(in byte[] data),这便是是外部调用send()接口,然后传入data的字节数组,加入是想从AIDL绑定的Service中回调获取数据的话,例如onReceive(in byte[] data)
,也要加一个“in”修饰,而如果参数类型是简单数据类型:String,int等就不需要,在用Androidstudio建aidl文件的时候会有默认提示:
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
表示int,long,boolean,float,double,String就可以直接传递,不需要加修饰符,当然double部数据简单数据类型
缺点:AIDL接口不够灵活,如果要传复杂的数据对象很麻烦,要定义对应的AIDL接口,而且在两个进程之中都要定义才行,更重要的是AIDL接口不稳定,因为Service有可能被系统杀死,这时候Service就是停止状态,然而要是这时候程序调用AIDL接口就会报DeadObjectException异常;会报空指针错误:
/**
* 发送数据
*/
@Override public int send(byte[] data) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(data);
mRemote.transact(Stub.TRANSACTION_send, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
这段代码是前面定义的send()的AIDL接口编译后自动生成的一个类,有时候就会报__reply.readException()是NullPointerExeception,解决方案:
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
try {
return super.onTransact(code, data, reply, flags);
} catch(RuntimeException e) {
SyncLogUtil.e("AIDL接口异常,终于捕获到了");
SyncLogUtil.e(e);
throw e;
}
}
}
捕获aidl异常,然后查看log分析到底是那一句报错,aidl接口定义好了之后就在service里面建一个内部累,集成aidl的Sub:
private class ConnectionBinder extends IConnectionService.Stub {
@Override
public int connect(String hostname, int port) throws RemoteException {
return ConnectionService.this.connect(hostname, port, true);
}
@Override
public int send(byte[] data) throws RemoteException {
return ConnectionService.this.send(data);
}
@Override
public void heartbeat() throws RemoteException {
ConnectionService.this.heartbeat();
}
@Override
public void close() throws RemoteException {
ConnectionService.this.close();
}
@Override
public void closeForBack(ICloseCallback icloseCallback) throws RemoteException {
ConnectionService.this.close(icloseCallback);
}
@Override
public boolean isClose() throws RemoteException {
return ConnectionService.this.isClose();
}
@Override
public void initServerInfo(String[] serverInfo) throws RemoteException {
ConnectionService.this.initServerInfo(serverInfo);
}
@Override
public void switchHost() throws RemoteException {
ConnectionService.this.switchHost();
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
try {
return super.onTransact(code, data, reply, flags);
} catch(RuntimeException e) {
SyncLogUtil.e("AIDL接口异常,终于捕获到了");
SyncLogUtil.e(e);
throw e;
}
}
}
这个就是提供给ingyige进程调用的远程接口了,而且在onBind()方法里面要反悔这个Binder:
@Override
public IBinder onBind(Intent intent) {
SyncLogUtil.d("TCPConnection Service has binded.");
bind = true;
return new ConnectionBinder();
}
然后在另一个进程实现ServiceConnection中的两个方法:
public final class IConnServiceConnection implements ServiceConnection {
private String[] serverInfo;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
SyncLogUtil.d(name.getPackageName() + ":connect to service successfully.");
connectionService = IConnectionService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
connectionService 对象才是真正可以调用刚才定义的哪些aidl接口的对象,它就相当于外部进程和service之际嗯的桥梁,最后在startService,bindService就可以正常调用aidl接口了
3.LocalSocket和LocalServerSocket
这两个可实现和c进程的通信,比较灵活,因为数据传输是流式传输,用法和Socket,ServerSocket比较像,但是内部实现原理和SocketServerSocket就差很多了,虽然是好用,但是貌似调用close()方法的时候并不能像Socket那样直接就抛出异常,例如:
private void startLocalClient() throws IOException {
localSocket = new LocalSocket();
TLVByteBuffer buffer = new TLVByteBuffer();
byte[] receiveBuf = new byte[512];
int readLength = -1;
localSocket.connect(new LocalSocketAddress("com.xtc.watch.local"));
SyncLogUtil.i("LocalPush", "connect to local server success");
inputStream = localSocket.getInputStream();
outputStream = localSocket.getOutputStream();
while ((readLength = inputStream.read(receiveBuf)) != -1) {
buffer.write(receiveBuf, 0, readLength);
SyncLogUtil.i("LocalPush", "local client read [" + readLength + "] bytes,all buffer bytes:" + buffer.size());
while (buffer.hasNextTLVData()) {
byte[] data = buffer.cutNextTLVData();
if (data != null && data.length > 0) {
onRead(data);
} else {
SyncLogUtil.e("LocalPush", "local client data read completely,but cutted tlv bytes is null or length = 0.");
}
}
}
SyncLogUtil.w("LocalPush", "local connect client readLength:" + readLength);
buffer.reset();
}
这里客户端的localSocket如果调用close()方法或者调用localSocket.getInputStream().close()程序并不会抛出IOExeception,而是继续堵在read()方法,这样就会造成线程一直阻塞,线程isAlive()返回的是true,线程资源垡释放,LocalServerSocket也是同样的问题,我尝试在调用close()方法后不断的调用localSocket和localServerSocketwrite数据,这样线程就能正常抛出异常然后结束,而且在LocalServerSocket初始化后会绑定到一个地址,调用close()之后并不会释放这个地址,此时再次绑定这个地址会报address already used,但是在调用了LocalServerSocket的close()方法之后再次连接一个LocalSocket进来,是可以正常连上的,不过此时LocalServerSocket已经把绑定的地址释放了,而且也不能喜剧读取和写入数据了,这时候再次初始化一个LocalServerSocket丙丁岛相同的地址就能绑定成功,但是你要是new一个LocalSocket,写完数据后立即关闭,那这时候客户端的就不会造成阻塞,会正常抛出IO异常,线程结束,关于LocalSocket和LocalServerSocket的正确用法还要继续研究,主要难点就是当读取等待的线程不用了,怎么才能成功close掉,这样才能成功释放线程资源,待补充。。。
直接用本地的TCP:Socket和ServerSocket
socket实际上是用于网络通信的套接字,但是如果你把手机当作服务器,那么实际上进程间的通信也可以采用网络套接字来实现,因为Socket的通信的基于网络的通信,不存在什么进程概念,这里只需要获取手机本地的IP,然后创建ServerSocket,再用Socket连接上即可,跟网络通信的Socket使用方式一模一样,而且这条TCP连接还可实现进程间的互相监听,当有一方进程挂掉的时候,TCP连接久断开了,另一个进程能及时的受到反馈,这个就比LoccalSocket的反馈及时很多,LocalSocket其实现原理和Socket大不一样,后面有时间再做补充。另外,微信目前对于进程间的通信采用的是AIDL来实现,但是据我在一个课程上了解他们认为Socket来实现进程间的通信是非常好的,可值得尝试的做法,他们今后也会在这一个方面做尝试
直接用本地UDP
因为本地UDP数据传输并不用ping到网络上,都是本地发包与收包,因此丢包的概率大大降低了,而且效率也比TCP数据传输高