通信方式介紹
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請求=。=