android IPC通信小結

通信方式介紹

Bundle
文件共享
AIDL
Messenger
ContentProvider
Socket


Bundle

適合單向數據傳輸,即進程A啓動進程B的服務或者其他組件時,通過intent.putExtra傳輸。

當服務端是service時,可以在對應的onStartCommand和onBind 方法中訪問傳輸過來的intent,不過要注意,此intent是新的實例,不是原來的實例。


文件共享

通過序列化讀寫文件的方式,共享數據,注意android系統中讀寫是沒有限制,所以文件讀寫需要有一定間隔,且不能處理併發讀寫。

主要使用類爲ObjectOutputStream和ObjectInputStream:

/*
 * @author 龍龜的文具盒
 */

//寫入操作
File file = new FIle(fileUrl);
if(!file.exists()){
    try {
        //創建新的文件
        file.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
try {
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
    //需要實現Serilizable接口,使數據序列化
    //【實現Parcelable接口不行】
    MyData myData = new MyData();
    out.writeObject(myData);
    out.close();
} catch (IOException e) {
    e.printStackTrace();
}

//讀取
//使用ObjectInputStream讀取,跟寫入對稱
...

流數據讀寫如果遇到非基本數據類型,則需要序列化,序列化主要有兩種方式:
實現Serilizable接口
1. 簡單方便,不需要實現其他方法
2. 由java提供,底層靠反射實現
3. I/O操作比較頻繁
4. 適用於存儲設備和網絡傳輸
5. 具有serialVersionUID唯一標識值,決定寫入的文件的序列化版本信息,自己設定可以防止程序隨自己修改bean類而影響對舊版本bean文件流的反序列化

注意點:
1. 靜態成員變量屬於類而不是對象
2. transient標記的成員變量不參與序列化過程
3. 無法在bundle中傳輸

實現Parcelable接口
1. 實現起來較複雜
2. android自帶,優化速度
3. 速度快
4. 適用於內存序列化,即用於bundle傳輸過程中最合適
5. 無法在IO操作中使用

注意:
List和Map序列化的前提是元素都是可序列化

/**
 * 類說明:實現Parcelable的類
 * 創建人:龍龜的文具盒
 */
public class MyData implements Parcelable {


    public MyData(){}

    //構件反序列化的構造函數
    protected MyData(Parcel in) {
        myDataBean = in.readParcelable(MyDataBean.class.getClassLoader());
        name = in.readString();
        id = in.readInt();
    }

    public static final Creator<MyData> CREATOR = new Creator<MyData>() {
        @Override
        public MyData createFromParcel(Parcel in) {
            return new MyData(in);
        }

        @Override
        public MyData[] newArray(int size) {
            return new MyData[size];
        }
    };

    public MyDataBean getMyDataBean() {
        return myDataBean;
    }

    public void setMyDataBean(MyDataBean myDataBean) {
        this.myDataBean = myDataBean;
    }

    private MyDataBean myDataBean;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    private int id;
    //若有文件描述符則需要返回1
    @Override
    public int describeContents() {
        return 0;
    }
    //序列化過程
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(myDataBean, flags);
        dest.writeString(name);
        dest.writeInt(id);
    }
}

AIDL

適用於多客戶端對同一個服務端的併發訪問情景。

實現過程:
1. 創建.aidl文件,客戶端和服務端的路徑必須一致
2. 定義aidl文件內接口,出現非支持類型則必須導包
3. build project後,系統會自動創建相應的interface
4. 在服務端service中實例化自動創建的interface中的Stub類,並且需要實現aidl中定義的接口
5. 通過onBind, 返回步驟四實例化的binder【非主線程】
6. 客戶端通過實例化ServiceConnection【UI主線程】並實現onServiceConnected來獲取服務端的binder對象,並調用 yourInterface.Stub.asInterface來獲得相應的接口,並在適當的時候回調服務端方法【注意回調時儘量避免在UI線程回調】

AIDL支持的類型:
1. 原子類型(int,long,char,boolean)
2. String or CharSequence
3. List (實際爲ArrayList) Map(實際爲HashMap) 元素也必須是可支持的
【因爲一般是服務端一對多,ArrayList並不支持併發讀寫,List的話選擇CopyOnWriteArrayList最合適,但客戶端只能收到ArrayList=。=】
4. 實現Parcelable接口的對象

自定義類型
自己實現的Parcelable,除了要有實現bean.java對象外,還需要在當前包下新建bean.aidl文件,並在裏面定義parcelable對象。再需要引用該自定義類型的interface定義中,也需要import這個對象【注意的是,interface.aidl導入的,其實是從bean.aidl中導入的,不是從bean.java裏】
而且注意對象前需要用 in, out 或者 inout修飾,這是由底層去實現的
in:輸入型參數
out:輸出型參數
inout:輸入輸出型參數
原子類型也是默認使用in

訂閱者模式
實現方式:
1. 爲客戶端創建可以讓服務端回調的aidl接口
2. 在服務器端的回調接口添加register和unregister方法,在service中實現時,採用數組來存儲客戶端listener
【數組應選用RemoteCallbackList,其保存真正的遠程listener,實現靠ArrayMap,key:IBinder,value:Callback, 遍歷能夠找到真正的listener實例,能夠真正unRegister,因爲底層的IBinder是一致的。使用時必須和 beginBroadcast()與finishBroadcast()相匹配】
3. 服務端工作線程可以遍歷list中的listener,而直接調用客戶端的方法
4. 客戶端listener方法中,通過handle交由UI線程處理

權限認證
1. 同一工程中的服務端和客戶端可用onBind
2. 跨工程的服務端和客戶端在服務端onTransact中驗證

       /*
        * @author 龍龜的文具盒
        */
       @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            //驗證權限,客戶端需要在androidmainfest中添加該權限
            int check = checkCallingOrSelfPermission("PERMISSION");
            if(check == PackageManager.PERMISSION_DENIED){
                return false;
            }

            //驗證包名
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if(packages!=null && packages.length>0)
            {
                packageName = packages[0];
            }
            //匹配包名前綴
            if(!packageName.startsWith("com.xxxx")){
                return false;
            }

            return super.onTransact(code, data, reply, flags);
        }

斷開重連
1. onServiceDisconnected中重連,UI線程
2. 給客戶端Binder設置DeathRecipient監聽,非UI線程

ps: 調試時雖然通過killProcess結束了服務端進程,但即使不自己重連,也會自動重連,這還是個梗orz待解決

 //在onServiceConnected中註冊
mInterface.asBinder().linkToDeath(mBinderDeathRecipient,0);
//實例化時,實現binderDied方法,並注意調用unlinkToDeath,異步重連也需要轉化成同步請求bindService
..

Messenger

簡單化的aidl,客戶端與服務端爲一對一關係咯。
操作方式:
1. 服務端onBind中實例一個Messenger,和其依賴的handle,在handle中處理客戶端的信息,onBind中返回messenger.getBinder
2. 客戶端在onServiceConnected中將自己的Messenger作爲replyTo參數放到Message中,再通過new Messenger(IBinder) 的send,把客戶端數據傳送到服務端

注意:
message的參數object在2.2之前不支持跨進程通信,2.2之後只有系統實現的Parcelable接口的對象才能傳輸,所以可以用bundle進行數據傳輸


ContentProvider

與sqlite結合使用,設置contentProvide的process屬性,其就屬於其他進程的


Socket

服務器端開工作線程,監聽socket請求=。=


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章