IPC為進(jìn)程間通信或跨進(jìn)程通信朵锣,是指兩個(gè)進(jìn)程進(jìn)行進(jìn)程間通信的過程谬盐。在PC和移動(dòng)設(shè)備上一個(gè)進(jìn)程指的是一個(gè)程序或者一個(gè)應(yīng)用,所以我們可以將進(jìn)程間通信簡(jiǎn)單的理解為:不同應(yīng)用之間的通信诚些,當(dāng)然這種說(shuō)法并不嚴(yán)謹(jǐn)飞傀。
在Android中,為每一個(gè)應(yīng)用程序都分配了一個(gè)獨(dú)立的虛擬機(jī)诬烹,或者說(shuō)每個(gè)進(jìn)程都分配一個(gè)獨(dú)立的虛擬機(jī)砸烦,不同虛擬機(jī)在內(nèi)存分配上都有不同的地址空間,這就導(dǎo)致在不同的虛擬機(jī)互相訪問數(shù)據(jù)需要借助其他手段椅您。下面分別介紹一下在Android中實(shí)現(xiàn)IPC的方式:
-
使用Bundle的方式
我們知道在Android中三大組件(Activity外冀,Service,Receiver)都支持在Intent中傳遞Bundle數(shù)據(jù)掀泳,由于Bundle實(shí)現(xiàn)了Parceable接口雪隧,所以它可以很方便的在不同的進(jìn)程之間進(jìn)行傳輸。當(dāng)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)另外一個(gè)進(jìn)程的Activity员舵,Service脑沿,Receiver時(shí),我們就可以在Bundle中附加我們所需要傳輸給遠(yuǎn)程的進(jìn)程的信息马僻,并且通過Intent發(fā)送出去庄拇。這里注意:我們傳輸?shù)臄?shù)據(jù)必須基本數(shù)據(jù)類型或者能夠被序列化。
1:基本數(shù)據(jù)類型(int, long, char, boolean, double等)
2:String和CharSequence
3:List:只支持ArrayList韭邓,并且里面的元素都能被AIDL支持
4:Map:只支持HashMap措近,里面的每個(gè)元素能被AIDL支持
5:Parcelable:所有實(shí)現(xiàn)Parcelable接口的對(duì)象```
下面看一個(gè)Demo例子:利用Bundle進(jìn)行進(jìn)程間通信
Intent intent = new Intent(MainActivity.this, TwoActivity.class);
Bundle bundle = new Bundle();
bundle.putString("data", "測(cè)試數(shù)據(jù)");
intent.putExtras(bundle);
startActivity(intent);```
注意:利用Bundle進(jìn)行進(jìn)程間通信是很容易的,大家應(yīng)該注意到女淑,這種方式進(jìn)行進(jìn)程間通信只能是單方向的簡(jiǎn)單數(shù)據(jù)傳輸瞭郑,它使用時(shí)有一定的局限性。
-
使用文件共享的方式
共享文件也是以后不錯(cuò)的進(jìn)程間通信的方式鸭你,兩個(gè)進(jìn)程通過讀/寫同一個(gè)文件來(lái)交換數(shù)據(jù)屈张,比如進(jìn)程A把數(shù)據(jù)寫入到文件File中,然后進(jìn)程B就可以通過讀取這個(gè)文件來(lái)獲取這個(gè)數(shù)據(jù)袱巨。通過這種方式阁谆,除了可以交換簡(jiǎn)單的文本信息之外,我們還可以序列化一個(gè)對(duì)象到文件系統(tǒng)中愉老,另一個(gè)進(jìn)程可以通過反序列化恢復(fù)這個(gè)對(duì)象场绿。
舉個(gè)例子:
在A進(jìn)程中創(chuàng)建一個(gè)線程進(jìn)行寫數(shù)據(jù):
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1, "user", false);
File cachedFile = new File(CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try{
objectOutputStream = new ObjectOutputStream
(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectOutputStream.close();
}
}
}).start();
在B進(jìn)程中創(chuàng)建一個(gè)線程進(jìn)行讀取數(shù)據(jù):
@Override
public void run() {
User user = null;
File cachedFile = new File(CACHE_FILE_PATH);
if(cachedFile.exists()){
ObjectInputStream objectInputStream = null;
try{
objectInputStream = new ObjectInputStream
(new FileInputStream(cachedFile));
user = objectInputStream.readObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectInputStream.close();
}
}
try{
objectOutputStream = new ObjectOutputStream
(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectOutputStream.close();
}
}
}).start();
通過文件共享的這種方式來(lái)共享數(shù)據(jù)對(duì)文件的格式是有具體要求的,比如可以是文本文件嫉入,也可以是XML文件裳凸,只要讀寫雙方約定數(shù)據(jù)格式即可贱鄙。這種方式進(jìn)行進(jìn)程間通信雖然方便劝贸,可是也是有局限性的姨谷,比如并發(fā)讀/寫,這會(huì)導(dǎo)致比較嚴(yán)重的問題映九,如讀取的數(shù)據(jù)不完整或者讀取的數(shù)據(jù)不是最新的梦湘。因此通過文件共享的方式適合在數(shù)據(jù)同步要求不高的進(jìn)程間通信,并且要妥善處理并發(fā)讀/寫問題件甥。
-
使用Messenger的方式
我們也可以通過Messenger來(lái)進(jìn)行進(jìn)程間通信捌议,在Messenger中放入我們需要傳遞的數(shù)據(jù),就可以輕松的實(shí)現(xiàn)進(jìn)程之間數(shù)據(jù)傳遞了引有。Messenger是一種輕量級(jí)的IPC方案瓣颅,它的底層實(shí)現(xiàn)是AIDL,關(guān)于AIDL我在下面會(huì)介紹到譬正。
Messenger的使用方法也是比較簡(jiǎn)單的宫补,實(shí)現(xiàn)一個(gè)Messenger有以下幾步,分為服務(wù)器端和客服端:
服務(wù)器進(jìn)程:在A進(jìn)程創(chuàng)建一個(gè)Service來(lái)處理其他進(jìn)程的連接請(qǐng)求曾我,同時(shí)創(chuàng)建一個(gè)Handler并通過它來(lái)創(chuàng)建一個(gè)Messenger對(duì)象粉怕,然后在Service的onBind()中返回這個(gè)Messenger對(duì)象底層的Binder即可。
public class MessengerService extends Service{
private Handler MessengerHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//消息處理.......
};
//創(chuàng)建服務(wù)端Messenger
private final Messenger mMessenger = new Messenger(MessengerHandler);
@Override
public IBinder onBind(Intent intent) {
//向客戶端返回Ibinder對(duì)象抒巢,客戶端利用該對(duì)象訪問服務(wù)端
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
}
客戶端進(jìn)程:在進(jìn)程B中首先綁定遠(yuǎn)程進(jìn)程Service贫贝,綁定成功后,根據(jù)Service返回的IBinder對(duì)象創(chuàng)建Messenger對(duì)象蛉谜,并使用此對(duì)象發(fā)送消息稚晚,為了能收到Service端返回的消息,客戶端也創(chuàng)建了一個(gè)自己的Messenger發(fā)送給Service端型诚,Service端就可以通過客戶端的Messenger向客戶端發(fā)送消息了客燕,具體的實(shí)現(xiàn)代碼如下:
public class MessengerActivity extends Activity{
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//根據(jù)得到的IBinder對(duì)象創(chuàng)建Messenger
mService = new Messenger(service);
//通過得到的mService 可以進(jìn)行通信
}
};
//為了收到Service的回復(fù),客戶端需要?jiǎng)?chuàng)建一個(gè)接收消息的Messenger和Handler
private Handler MessengerHander = new Handler(){
@Override
public void handleMessage(Message msg) {
//消息處理
}
};
private Messenger mGetMessenger = new Messenger(MessengerHander);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
init();
}
private void init() {
intent = new Intent(MessengerActivity.this, MessengerService.class);
indService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy(){
unbindService(conn);
super.onDestroy();
}
}
下面給出一張Messenger的工作原理圖俺驶,以便于更好的理解Messenger:
Messenger內(nèi)部消息處理使用Handler實(shí)現(xiàn)的幸逆,所以它是以串行的方式處理客服端發(fā)送過來(lái)的消息的,如果有大量的消息發(fā)送給服務(wù)器端暮现,服務(wù)器端只能一個(gè)一個(gè)處理还绘,如果并發(fā)量大的話用Messenger就不合適了,而且Messenger的主要作用就是為了傳遞消息栖袋,很多時(shí)候我們需要跨進(jìn)程調(diào)用服務(wù)器端的方法拍顷,這種需求Messenger就無(wú)法做到了。
-
使用AIDL的方式
AIDL(Android Interface Definition Language)是一種IDL語(yǔ)言塘幅,用于生成可以在Android設(shè)備上兩個(gè)進(jìn)程之間進(jìn)行進(jìn)程間通信(IPC)的代碼昔案。如果在一個(gè)進(jìn)程中(例如Activity)要調(diào)用另一個(gè)進(jìn)程中(例如Service)對(duì)象的操作尿贫,就可以使用AIDL生成可序列化的參數(shù)。
AIDL是IPC的一個(gè)輕量級(jí)實(shí)現(xiàn)踏揣,用了對(duì)于Java開發(fā)者來(lái)說(shuō)很熟悉的語(yǔ)法庆亡。Android也提供了一個(gè)工具,可以自動(dòng)創(chuàng)建Stub(類架構(gòu)捞稿,類骨架)又谋。當(dāng)我們需要在應(yīng)用間通信時(shí),我們需要按以下幾步走:
1:定義一個(gè)AIDL接口娱局。
2:為遠(yuǎn)程服務(wù)(Service)實(shí)現(xiàn)對(duì)應(yīng)Stub彰亥。
3:將服務(wù)“暴露”給客戶程序使用。
官方文檔中對(duì)AIDL有這樣一段介紹:
Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
第一句最重要衰齐,“只有當(dāng)你允許來(lái)自不同的客戶端訪問你的服務(wù)并且需要處理多線程問題時(shí)你才必須使用AIDL”任斋,其他情況下你都可以選擇其他方法,如使用Messenger耻涛,也能跨進(jìn)程通信废酷。可見AIDL是處理多線程犬第、多客戶端并發(fā)訪問的锦积。而Messenger是單線程處理。
AIDL很大的好處就是我們直接可以調(diào)用服務(wù)端進(jìn)程所暴露出來(lái)的方法歉嗓,下面簡(jiǎn)單介紹一下使用AIDL的使用方法:
服務(wù)端:
(1):創(chuàng)建aidl接口文件
AIDL使用簡(jiǎn)單的語(yǔ)法來(lái)聲明接口丰介,描述其方法以及方法的參數(shù)和返回值。這些參數(shù)和返回值可以是任何類型的鉴分,甚至是其他AIDL生成的接口哮幢。重要的是必須導(dǎo)入所有非內(nèi)置類型,哪怕是這些類型是與接口相同的包中志珍。
package com.example.android;
interface IRemoteService {
int getPid();
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
(2):向客戶端暴露接口
public class DDService extends Service {
@Override
public void onCreate() {
super.onCreate();
System.out.println("DDService onCreate........"
+ "Thread: " + Thread.currentThread().getName());
}
@Override
public IBinder onBind(Intent arg0) {
System.out.println("DDService onBind");
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
System.out.println("Thread: " + Thread.currentThread()
.getName());
System.out.println("DDService getPid ");
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
System.out.println("Thread: " + Thread.currentThread().getName());
System.out.println("basicTypes aDouble: " + aDouble
+" anInt: " + anInt+" aBoolean " + aBoolean
+" aString " + aString);
}
};
}
這樣我們的服務(wù)器端就完成了橙垢,把服務(wù)器端運(yùn)行到手機(jī)上,等一會(huì)可以看一下打印信息伦糯。重點(diǎn)看“線程名”柜某。
客戶端:
客戶端所做的事情就要簡(jiǎn)單很多了,首先需要綁定服務(wù)器端Service敛纲,綁定成功后將服務(wù)器端返回的Binder對(duì)象轉(zhuǎn)成AIDL接口所屬的類型喂击,接著皆可以調(diào)用AIDL中的方法了。
public class MainActivity extends Activity {
private IRemoteService remoteService;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
remoteService = IRemoteService.Stub.asInterface(service);
try {
int pid = remoteService.getPid();
int currentPid = Process.myPid();
System.out.println("currentPID: "
+ currentPid +" remotePID: " + pid);
remoteService.basicTypes(12, 1223, true, 12.2f, 12.3,
"我們的愛淤翔,我明白");
} catch (RemoteException e) {
e.printStackTrace();
}
System.out.println("bind success! "
+ remoteService.toString());
}
};
/**
* 監(jiān)聽按鈕點(diǎn)擊
* @param view
*/
public void buttonClick(View view) {
System.out.println("begin bindService");
Intent intent = new Intent("duanqing.test.aidl");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
}
這樣就實(shí)現(xiàn)了AIDL進(jìn)行進(jìn)程間通信了翰绊,是不是也很簡(jiǎn)單,不過這個(gè)看似簡(jiǎn)單,其實(shí)底層Android為我們做了很多的事情监嗜,核心就是Binder谐檀,感興趣的讀者可以學(xué)習(xí)一下Binder原理。
使用ContentProvider的方式
ContentProvider(內(nèi)容提供者)是Android中的四大組件之一裁奇,為了在應(yīng)用程序之間進(jìn)行數(shù)據(jù)交換桐猬,Android提供了ContentProvider,ContentProvider是不同應(yīng)用之間進(jìn)行數(shù)據(jù)交換的API框喳,一旦某個(gè)應(yīng)用程序通過ContentProvider暴露了自己的數(shù)據(jù)操作的接口课幕,那么不管該應(yīng)用程序是否啟動(dòng),其他的應(yīng)用程序都可以通過接口來(lái)操作接口內(nèi)的數(shù)據(jù)五垮,包括數(shù)據(jù)的增、刪杜秸、改放仗、查等操作。ContentProvider分為系統(tǒng)的和自定義的撬碟,系統(tǒng)的(例如:聯(lián)系人诞挨,圖片等數(shù)據(jù))。
開發(fā)一個(gè)ContentProvider的步驟很簡(jiǎn)單:
(1):定義自己的ContentProvider類呢蛤,該類集成ContentProvider基類惶傻;
(2):在AndroidMainfest.xml中注冊(cè)這個(gè)ContentProvider,類似于Activity注冊(cè)其障,注冊(cè)時(shí)要給ContentProvider綁定一個(gè)域名银室;
(3):當(dāng)我們注冊(cè)好這個(gè)ContentProvider后,其他應(yīng)用就可以訪問ContentProvider暴露出來(lái)的數(shù)據(jù)了励翼。
ContentProvider只是暴露出來(lái)可供其他應(yīng)用操作的數(shù)據(jù)蜈敢,其他應(yīng)用則需要通過ContentProvider來(lái)操作ContentProvider所暴露出來(lái)的數(shù)據(jù)。Content提供了getContentResolver()方法來(lái)獲取ContentProvider對(duì)象汽抚,獲取之后皆可以對(duì)暴露出來(lái)的數(shù)據(jù)進(jìn)行增抓狭、刪、改造烁、查操作了否过。
使用ContentResolver操作數(shù)據(jù)的步驟也很簡(jiǎn)單:
(1)調(diào)用Activity的getContentResolver()獲取ContentResolver對(duì)象;
(2)根據(jù)調(diào)用的ContentResolver的insert()惭蟋、delete()苗桂、update()和query()方法操作數(shù)據(jù)庫(kù)即可。使用廣播接收者(Broadcast)的方式
廣播是一種被動(dòng)跨進(jìn)程通信方式敞葛。當(dāng)某個(gè)程序向系統(tǒng)發(fā)送廣播時(shí)誉察,其他的應(yīng)用程序只能被動(dòng)地接收廣播數(shù)據(jù)。這就像電臺(tái)進(jìn)行廣播一樣惹谐,聽眾只能被動(dòng)地收聽持偏,而不能主動(dòng)與電臺(tái)進(jìn)行溝通驼卖。
BroadcastReceiver本質(zhì)上是一個(gè)系統(tǒng)級(jí)的監(jiān)聽器,它專門監(jiān)聽各個(gè)程序發(fā)出的Broadcast鸿秆,因此它擁有自己的進(jìn)程酌畜,只要存在與之匹配的Intent被廣播出來(lái),BroadcastReceivert總會(huì)被激發(fā)卿叽。我們知道桥胞,只要注冊(cè)了某個(gè)廣播之后,廣播接收者才能收到該廣播考婴。廣播注冊(cè)的一個(gè)行為是將自己感興趣的IntentFilter注冊(cè)到Android系統(tǒng)的AMS(ActivityManagerService)中贩虾,里面保存了一個(gè)IntentFilter列表。廣播發(fā)送者將IntentFilter的action行為發(fā)送到AMS中沥阱,然后遍歷AMS中的IntentFilter列表缎罢,看誰(shuí)訂閱了該廣播,然后將消息遍歷發(fā)送到注冊(cè)了相應(yīng)的IntentFilter或者Service中---也就是說(shuō):會(huì)調(diào)用抽象方法onReceive()方法考杉。其中AMS起到了中間橋梁的作用策精。
程序啟動(dòng)BroadcastReceiver只需要兩步:
(1):創(chuàng)建需要啟動(dòng)的BroadcastReceivert的intent;
(2):調(diào)用Context的sendBroadcast()或者sendOrderBroadcast()方法來(lái)啟動(dòng)指定的BroadcastReceivert崇棠。
每當(dāng)Broadcast事件發(fā)生后咽袜,系統(tǒng)會(huì)創(chuàng)建對(duì)應(yīng)的BroadcastReceiver實(shí)例,并自動(dòng)觸發(fā)onReceiver()方法枕稀,onReceiver()方法執(zhí)行完后询刹,BroadcastReceiver實(shí)例就會(huì)被銷毀。
注意:onReceiver()方法中盡量不要做耗時(shí)操作抽莱,如果onReceiver()方法不能再10秒之內(nèi)完成事件的處理范抓,Android會(huì)認(rèn)為該進(jìn)程無(wú)響應(yīng),也就彈出我們熟悉的ANR對(duì)話框食铐。如果我們需要在接收到廣播消息后進(jìn)行耗時(shí)的操作匕垫,我們可以考慮通過Intent啟動(dòng)一個(gè)Server來(lái)完成操作,不應(yīng)該啟動(dòng)一個(gè)新線程來(lái)完成操作虐呻,因?yàn)锽roadcastReceiver生命周期很短象泵,可能新建線程還沒有執(zhí)行完,BroadcastReceivert已經(jīng)銷毀了斟叼,而如果BroadcastReceivert結(jié)束了偶惠,它所在的進(jìn)程中雖然還有啟動(dòng)的新線程執(zhí)行任務(wù),可是由于該進(jìn)程中已經(jīng)沒有任何組件朗涩,因此系統(tǒng)會(huì)在內(nèi)存緊張的情況下回收該進(jìn)程忽孽,這就導(dǎo)致BroadcastReceivert啟動(dòng)的子線程不能執(zhí)行完成。
-
使用Socket的方式
Socaket也是實(shí)現(xiàn)進(jìn)程間通信的一種方式,Socaket也稱為“套接字”兄一,網(wǎng)絡(luò)通信中的概念厘线,通過Socket我們可以很方便的進(jìn)行網(wǎng)絡(luò)通信,都可以實(shí)現(xiàn)網(wǎng)絡(luò)通信錄出革,那么實(shí)現(xiàn)跨進(jìn)程通信不是也是相同的嘛造壮,但是Socaket主要還是應(yīng)用在網(wǎng)絡(luò)通信中。