前言:IPC(inter-Process-Communication)進(jìn)程間通信与学,用于兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交互的過程尼摹,任何操作系統(tǒng)都有IPC機(jī)制尘喝,但不同的操作系統(tǒng)有著不同的通信方式询一,Android系統(tǒng)是一種基于Linux內(nèi)核的移動(dòng)操作系統(tǒng)悠夯,Linux的跨進(jìn)程通信主要通過管道寸谜、共享內(nèi)存竟稳、內(nèi)存映射等實(shí)現(xiàn)的,但Android有自己的進(jìn)程間通信機(jī)制熊痴,最具代表性的就是Binder他爸。
本文篇幅雖長,但可以復(fù)習(xí)到的知識(shí)卻有很多果善,通過閱讀你能了解什么是IPC機(jī)制(Linux&Android)诊笤、多進(jìn)程模式的用法、跨進(jìn)城通訊的方式巾陕、序列化反序列化讨跟、Binder的作用、Bundle的使用鄙煤、ContentProvider晾匠、Messenger、Socket等知識(shí)點(diǎn)馆类。
尊重原著混聊,參考資料如下:任志強(qiáng)《Android開發(fā)藝術(shù)探索》、Serializable和Parcelable乾巧、ServiceManager句喜、AIDL的使用詳解、AIDL的工作原理沟于、ContentProvider咳胃、Binder原理大量參考寫給 Android 應(yīng)用工程師的 Binder 原理剖析,寫的很好請(qǐng)去支持旷太!
碼字不易展懈,耗時(shí)三天學(xué)習(xí)和理解最后歸納出了這篇文章销睁,轉(zhuǎn)載請(qǐng)標(biāo)明出處。
簡單介紹一下IPC機(jī)制
- 含義:進(jìn)程間通信存崖、跨進(jìn)程通信冻记,進(jìn)程是資源分配的最小單位,操作系統(tǒng)的一個(gè)執(zhí)行單元来惧,即一個(gè)程序或者應(yīng)用冗栗。一個(gè)進(jìn)程至少包含一個(gè)線程,線程是CPU調(diào)度的最小單位供搀,是一種有限的系統(tǒng)資源隅居。
- 方式:Binder、AIDL葛虐、Socket胎源、文件共享、ContentProvide等屿脐。
- 場景:多進(jìn)程涕蚤、一種是一個(gè)應(yīng)用由于特殊原因需要多進(jìn)程,特殊原因比如模塊需要運(yùn)行在單獨(dú)的進(jìn)程中的诵,或者應(yīng)用為了獲取更大的內(nèi)存空間(系統(tǒng)給單個(gè)應(yīng)用分配的空間有限赞季,不同手機(jī)設(shè)備大小不一,但相差不大)奢驯。另一種是當(dāng)前應(yīng)用需要向其他應(yīng)用獲取數(shù)據(jù)。
Android中的多進(jìn)程模式
指的是指定應(yīng)用內(nèi)部開啟多進(jìn)程模式次绘,只有一中方法瘪阁,給四大組件在AndroidMenifest中指定android:process屬性。如下圖方式:上圖可以看到process屬性有兩種寫法邮偎,這兩種寫法是有區(qū)別的管跺,以”:“開頭的相當(dāng)于當(dāng)前應(yīng)用的私有進(jìn)程,其他應(yīng)用的組件不可以和他跑在同一進(jìn)程中禾进,而另一種則屬于全局進(jìn)程豁跑,其他應(yīng)用通過ShareUID方式和它跑在同一進(jìn)程,那UID是什么呢泻云?UID是Android系統(tǒng)為每一個(gè)應(yīng)用分配的用戶身份證明來區(qū)分不同的應(yīng)用艇拍。
- 具有相同UID的應(yīng)用能共享數(shù)據(jù)(可以互相訪問對(duì)方的私有數(shù)據(jù)data目錄、組件信息等)
- 跑在同一進(jìn)程需要相同的UID+相同的簽名(還可以訪問共享內(nèi)存數(shù)據(jù))
相同UID的應(yīng)用實(shí)現(xiàn)資源共享: 首先需要在兩個(gè)應(yīng)用的AndroidManifest.xml中都定義相同的sharedUserId宠纯,如:android:sharedUserId="com.test"卸夕。
uid共享res資源實(shí)例
假設(shè)我們有這樣一個(gè)需求,A和B是兩個(gè)應(yīng)用婆瓜,現(xiàn)在要求在A中獲取B的一張名字為send_bg的圖片資源快集,那么先將A和B的注冊(cè)文件的AndroidManifest.xml節(jié)點(diǎn)添加sharedUserId贡羔,并且賦值相同,然后在A中可以用如下方式實(shí)現(xiàn):
Context thdContext = null;
try {
thdContext = createPackageContext(
"com.example.testdatabase",
Context.CONTEXT_IGNORE_SECURITY);
Resources res = thdContext.getResources();
int menuIconId = res.getIdentifier("send_bg", "drawable",
"com.example.testdatabase");
Drawable drawable = res.getDrawable(menuIconId);
mButton.setBackgroundDrawable(drawable);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
uid共享database實(shí)例
假設(shè)我們有這樣一個(gè)需求个初,A和B是兩個(gè)應(yīng)用乖寒,現(xiàn)在要求在A中要獲取B的數(shù)據(jù)庫,那么先將A和B的注冊(cè)文件的AndroidManifest.xml節(jié)點(diǎn)添加sharedUserId院溺,并且賦值相同楣嘁,然后在A中可以用如下方式實(shí)現(xiàn):
Context thdContext = null;
try {
thdContext = createPackageContext(
"com.example.testdatabase",
Context.CONTEXT_IGNORE_SECURITY);
String dbPath = thdContext.getDatabasePath("BookStore.db")
.getAbsolutePath();
SQLiteDatabase db = SQLiteDatabase.openDatabase(dbPath,
null, SQLiteDatabase.OPEN_READWRITE);
Cursor cursor = db.query("Book", null, null, null, null,
null, null);
if (cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor
.getColumnIndex("name"));
Log.d(TAG, "name: " + name);
} while (cursor.moveToNext());
}
} catch (NameNotFoundException e) {
e.printStackTrace();
}
同應(yīng)用開啟多進(jìn)程后,就運(yùn)行在不同的虛擬機(jī)里了覆获,不同的虛擬機(jī)在內(nèi)存分配上有不同的地址空間马澈,造成的問題主要是:
- 靜態(tài)成員和單例模式失效
- 線程同步機(jī)制失效
- SharePreferences可靠性下降,底層是通過讀寫XML文件來實(shí)現(xiàn)的弄息,不支持兩個(gè)進(jìn)程同時(shí)讀寫操作痊班,會(huì)丟失數(shù)據(jù)。
- Application會(huì)多次創(chuàng)建摹量,因?yàn)橄到y(tǒng)要為新建的進(jìn)程分配獨(dú)立的虛擬機(jī)涤伐,這個(gè)過程就是啟動(dòng)一個(gè)新應(yīng)用的過程,相當(dāng)于系統(tǒng)又把這個(gè)應(yīng)用重新啟動(dòng)了一遍導(dǎo)致重建新的Application缨称。運(yùn)行在同一進(jìn)程中的組件屬于同一個(gè)虛擬機(jī)和Application凝果,反之則反。
利用跨進(jìn)程通信解決上述問題
簡單說就是:通過使數(shù)據(jù)持久化存儲(chǔ)在設(shè)備上來傳輸數(shù)據(jù)睦尽。
需要通過Serializable接口/Parcelable接口來完成對(duì)象的序列化器净,再通過Intent和Binder傳輸數(shù)據(jù)或者通過Serializable將對(duì)象持久化到存儲(chǔ)設(shè)備上或者網(wǎng)絡(luò)傳輸給其他應(yīng)用。
對(duì)比 | Parcelable | Serializable |
---|---|---|
實(shí)現(xiàn)方式 | 實(shí)現(xiàn)Parcelable接口 | 實(shí)現(xiàn)Serializable接口 |
屬于 | android 專用当凡,基于DVM | Java自帶山害,基于JVM |
內(nèi)存消耗 | 優(yōu)秀 | 一般,相對(duì)開銷大 |
讀寫數(shù)據(jù) | 內(nèi)存中直接進(jìn)行讀寫 | 通過使用IO流的形式將數(shù)據(jù)讀寫入在硬盤上 |
持久化 | 不可以 | 可以 |
速度 | 優(yōu)秀 | 一般 |
Android中使用場景 | 跨進(jìn)城數(shù)據(jù)的傳輸 | 數(shù)據(jù)持久化存儲(chǔ)設(shè)備沿量、網(wǎng)絡(luò)傳輸數(shù)據(jù) |
Serializable接口浪慌,基于JVM上的持久化數(shù)據(jù)
Java提供的一個(gè)序列化接口,它是一個(gè)空接口朴则,為對(duì)象提供標(biāo)準(zhǔn)的序列化和反序列化权纤。Serializable使用IO讀寫存儲(chǔ)在硬盤上。序列化過程使用了反射技術(shù)乌妒,并且期間產(chǎn)生臨時(shí)對(duì)象汹想。Serializable在序列化的時(shí)候會(huì)產(chǎn)生大量的臨時(shí)變量,從而引起頻繁的GC。優(yōu)點(diǎn)代碼少芥被。
使用方法:在需要序列化的類里面實(shí)現(xiàn)Serializable接口并聲明一個(gè)標(biāo)識(shí)serialVersionUID 如
public class User implements Serializable {
//可以不寫欧宜,系統(tǒng)根據(jù)結(jié)構(gòu)計(jì)算當(dāng)前類的hash值自動(dòng)分配,增加或刪除結(jié)構(gòu)從新計(jì)算會(huì)hash值會(huì)不一致拴魄,導(dǎo)致反序列化失敗程序crash冗茸。
//手動(dòng)指定席镀,最大程度反序列化成功,建議用插件自動(dòng)生成夏漱。
private static final long serialVersionUID =463442362345654634L;
... ...
}
剩下的工作系統(tǒng)會(huì)自動(dòng)完成豪诲,進(jìn)行序列化和反序列化的方法:采用ObjectOutputStream和ObjectInputStream。
//序列化過程
User user = new User("chou",27,man)挂绰;
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.tex"));
out.writeObject(user);
out.close;
//反序列化過程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.tex"));
User newUser = (User)in.readObject();
in.close;
Parcelable接口屎篱,Android專用,基于DVM上的持久化數(shù)據(jù),Parcelable 的讀取順序必須一致葵蒂,不然有的機(jī)型會(huì)出錯(cuò)閃退(遇到過這種bug)
Parcelable是直接在內(nèi)存中讀寫交播,我們知道內(nèi)存的讀寫速度肯定優(yōu)于硬盤讀寫速度,所以Parcelable序列化方式性能上要優(yōu)于Serializable方式很多践付。但是代碼寫起來相比Serializable方式麻煩一些秦士。
代碼示例:User(Parcel source) 參數(shù)的順序一定需要與 writeToParcel(Parcel dest, int flags)參數(shù)順序
public class User implements Parcelable {
private String name;
private int age;
private String sex;
public User(String name, int age, String sex ) {
this.name = name;
this.age = age;
this.sex = sex;
}
//返回當(dāng)前對(duì)象的內(nèi)容描述
@Override
public int describeContents() {
return 0;
}
//序列化操作,將對(duì)象寫入序列化結(jié)構(gòu)中
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeString(sex);
}
//反序列化操作
public static final Creator<User> CREATOR = new Creator<User>() {
//從序列化后的對(duì)象中創(chuàng)建原始對(duì)象
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
//創(chuàng)建指定長度的原始對(duì)象數(shù)組
@Override
public User[] newArray(int size) {
return new User[size];
}
};
//從序列化后的對(duì)象中創(chuàng)建原始對(duì)象
public User(Parcel source) {
this.name = source.readString();
this.age = source.readInt();
this.sex = source.readString();
}
}
這里先說一下Parcel永高,Parcel內(nèi)部包裝了可序列化的數(shù)據(jù)隧土,可以在Binder中自由傳輸。從上述代碼中可以看出命爬,在序列化過程中需要實(shí)現(xiàn)的功能有序列化曹傀、反序列化和內(nèi)容描述。序列化功能由writeToParcel方法來完成饲宛,最終是通過Parcel中的一系列write方法來完成的皆愉;反序列化功能由CREATOR來完成,其內(nèi)部標(biāo)明了如何創(chuàng)建序列化對(duì)象和數(shù)組艇抠,并通過Parcel的一系列read方法來完成反序列化過程亥啦;內(nèi)容描述功能由describeContents方法來完成,幾乎在所有情況下這個(gè)方法都應(yīng)該返回0练链,僅當(dāng)當(dāng)前對(duì)象中存在文件描述符時(shí),此方法返回1奴拦。需要注意的是媒鼓,在User(Parcel in)方法中,由于book是另一個(gè)可序列化對(duì)象错妖,所以它的反序列化過程需要傳遞當(dāng)前線程的上下文類加載器绿鸣,否則會(huì)報(bào)無法找到類的錯(cuò)誤。詳細(xì)的方法說明請(qǐng)參看下面表格
方法 | 功能 | 標(biāo)記位 |
---|---|---|
createFromParcel(Parcel in) | 從序列化后的對(duì)象中創(chuàng)建原始對(duì)象 | |
newArray(int size) | 創(chuàng)建指定長度的原始對(duì)象數(shù)組 | |
User(Parcel in) | 從序列化后的對(duì)象中創(chuàng)建原始對(duì)象 | |
writeToParce(Parcel out, int flags) | 將當(dāng)前對(duì)象寫入序列化結(jié)構(gòu)中,其中flags標(biāo)識(shí)有兩種值: 0或者1 (參見右側(cè)標(biāo)記位)暂氯。為1時(shí)標(biāo)識(shí)當(dāng)前對(duì)象需要作為返回值返回,不能立即釋放資源,幾乎所有情況都為0 | PARCELABLE_WRITE_RETURN_VALUE |
describeContents | 返回當(dāng)前對(duì)象的內(nèi)容描述潮模。如果含有文件描述符,返回1(參見右側(cè)標(biāo)記位),否則返回0, 幾乎所有情況都返回0 | ONTENTS_FILE_DESCRIPTOR |
- 將這個(gè)數(shù)據(jù)放到 intent 或者 Bundle 中完成傳遞
Intent intent = new Intent(MainActivity.this, ServerService.class);
intent.setExtrasClassLoader(User.class.getClassLoader());
Bundle bundle = new Bundle();
bundle.putParcelable("User", new User("chou",27,"sex"));
intent.putExtras(bundle);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- 在需要接收目標(biāo)組件中完成接收操作
Bundle bundle = intent.getExtras();
User user = bundle.getParcelable("User");
Binder重頭戲
binder是Android中的一種跨進(jìn)程通信方式,是Android的一個(gè)類痴施,它實(shí)現(xiàn)了IBinder接口擎厢。
- 從IPC角度講:也可以把它當(dāng)成一種虛擬的物理設(shè)備究流,它的驅(qū)動(dòng)是/dev/binder,該通訊方式在Linux中沒有动遭,所以是Android特有的跨進(jìn)程通信方式芬探。
- 從Android Framework層面上講:Binder是連接ServiceManager(用來管理系統(tǒng)的service)連接各種Manager(ActivityManager、WindowManager等等)和相應(yīng)ManagerService的橋梁厘惦。
- 從應(yīng)用層講:Binder是客戶端和服務(wù)端進(jìn)行通信的媒介偷仿,當(dāng)bindService時(shí),服務(wù)端會(huì)返回一個(gè)包含服務(wù)端業(yè)務(wù)調(diào)用的Binder對(duì)象宵蕉,客戶端通過這個(gè)對(duì)象來獲取服務(wù)端提供的服務(wù)或數(shù)據(jù)酝静。
Android是基于Linux系統(tǒng)上的,我們先了解一下Linux中的IPC通信原理
在Linux中羡玛,進(jìn)程之間是隔離的别智,內(nèi)存也是不共享的,想要進(jìn)程之間通信必須先了解一下進(jìn)程空間缝左,進(jìn)程空間分為用戶空間和內(nèi)核空間亿遂,用戶空間是用戶程序運(yùn)行的空間,內(nèi)核空間則是內(nèi)核運(yùn)行的空間渺杉,為了防止用戶空間隨便干擾蛇数,用戶空間是獨(dú)立的,內(nèi)核空間是共享的是越,但為了安全性考慮耳舅,內(nèi)核空間跟用戶空間也是隔離的,它們之間的僅可以通過系統(tǒng)調(diào)用來通信倚评。至此我們知道IPC的大致方案是A進(jìn)程的數(shù)據(jù)通過系統(tǒng)調(diào)用把數(shù)據(jù)傳遞到內(nèi)核空間浦徊,內(nèi)核空間再利用系統(tǒng)調(diào)用把數(shù)據(jù)傳遞到B空間,其中會(huì)有兩次數(shù)據(jù)的拷貝如下圖:
缺點(diǎn)顯而易見天梧,數(shù)據(jù)傳遞通過內(nèi)存緩存—>內(nèi)核緩存—>內(nèi)存緩存2次拷貝性能低盔性,其次是傳遞數(shù)據(jù)后,接收方不知道用多大內(nèi)存存放呢岗,所以盡可能大的開辟內(nèi)存空間導(dǎo)致內(nèi)存空間浪費(fèi)冕香。
再來看一下Android中的IPC通信-Binder
字面意思,粘合劑后豫、膠水的意思悉尾,顧名思義就是粘合不同的進(jìn)程,使之實(shí)現(xiàn)通信挫酿。
你肯定有疑問构眯,既然基于Linux系統(tǒng),為什么Android不沿用Linux的IPC而要使用Binder呢早龟?因?yàn)榫C合考慮其性能惫霸、穩(wěn)定性和安全性猫缭。
- 性能 :數(shù)據(jù)拷貝次數(shù),共享內(nèi)存0次它褪、Binder 1次饵骨、Socket/管道/消息隊(duì)列2次。
- 穩(wěn)定性:Binder基于C/S架構(gòu)茫打,客戶端有需求丟給服務(wù)端完成居触,架構(gòu)清晰,職責(zé)明確獨(dú)立老赤,共享內(nèi)存需要控制負(fù)責(zé)轮洋、難以使用,Binder機(jī)制更優(yōu)抬旺。
- 安全性:Android開放性平臺(tái)弊予,不免會(huì)有很多惡意APP、流氓軟件等开财,傳統(tǒng)IPC沒有任何安全防護(hù)汉柒,無法獲取對(duì)方進(jìn)程ID,Android為每個(gè)進(jìn)程分配UID责鳍,傳統(tǒng)的IPC機(jī)制只能在數(shù)據(jù)包中添加碾褂,可靠的身份標(biāo)識(shí)只能由IPC機(jī)制在內(nèi)核添加才安全,其次傳統(tǒng)的IPC訪問接入點(diǎn)是開放的历葛,惡意軟件通過猜測(cè)接入點(diǎn)可以獲得連接正塌,所以不安全。Binder支持實(shí)名和匿名Binder恤溶,安全性更高乓诽。
先看看了解Binder可以給我們帶來什么幫助?
- 為什么Activity間傳遞對(duì)象需要序列化咒程?
- 幫助我們理解Activity的啟動(dòng)流程
- 四大組件底層的通訊機(jī)制是怎樣的鸠天?
- 理解AIDL內(nèi)部實(shí)現(xiàn)機(jī)制
- 幫助理解插件化編程等等~
Android應(yīng)用程序是由四大組件中的一個(gè)或多個(gè)組成,有時(shí)這些組件運(yùn)行在同一進(jìn)程帐姻,有時(shí)運(yùn)行在不同進(jìn)程粮宛,這些進(jìn)程間通信需要依賴Binder IPC機(jī)制,不光于此卖宠,Android系統(tǒng)對(duì)應(yīng)用層提供的各種服務(wù)如AMS、PMS等都是基于Binder IPC機(jī)制來實(shí)現(xiàn)的忧饭。
了解了這些扛伍,我們?cè)賮砜碆inder是怎樣不利用傳統(tǒng)的IPC來實(shí)現(xiàn)通信的
IPC基于內(nèi)核空間,但Binder不是Linux系統(tǒng)內(nèi)核的一部分词裤,但Linux有動(dòng)態(tài)內(nèi)核可加載模塊(LKM)的機(jī)制刺洒,模塊是具有獨(dú)立功能的程序鳖宾,可以被單獨(dú)編譯但不能獨(dú)立運(yùn)行,運(yùn)行時(shí)被了鏈接到內(nèi)核座位內(nèi)核的一部分運(yùn)行逆航,至此Android系統(tǒng)就可以通過動(dòng)態(tài)添加一個(gè)內(nèi)核模塊運(yùn)行在內(nèi)核空間鼎文,用戶進(jìn)程之間通過這個(gè)內(nèi)核模塊作為橋梁來實(shí)現(xiàn)通信,我們把這個(gè)內(nèi)核模塊叫做Binder驅(qū)動(dòng)(Binder Driver)因俐。它是怎樣實(shí)現(xiàn)進(jìn)程間通信的呢拇惋?通過內(nèi)存映射!抹剩,Binder IPC 機(jī)制中涉及到的內(nèi)存映射通過 mmap() 來實(shí)現(xiàn)撑帖,mmap() 是操作系統(tǒng)中一種內(nèi)存映射的方法。內(nèi)存映射簡單的講就是將用戶空間的一塊內(nèi)存區(qū)域映射到內(nèi)核空間澳眷。映射關(guān)系建立后胡嘿,用戶對(duì)這塊內(nèi)存區(qū)域的修改可以直接反應(yīng)到內(nèi)核空間;反之內(nèi)核空間對(duì)這段區(qū)域的修改也能直接反應(yīng)到用戶空間钳踊。內(nèi)存映射能減少數(shù)據(jù)拷貝次數(shù)衷敌,實(shí)現(xiàn)用戶空間和內(nèi)核空間的高效互動(dòng)。兩個(gè)空間各自的修改能直接反映在映射的內(nèi)存區(qū)域拓瞪,從而被對(duì)方空間及時(shí)感知缴罗。也正因?yàn)槿绱耍瑑?nèi)存映射能夠提供對(duì)進(jìn)程間通信的支持吴藻。
Binder IPC 原理
Binder IPC 正是基于內(nèi)存映射(mmap)來實(shí)現(xiàn)的瞒爬,但是 mmap() 通常是用在有物理介質(zhì)的文件系統(tǒng)上的。舉個(gè)栗子:進(jìn)程中的用戶區(qū)域不能直接訪問物理設(shè)備沟堡,如果想訪問磁盤數(shù)據(jù)侧但,需要通過磁盤—>內(nèi)核空間—>用戶空間這兩次拷貝,我們通過mmap()在兩者之間建立映射航罗,減少數(shù)據(jù)的拷貝禀横,用內(nèi)存讀取來取代I/O讀寫提高效率。而 Binder 并不存在物理介質(zhì)粥血,因此 Binder 驅(qū)動(dòng)使用 mmap() 并不是為了在物理介質(zhì)和用戶空間之間建立映射柏锄,而是用來在內(nèi)核空間創(chuàng)建數(shù)據(jù)接收的緩存空間。
過程如下圖文:
1.首先 Binder 驅(qū)動(dòng)在內(nèi)核空間創(chuàng)建一個(gè)數(shù)據(jù)接收緩存區(qū)复亏;
2.接著在內(nèi)核空間開辟一塊內(nèi)核緩存區(qū)趾娃,建立內(nèi)核緩存區(qū)和內(nèi)核中數(shù)據(jù)接收緩存區(qū)之間的映射關(guān)系,以及內(nèi)核中數(shù)據(jù)接收緩存區(qū)和接收進(jìn)程用戶空間地址的映射關(guān)系缔御;
3.發(fā)送方進(jìn)程通過系統(tǒng)調(diào)用 copy_from_user() 將數(shù)據(jù) copy 到內(nèi)核中的內(nèi)核緩存區(qū)抬闷,由于內(nèi)核緩存區(qū)和接收進(jìn)程的用戶空間存在內(nèi)存映射,因此也就相當(dāng)于把數(shù)據(jù)發(fā)送到了接收進(jìn)程的用戶空間,這樣便完成了一次進(jìn)程間的通信笤成。
Client/Server/ServiceManager/驅(qū)動(dòng)
Client评架、Server、Service Manager 運(yùn)行在用戶空間炕泳,Binder 驅(qū)動(dòng)運(yùn)行在內(nèi)核空間纵诞。其中 Service Manager 和 Binder 驅(qū)動(dòng)由系統(tǒng)提供,而 Client培遵、Server 由應(yīng)用程序來實(shí)現(xiàn)浙芙。Client、Server 和 ServiceManager 均是通過系統(tǒng)調(diào)用 open荤懂、mmap 和 ioctl 來訪問設(shè)備文件 /dev/binder茁裙,從而實(shí)現(xiàn)與 Binder 驅(qū)動(dòng)的交互來間接的實(shí)現(xiàn)跨進(jìn)程通信。Client节仿、Server晤锥、ServiceManager、Binder 驅(qū)動(dòng)這幾個(gè)組件在通信過程中扮演的角色就如同互聯(lián)網(wǎng)中服務(wù)器(Server)廊宪、客戶端(Client)矾瘾、DNS域名服務(wù)器(ServiceManager)以及路由器(Binder 驅(qū)動(dòng))之前的關(guān)系。
- binder 驅(qū)動(dòng) -> 路由器
- ServiceManager -> DNS
- Binder Client -> 客戶端
-
Binder Server -> 服務(wù)器
如下圖比較:
Binder通訊整個(gè)下來的過程
1.首先箭启,一個(gè)進(jìn)程使用 BINDER_SET_CONTEXT_MGR 命令通過 Binder 驅(qū)動(dòng)將自己注冊(cè)成為 ServiceManager壕翩;
2.Server 通過驅(qū)動(dòng)向 ServiceManager 中注冊(cè) Binder(Server 中的 Binder 實(shí)體),表明可以對(duì)外提供服務(wù)傅寡。驅(qū)動(dòng)為這個(gè) Binder 創(chuàng)建位于內(nèi)核中的實(shí)體節(jié)點(diǎn)以及 ServiceManager 對(duì)實(shí)體的引用放妈,將名字以及新建的引用打包傳給 ServiceManager,ServiceManger 將其填入查找表荐操。
3.Client 通過名字芜抒,在 Binder 驅(qū)動(dòng)的幫助下從 ServiceManager 中獲取到對(duì) Binder 實(shí)體的引用,通過這個(gè)引用就能實(shí)現(xiàn)和 Server 進(jìn)程的通信托启。
Binder 通信中的代理模式
我們已經(jīng)解釋清楚 Client宅倒、Server 借助 Binder 驅(qū)動(dòng)完成跨進(jìn)程通信的實(shí)現(xiàn)機(jī)制了,但是還有個(gè)問題會(huì)讓我們困惑屯耸。A 進(jìn)程想要 B 進(jìn)程中某個(gè)對(duì)象(object)是如何實(shí)現(xiàn)的呢拐迁?畢竟它們分屬不同的進(jìn)程,A 進(jìn)程 沒法直接使用 B 進(jìn)程中的 object疗绣。
前面我們介紹過跨進(jìn)程通信的過程都有 Binder 驅(qū)動(dòng)的參與线召,因此在數(shù)據(jù)流經(jīng) Binder 驅(qū)動(dòng)的時(shí)候驅(qū)動(dòng)會(huì)對(duì)數(shù)據(jù)做一層轉(zhuǎn)換。當(dāng) A 進(jìn)程想要獲取 B 進(jìn)程中的 object 時(shí)多矮,驅(qū)動(dòng)并不會(huì)真的把 object 返回給 A缓淹,而是返回了一個(gè)跟 object 看起來一模一樣的代理對(duì)象 objectProxy,這個(gè) objectProxy 具有和 object 一摸一樣的方法,但是這些方法并沒有 B 進(jìn)程中 object 對(duì)象那些方法的能力割卖,這些方法只需要把把請(qǐng)求參數(shù)交給驅(qū)動(dòng)即可。對(duì)于 A 進(jìn)程來說和直接調(diào)用 object 中的方法是一樣的患雏。
各 Java 類職責(zé)描述
在正式編碼實(shí)現(xiàn)跨進(jìn)程調(diào)用之前,先介紹下實(shí)現(xiàn)過程中用到的一些類吓肋。了解了這些類的職責(zé)凳怨,有助于我們更好的理解和實(shí)現(xiàn)跨進(jìn)程通信。
IBinder : IBinder 是一個(gè)接口是鬼,代表了一種跨進(jìn)程通信的能力肤舞。只要實(shí)現(xiàn)了這個(gè)接口,這個(gè)對(duì)象就能跨進(jìn)程傳輸均蜜。
IInterface : IInterface 代表的就是 Server 進(jìn)程對(duì)象具備什么樣的能力(能提供哪些方法李剖,其實(shí)對(duì)應(yīng)的就是 AIDL 文件中定義的接口)
Binder : Java 層的 Binder 類,代表的其實(shí)就是 Binder 本地對(duì)象囤耳。BinderProxy 類是 Binder 類的一個(gè)內(nèi)部類篙顺,它代表遠(yuǎn)程進(jìn)程的 Binder 對(duì)象的本地代理;這兩個(gè)類都繼承自 IBinder, 因而都具有跨進(jìn)程傳輸?shù)哪芰Τ湓瘢粚?shí)際上德玫,在跨越進(jìn)程的時(shí)候,Binder 驅(qū)動(dòng)會(huì)自動(dòng)完成這兩個(gè)對(duì)象的轉(zhuǎn)換聪铺。
Stub : AIDL 的時(shí)候化焕,編譯工具會(huì)給我們生成一個(gè)名為 Stub 的靜態(tài)內(nèi)部類;這個(gè)類繼承了 Binder, 說明它是一個(gè) Binder 本地對(duì)象铃剔,它實(shí)現(xiàn)了 IInterface 接口撒桨,表明它具有 Server 承諾給 Client 的能力;Stub 是一個(gè)抽象類键兜,具體的 IInterface 的相關(guān)實(shí)現(xiàn)需要開發(fā)者自己實(shí)現(xiàn)凤类。
實(shí)現(xiàn)過程
一次跨進(jìn)程通信必然會(huì)涉及到兩個(gè)進(jìn)程,在這個(gè)例子中 RemoteService 作為服務(wù)端進(jìn)程普气,提供服務(wù)谜疤;ClientActivity 作為客戶端進(jìn)程,使用 RemoteService 提供的服務(wù)。如下圖:
那么服務(wù)端進(jìn)程具備什么樣的能力夷磕?能為客戶端提供什么樣的服務(wù)呢履肃?還記得我們前面介紹過的 IInterface 嗎,它代表的就是服務(wù)端進(jìn)程具體什么樣的能力坐桩。因此我們需要定義一個(gè) BookManager 接口尺棋,BookManager 繼承自 IIterface,表明服務(wù)端具備什么樣的能力绵跷。
/**
* 這個(gè)類用來定義服務(wù)端 RemoteService 具備什么樣的能力
*/
public interface BookManager extends IInterface {
void addBook(Book book) throws RemoteException;
}
只定義服務(wù)端具備什么要的能力是不夠的膘螟,既然是跨進(jìn)程調(diào)用,那么接下來我們得實(shí)現(xiàn)一個(gè)跨進(jìn)程調(diào)用對(duì)象 Stub碾局。Stub 繼承 Binder, 說明它是一個(gè) Binder 本地對(duì)象荆残;實(shí)現(xiàn) IInterface 接口,表明具有 Server 承諾給 Client 的能力净当;Stub 是一個(gè)抽象類内斯,具體的 IInterface 的相關(guān)實(shí)現(xiàn)需要調(diào)用方自己實(shí)現(xiàn)。
public abstract class Stub extends Binder implements BookManager {
...
public static BookManager asInterface(IBinder binder) {
if (binder == null)
return null;
IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
if (iin != null && iin instanceof BookManager)
return (BookManager) iin;
return new Proxy(binder);
}
...
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSAVTION_addBook:
data.enforceInterface(DESCRIPTOR);
Book arg0 = null;
if (data.readInt() != 0) {
arg0 = Book.CREATOR.createFromParcel(data);
}
this.addBook(arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
...
}
Stub 類中我們重點(diǎn)介紹下 asInterface
和 onTransact
蚯瞧。
先說說 asInterface
嘿期,當(dāng) Client 端在創(chuàng)建和服務(wù)端的連接,調(diào)用 bindService 時(shí)需要?jiǎng)?chuàng)建一個(gè) ServiceConnection 對(duì)象作為入?yún)⒙窈稀T?ServiceConnection 的回調(diào)方法 onServiceConnected 中 會(huì)通過這個(gè) asInterface(IBinder binder) 拿到 BookManager 對(duì)象备徐,這個(gè) IBinder 類型的入?yún)?binder 是驅(qū)動(dòng)傳給我們的,正如你在代碼中看到的一樣甚颂,方法中會(huì)去調(diào)用 binder.queryLocalInterface() 去查找 Binder 本地對(duì)象蜜猾,如果找到了就說明 Client 和 Server 在同一進(jìn)程,那么這個(gè) binder 本身就是 Binder 本地對(duì)象振诬,可以直接使用蹭睡。否則說明是 binder 是個(gè)遠(yuǎn)程對(duì)象屎媳,也就是 BinderProxy欧漱。因此需要我們創(chuàng)建一個(gè)代理對(duì)象 Proxy,通過這個(gè)代理對(duì)象來是實(shí)現(xiàn)遠(yuǎn)程訪問凉泄。
接下來我們就要實(shí)現(xiàn)這個(gè)代理類 Proxy 了辫呻,既然是代理類自然需要實(shí)現(xiàn) BookManager 接口清钥。
public class Proxy implements BookManager {
...
public Proxy(IBinder remote) {
this.remote = remote;
}
@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if (book != null) {
data.writeInt(1);
book.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
replay.readException();
} finally {
replay.recycle();
data.recycle();
}
}
...
}
我們看看 addBook() 的實(shí)現(xiàn);在 Stub 類中放闺,addBook(Book book) 是一個(gè)抽象方法祟昭,Client 端需要繼承并實(shí)現(xiàn)它。
- 如果 Client 和 Server 在同一個(gè)進(jìn)程怖侦,那么直接就是調(diào)用這個(gè)方法篡悟。
- 如果是遠(yuǎn)程調(diào)用谜叹,Client 想要調(diào)用 Server 的方法就需要通過 Binder 代理來完成,也就是上面的 Proxy搬葬。
在 Proxy 中的 addBook() 方法中首先通過 Parcel 將數(shù)據(jù)序列化荷腊,然后調(diào)用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中創(chuàng)建急凰,能走到創(chuàng)建 Proxy 這一步就說明 Proxy 構(gòu)造函數(shù)的入?yún)⑹?BinderProxy停局,即這里的 remote 是個(gè) BinderProxy 對(duì)象。最終通過一系列的函數(shù)調(diào)用香府,Client 進(jìn)程通過系統(tǒng)調(diào)用陷入內(nèi)核態(tài),Client 進(jìn)程中執(zhí)行 addBook() 的線程掛起等待返回码倦;驅(qū)動(dòng)完成一系列的操作之后喚醒 Server 進(jìn)程企孩,調(diào)用 Server 進(jìn)程本地對(duì)象的 onTransact()。最終又走到了 Stub 中的 onTransact() 中袁稽,onTransact() 根據(jù)函數(shù)編號(hào)調(diào)用相關(guān)函數(shù)(在 Stub 類中為 BookManager 接口中的每個(gè)函數(shù)中定義了一個(gè)編號(hào)勿璃,只不過上面的源碼中我們簡化掉了;在跨進(jìn)程調(diào)用的時(shí)候推汽,不會(huì)傳遞函數(shù)而是傳遞編號(hào)來指明要調(diào)用哪個(gè)函數(shù))补疑;我們這個(gè)例子里面,調(diào)用了 Binder 本地對(duì)象的 addBook() 并將結(jié)果返回給驅(qū)動(dòng)歹撒,驅(qū)動(dòng)喚醒 Client 進(jìn)程里剛剛掛起的線程并將結(jié)果返回莲组。
這樣一次跨進(jìn)程調(diào)用就完成了。
多種多樣的跨進(jìn)程方式
上面介紹了Binder暖夭、序列化锹杈,我們?cè)賮砹私庖幌缕渌姆绞剑热缤ㄟ^Intent中附加的extra來傳遞信息迈着、通過文件共享的方式共享數(shù)據(jù)竭望、ContentProvider這種天生就支持跨進(jìn)城訪問的方式、通過網(wǎng)絡(luò)通訊Socket來完成通訊等裕菠。
1.使用Bundle:
四大組件都支持在Intent中傳遞Bundle數(shù)據(jù)咬清,Intent傳遞數(shù)據(jù)的特點(diǎn)上面說過需要序列化,Bundle實(shí)現(xiàn)了Pracelable接口奴潘,所以當(dāng)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)另一個(gè)進(jìn)程的Activity旧烧、Service等,就可以利用Bundle附加數(shù)據(jù)來傳遞信息萤彩,Bindle傳遞自定義類型需要序列化粪滤,但局限性是Bundle傳遞的數(shù)據(jù)有限制。
2.使用文件共享:
把一個(gè)對(duì)象序列化存儲(chǔ)寫入到SD卡上的一個(gè)文件里雀扶,另一個(gè)進(jìn)程再去讀取這個(gè)文件獲得通信杖小。
3.使用Messenger:
Messenger顧名思義肆汹,信使的意思,通過它我們可以在不同的進(jìn)程中傳遞Message對(duì)象予权,Message中存放著需要傳遞的對(duì)象昂勉。它是一種輕量級(jí)的IPC方案,底層實(shí)現(xiàn)是AIDL扫腺,它對(duì)AIDL做了封裝岗照,使我們使用起來更簡單,由于它一次處理一個(gè)請(qǐng)求笆环,所以我們不需要考慮線程同步問題攒至。
使用方法:
1.服務(wù)端進(jìn)程:創(chuàng)建Service來處理客戶端的連接請(qǐng)求,同時(shí)創(chuàng)建一個(gè)Handler并通過它來創(chuàng)建一個(gè)Messenger對(duì)象躁劣,然后在Service的onBind方法中返回這個(gè)messenger對(duì)象底層的Binder迫吐。
2.客戶端進(jìn)程:首先需要綁定服務(wù)端的Service,綁定成功后在服務(wù)端返回的IBinder對(duì)象中創(chuàng)建一個(gè)Messenger账忘,通過它向服務(wù)端發(fā)Message類型的消息志膀。如果服務(wù)端回應(yīng)客戶端,和服務(wù)端鳖擒,我們還需要?jiǎng)?chuàng)建一個(gè)Handler并創(chuàng)建一個(gè)新的Messenger并把這個(gè)Messenger對(duì)象通過Message的replyTo參數(shù)傳遞給服務(wù)端溉浙,服務(wù)端通過replyTo參數(shù)就可以回應(yīng)客戶端。
代碼示例:
- 構(gòu)建一個(gè)運(yùn)行在獨(dú)立進(jìn)程中的服務(wù)端Service:
public class MessengerService extends Service {
private static final String TAG = "MessagerService";
/**
* 處理來自客戶端的消息蒋荚,并用于構(gòu)建Messenger
*/
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_FROM_CLIENT:
Log.e(TAG, "receive message from client:" + message.getData().getString("msg"));
break;
default:
super.handleMessage(message);
break;
}
}
}
/**
* 構(gòu)建Messenger對(duì)象
*/
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
//將Messenger對(duì)象的Binder返回給客戶端
return mMessenger.getBinder();
}
}
- 注冊(cè)Service戳稽,在不同進(jìn)程
<service
android:name="com.xxq2dream.service.MessengerService"
android:process=":remote" />
- 然后客戶端是通過綁定服務(wù)端返回的binder來創(chuàng)建Messenger對(duì)象,并通過這個(gè)Messenger對(duì)象來向服務(wù)端發(fā)送消息
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.e(TAG, "ServiceConnection-->" + System.currentTimeMillis());
//通過服務(wù)端返回的Binder創(chuàng)建Messenger
mService = new Messenger(iBinder);
//創(chuàng)建消息期升,通過Bundle傳遞數(shù)據(jù)
Message message = Message.obtain(null, MESSAGE_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "hello service,this is client");
message.setData(bundle);
try {
//向服務(wù)端發(fā)送消息
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.e(TAG, "onServiceDisconnected-->binder died");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
//綁定服務(wù)
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
//解綁服務(wù)
unbindService(mConnection);
super.onDestroy();
}
}
- 服務(wù)端如果要回復(fù)消息給客戶端广鳍,那就要用到Message的replyTo參數(shù)了
- 服務(wù)端改造:
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case Constant.MESSAGE_FROM_CLIENT:
Log.e(TAG, "receive message from client:" + message.getData().getString("msg"));
//獲取客戶端傳遞過來的Messenger,通過這個(gè)Messenger回傳消息給客戶端
Messenger client = message.replyTo;
//當(dāng)然吓妆,回傳消息還是要通過message
Message msg = Message.obtain(null, Constant.MESSAGE_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("msg", "hello client, I have received your message!");
msg.setData(bundle);
try {
client.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(message);
break;
}
}
}
- 客戶端改造:
/**
* 用于構(gòu)建客戶端的Messenger對(duì)象赊时,并處理服務(wù)端的消息
*/
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case Constant.MESSAGE_FROM_SERVICE:
Log.e(TAG, "receive message from service:" + message.getData().getString("msg"));
break;
default:
super.handleMessage(message);
break;
}
}
}
/**
* 客戶端Messenger對(duì)象
*/
private Messenger mClientMessenger = new Messenger(new MessengerHandler());
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.e(TAG, "ServiceConnection-->" + System.currentTimeMillis());
mService = new Messenger(iBinder);
Message message = Message.obtain(null, MESSAGE_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "hello service,this is client");
message.setData(bundle);
//將客戶端的Messenger對(duì)象傳遞給服務(wù)端
message.replyTo = mClientMessenger;
try {
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.e(TAG, "onServiceDisconnected-->binder died");
}
};
4.使用AIDL:
Messenger是串行處理消息的,服務(wù)端需要一個(gè)個(gè)來處理行拢,不適合大量的消息同時(shí)發(fā)送給服務(wù)端祖秒。其次Messenger作用是傳遞消息,有時(shí)候我們還需要調(diào)用服務(wù)端方法舟奠,這種情景AIDL更適合竭缝。步驟如下:
1.服務(wù)端:創(chuàng)建Service用來監(jiān)聽客戶端連接請(qǐng)求
2.客戶端:綁定服務(wù)端的Service,將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)換成AIDL接口所屬的類型后就可以調(diào)用AIDL中的方法了沼瘫。
3.AIDL接口創(chuàng)建抬纸,聲明接口、方法耿戚、同步項(xiàng)目湿故,具體使用如下圖:
// IMyAidlInterface.aidl
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*
* 此方法作用是告訴我們aidl中可以使用的基本類型,可以刪除無視坛猪。
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
在AIDL中定義提供給其他app使用的方法
interface IMyAidlInterface {
String getName();
}
sycn project同步后脖阵,編譯工具會(huì)自動(dòng)幫我們生成一個(gè)Stub類,創(chuàng)建一個(gè)service并在里面創(chuàng)建一個(gè)繼承剛才接口的Stub類內(nèi)部類墅茉,實(shí)現(xiàn)接口方法命黔,并在onBind方法中返回內(nèi)部類的實(shí)例:
public class MyService extends Service
{
public MyService()
{
...
}
@Override
public IBinder onBind(Intent intent)
{
return new MyBinder();
}
class MyBinder extends IMyAidlInterface.Stub
{
@Override
public String getName() throws RemoteException
{
return "test";
}
}
}
接下來我們將AIDL文件拷貝到客戶端(包名,文件名必須完全一致)就斤,然后在Activity中綁定服務(wù)悍募。
public class MainActivity extends AppCompatActivity
{
private IMyAidlInterface iMyAidlInterface;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent("cc.abto.server"), new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name)
{
}
}, BIND_AUTO_CREATE);
}
public void onClick(View view)
{
try
{
Toast.makeText(MainActivity.this, iMyAidlInterface.getName(), Toast.LENGTH_SHORT).show();
}
catch (RemoteException e)
{
e.printStackTrace();
}
}
}
這邊我們通過隱式意圖來綁定service,在onServiceConnected方法中通過IMyAidlInterface.Stub.asInterface(service)獲取iMyAidlInterface對(duì)象洋机,然后在onClick中調(diào)用iMyAidlInterface.getName()搜立。
使用自定義數(shù)據(jù)類型是需要把使用的對(duì)象序列化實(shí)現(xiàn)Parcelable接口,在aidl中導(dǎo)入該類型再使用槐秧,注意包名文件名。
5.使用ContentProvider
ContentProvider是Android提供的專門用于應(yīng)用間進(jìn)行數(shù)據(jù)共享的方式忧设,天生適合進(jìn)程間通信刁标,和Messenger一樣底層同樣實(shí)現(xiàn)了Binder(可以理解幾乎所有跨進(jìn)程都是基于Binder的封裝。來實(shí)現(xiàn)的)址晕。
ContentProvider是一個(gè)抽象類膀懈,如果我們需要開發(fā)自己的內(nèi)容提供者我們就需要繼承這個(gè)類并復(fù)寫其方法,需要實(shí)現(xiàn)的主要方法如下:
public boolean onCreate()
在創(chuàng)建ContentProvider時(shí)使用
public Cursor query()
用于查詢指定uri的數(shù)據(jù)返回一個(gè)Cursor
public Uri insert()
用于向指定uri的ContentProvider中添加數(shù)據(jù)
public int delete()
用于刪除指定uri的數(shù)據(jù)
public int update()
用戶更新指定uri的數(shù)據(jù)
public String getType()
用于返回指定的Uri中的數(shù)據(jù)MIME類型
數(shù)據(jù)訪問的方法insert谨垃,delete和update可能被多個(gè)線程同時(shí)調(diào)用启搂,此時(shí)必須是線程安全
其它應(yīng)用可以通過ContentResolver來訪問ContentProvider提供的數(shù)據(jù),而ContentResolver通過uri來定位自己要訪問的數(shù)據(jù)刘陶。
為什么要通過再加一層ContentResolver而不是直接訪問ContentProvider胳赌?
原因是:一臺(tái)手機(jī)中可不是只有一個(gè)Provider內(nèi)容,它可能安裝了很多含有Provider的應(yīng)用匙隔,比如聯(lián)系人應(yīng)用疑苫,日歷應(yīng)用,字典應(yīng)用等等纷责。有如此多的Provider捍掺,如果你開發(fā)一款應(yīng)用要使用其中多個(gè),如果讓你去了解每個(gè)ContentProvider的不同實(shí)現(xiàn)再膳,豈不是要頭都大了挺勿。所以Android為我們提供了ContentResolver來統(tǒng)一管理與不同ContentProvider間的操作。怎樣區(qū)別不同的Provider則是通過URI喂柒!
擴(kuò)展:URI(Universal Resource Identifier)統(tǒng)一資源定位符
格式:
[scheme:][//host:port][path][?query]
URI:
http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack
- scheme:根據(jù)格式我們很容易看出來scheme為http
- host:www.baidu.com
- port:就是主機(jī)名后面path前面的部分為8080
- path:在port后面不瓶?的前面為wenku/jiatiao.html
- query:?之后的都是query部分為 id=123456$name=jack
uri的各個(gè)部分在安卓中都是可以通過代碼獲取的禾嫉,下面我們就以上面這個(gè)uri為例來說下獲取各個(gè)部分的方法: - getScheme() :獲取Uri中的scheme字符串部分,在這里是http
- getHost():獲取Authority中的Host字符串湃番,即 www.baidu.com
- getPost():獲取Authority中的Port字符串夭织,即 8080
- getPath():獲取Uri中path部分,即 wenku/jiatiao.html
- getQuery():獲取Uri中的query部分吠撮,即 id=15&name=du
接下來我們看一下如何使用尊惰,創(chuàng)建了兩個(gè)工程,進(jìn)程一自定義了contentprovider泥兰,進(jìn)程二通過ContentResolver來訪問進(jìn)程一中的contentprovider的數(shù)據(jù)(對(duì)進(jìn)程一中自定義的contentprovider的數(shù)據(jù)庫進(jìn)行增刪改查操作)
先寫個(gè)數(shù)據(jù)庫的工具類用來創(chuàng)建數(shù)據(jù)庫(進(jìn)程二就是跨進(jìn)程操作此數(shù)據(jù)庫的)
public class DBHelper extends SQLiteOpenHelper {
// 數(shù)據(jù)庫名
private static final String DATABASE_NAME = "finch.db";
// 表名
public static final String USER_TABLE_NAME = "user";
public static final String JOB_TABLE_NAME = "job";
private static final int DATABASE_VERSION = 1;
//數(shù)據(jù)庫版本號(hào)
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 創(chuàng)建兩個(gè)表格:用戶表 和職業(yè)表
db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)");
db.execSQL("CREATE TABLE IF NOT EXISTS " + JOB_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " job TEXT)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
再自定義一個(gè)自己的contentprovider弄屡。。DBHelper中有兩張表鞋诗, UriMatcher是用來根據(jù)進(jìn)程二的ContentResolver調(diào)用的uri判斷進(jìn)程二到底是需要操作哪張表的數(shù)據(jù)的膀捷。
public class MyProvider extends ContentProvider {
private Context mContext;
DBHelper mDbHelper = null;
SQLiteDatabase db = null;
public static final String AUTOHORITY = "com.example.zhaoziliang";
// 設(shè)置ContentProvider的唯一標(biāo)識(shí)
public static final int User_Code = 1;
public static final int Job_Code = 2;
// UriMatcher類使用:在ContentProvider 中注冊(cè)URI
private static final UriMatcher mMatcher;
static{
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 初始化
mMatcher.addURI(AUTOHORITY,"user", User_Code);
mMatcher.addURI(AUTOHORITY, "job", Job_Code);
// 若URI資源路徑 = content://cn.scu.myprovider/user ,則返回注冊(cè)碼User_Code
// 若URI資源路徑 = content://cn.scu.myprovider/job 削彬,則返回注冊(cè)碼Job_Code
}
// 以下是ContentProvider的6個(gè)方法
/**
* 初始化ContentProvider
*/
@Override
public boolean onCreate() {
mContext = getContext();
// 在ContentProvider創(chuàng)建時(shí)對(duì)數(shù)據(jù)庫進(jìn)行初始化
// 運(yùn)行在主線程全庸,故不能做耗時(shí)操作,此處僅作展示
mDbHelper = new DBHelper(getContext());
db = mDbHelper.getWritableDatabase();
// 初始化兩個(gè)表的數(shù)據(jù)(先清空兩個(gè)表,再各加入一個(gè)記錄)
db.execSQL("delete from user");
db.execSQL("insert into user values(1,'Carson');");
db.execSQL("insert into user values(2,'Kobe');");
db.execSQL("delete from job");
db.execSQL("insert into job values(1,'Android');");
db.execSQL("insert into job values(2,'iOS');");
return true;
}
/**
* 添加數(shù)據(jù)
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
// 根據(jù)URI匹配 URI_CODE,從而匹配ContentProvider中相應(yīng)的表名
// 該方法在最下面
String table = getTableName(uri);
// 向該表添加數(shù)據(jù)
db.insert(table, null, values);
// 當(dāng)該URI的ContentProvider數(shù)據(jù)發(fā)生變化時(shí)融痛,通知外界(即訪問該ContentProvider數(shù)據(jù)的訪問者)
mContext.getContentResolver().notifyChange(uri, null);
// // 通過ContentUris類從URL中獲取ID
// long personid = ContentUris.parseId(uri);
// System.out.println(personid);
return uri;
}
/**
* 查詢數(shù)據(jù)
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 根據(jù)URI匹配 URI_CODE壶笼,從而匹配ContentProvider中相應(yīng)的表名
// 該方法在最下面
String table = getTableName(uri);
// // 通過ContentUris類從URL中獲取ID
// long personid = ContentUris.parseId(uri);
// System.out.println(personid);
// 查詢數(shù)據(jù)
return db.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
}
/**
* 更新數(shù)據(jù)
*/
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// 由于不展示,此處不作展開
return 0;
}
/**
* 刪除數(shù)據(jù)
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 由于不展示,此處不作展開
return 0;
}
@Override
public String getType(Uri uri) {
// 由于不展示,此處不作展開
return null;
}
/**
* 根據(jù)URI匹配 URI_CODE,從而匹配ContentProvider中相應(yīng)的表名
*/
private String getTableName(Uri uri){
String tableName = null;
switch (mMatcher.match(uri)) {
case User_Code:
tableName = DBHelper.USER_TABLE_NAME;
break;
case Job_Code:
tableName = DBHelper.JOB_TABLE_NAME;
break;
}
return tableName;
}
}
進(jìn)程二:
ContentResolver通過對(duì)應(yīng)匹配的uri去調(diào)用對(duì)應(yīng)的進(jìn)程一的contentprovider的不同的表進(jìn)行增刪改查操作
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 對(duì)user表進(jìn)行操作
*/
// 設(shè)置URI
Uri uri_user = Uri.parse("content://com.example.zhaoziliang/user");
// 插入表中數(shù)據(jù)
ContentValues values = new ContentValues();
values.put("_id", 4);
values.put("name", "zzl");
// 獲取ContentResolver
ContentResolver resolver = getContentResolver();
// 通過ContentResolver 根據(jù)URI 向ContentProvider中插入數(shù)據(jù)
resolver.insert(uri_user,values);
// 通過ContentResolver 向ContentProvider中查詢數(shù)據(jù)
Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);
while (cursor.moveToNext()){
System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1));
// 將表中數(shù)據(jù)全部輸出
}
cursor.close();
// 關(guān)閉游標(biāo)
/**
* 對(duì)job表進(jìn)行操作
*/
// 和上述類似,只是URI需要更改,從而匹配不同的URI CODE,從而找到不同的數(shù)據(jù)資源
Uri uri_job = Uri.parse("content://com.example.zhaoziliang/job");
// 插入表中數(shù)據(jù)
ContentValues values2 = new ContentValues();
values2.put("_id", 4);
values2.put("job", "LOL Player");
// 獲取ContentResolver
ContentResolver resolver2 = getContentResolver();
// 通過ContentResolver 根據(jù)URI 向ContentProvider中插入數(shù)據(jù)
resolver2.insert(uri_job,values2);
// 通過ContentResolver 向ContentProvider中查詢數(shù)據(jù)
Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);
while (cursor2.moveToNext()){
System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1));
// 將表中數(shù)據(jù)全部輸出
}
cursor2.close();
// 關(guān)閉游標(biāo)
}
}
6.使用Socket(套接字)
socket實(shí)現(xiàn)進(jìn)程間通信雁刷,分為:
- TCP協(xié)議(傳輸控制/流式套接字):面向連接的協(xié)議覆劈,提供穩(wěn)定的雙向通信功能,三次握手四次揮手沛励,這個(gè)次數(shù)是保證安全又高效责语。
- UDP協(xié)議(用戶數(shù)據(jù)報(bào)套接字):面向無連接,不穩(wěn)定目派,不安全坤候,不保證數(shù)據(jù)一定能傳輸?shù)剑矢摺?br> 具體就是我們平常使用的網(wǎng)絡(luò)請(qǐng)求企蹭,有興趣的話自己寫個(gè)簡單的服務(wù)器聊天室铐拐。。练对。遍蟋。。螟凭。
Binder連接池
隨著項(xiàng)目越來越大虚青,很多業(yè)務(wù)模塊都需要使用AIDL來通信,我們還需要了解一下Binder連接池的原理螺男,但我真的寫吐了棒厘,以后再寫吧纵穿。。奢人。谓媒。
最后總結(jié)一下他們的使用場景