Android中的進程之間的通信

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

發佈了52 篇原創文章 · 獲贊 35 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章