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數據傳輸高