进程间通信--IPC
前言:
進(jìn)程間通信(Inter-Process Communication),簡(jiǎn)稱IPC,就是指進(jìn)程與進(jìn)程之間進(jìn)行通信.一般來說,一個(gè)app只有一個(gè)進(jìn)程,但是可能會(huì)有多個(gè)線程,所以我們用得比較多的是多線程通信,比如handler,AsyncTask.
但是在一些特殊的情況下,我們app會(huì)需要多個(gè)進(jìn)程,或者是我們?cè)谶h(yuǎn)程服務(wù)調(diào)用時(shí),就需要跨進(jìn)程通信了
1.設(shè)置多進(jìn)程
Android設(shè)置多進(jìn)程的步驟很簡(jiǎn)單,只用在清單文件中為四大組件加上process屬性
<service android:name=".MessagerService"android:process=":messager"> </service>( :messager 最終的進(jìn)程名會(huì)變成 包名+:messager)
雖然多進(jìn)程設(shè)置起來很簡(jiǎn)單,但是使用的時(shí)候卻會(huì)有一系列的問題
(兩個(gè)進(jìn)程對(duì)應(yīng)的是不同的內(nèi)存區(qū)域)
- 1.Application對(duì)象會(huì)創(chuàng)建多次
- 2.靜態(tài)成員不共用
- 3.同步鎖失效
- 4.單例模式失效
- 5.數(shù)據(jù)傳遞的對(duì)象必須可序列化
2.可序列化
進(jìn)程間通信傳遞的對(duì)象是有嚴(yán)格要求的,除了基本數(shù)據(jù)類型,其他對(duì)象要想可以傳遞,必須可序列化,Android實(shí)現(xiàn)可序列化一般是通過實(shí)現(xiàn)Serializable或者是Parcelable
如果你在進(jìn)程通信中不需要傳非基本數(shù)據(jù)類型的對(duì)象,那么你可以不了解序列化,但是可序列化是進(jìn)程間通信的基礎(chǔ),所以還是建議不了解的朋友先熟悉一下
筆者之前介紹過序列化的相關(guān)知識(shí),這里就不重復(fù)介紹了
序列化–Serializable與Parcelable
http://blog.csdn.net/yulyu/article/details/56481665
3.通信
跨進(jìn)程通信的方法有很多,比如通過Intent傳遞,通過AIDL以及Messager通信,通過socket通信,這里主要介紹的是基于Binder的AIDL和Messager
3.1 Intent
Intent進(jìn)行數(shù)據(jù)的傳遞是我們平時(shí)最常用的,他的原理其實(shí)是對(duì)于Binder的封裝,但是他只能做到單向的數(shù)據(jù)傳遞,所以并不能很好的實(shí)現(xiàn)跨進(jìn)程通信,我們這里就不展開來介紹了
3.2 Messager
Messager的底層也是基于Binder的,其實(shí)應(yīng)該說他是在AIDL的基礎(chǔ)上封裝了一層
一般來說安卓中使用Binder主要是通過綁定服務(wù)(bindService),服務(wù)端(這里指的不是后臺(tái),是指其中一個(gè)進(jìn)程)主要是運(yùn)行Service,客戶端通過bindService獲取到相關(guān)的Binder,Binder就作為橋梁進(jìn)行跨進(jìn)程的通信.
這里我們先演示同一個(gè)應(yīng)用內(nèi)的多進(jìn)程通信
3.2.1 服務(wù)器端
首先我們先創(chuàng)建一個(gè)Service,
public class XiayuService extends Service{@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;} }并在清單文件中配置他的進(jìn)程
<service android:name=".XiayuService"android:process=":xiayu"/>在Service里面創(chuàng)建一個(gè)Hander用來接受消息
private final static Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {System.out.println("地瓜地瓜,我是土豆,我是土豆, 聽到請(qǐng)回答,聽到請(qǐng)回答");} };在Service里面創(chuàng)建一個(gè)Messager,并把Handler放入其中
private final static Messenger mMessenger = new Messenger(mHandler);重寫onbind方法,返回Messager里面的Binder
public IBinder onBind(Intent intent) {return mMessenger.getBinder(); }3.2.2 客戶端
創(chuàng)建一個(gè)對(duì)象實(shí)現(xiàn)ServiceConnection
private class MyServiceConnection implements ServiceConnection {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//當(dāng)連接上服務(wù)后會(huì)調(diào)用這個(gè)方法//TODO }@Overridepublic void onServiceDisconnected(ComponentName name) {} }綁定服務(wù)
Intent intent = new Intent(MainActivity.this, XiayuService.class);MyServiceConnection myServiceConnection = new MyServiceConnection();bindService(intent, myServiceConnection, BIND_AUTO_CREATE);綁定服務(wù)后,會(huì)調(diào)用ServiceConnection的onServiceConnected方法,通過Messager發(fā)送消息,服務(wù)器端的Handler就能夠收到消息了
private class MyServiceConnection implements ServiceConnection {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//通過Binder創(chuàng)建MessagerMessenger messenger = new Messenger(service);//創(chuàng)建msgMessage msg = Message.obtain();try {//通過Messager發(fā)送消息messenger.send(msg);} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {} }這樣的話我們就能夠通過bindService獲取到一個(gè)包含Binder的Messager進(jìn)行通信了,但是我們目前只實(shí)現(xiàn)了客戶端對(duì)服務(wù)器端傳遞消息,那么服務(wù)器端如何對(duì)客戶端傳遞消息呢?
我們先對(duì)服務(wù)器端的代碼進(jìn)行修改,首先修改Service的Handler
(關(guān)鍵代碼是 Messenger messenger = msg.replyTo)
private final static Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {System.out.println("地瓜地瓜,我是土豆,我是土豆, 聽到請(qǐng)回答,聽到請(qǐng)回答");//獲取MessagerMessenger messenger = msg.replyTo;//創(chuàng)建消息Message msg_reply = Message.obtain();try {//發(fā)送messenger.send(msg_reply);} catch (RemoteException e) {e.printStackTrace();}} };接著我們?cè)诳蛻舳艘苍黾右粋€(gè)Handler和Messager來處理消息
private final static Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {System.out.println("土豆,土豆,我是地瓜,我已收到你的消息");} };private final static Messenger mReplyMessager = new Messenger(mHandler);還有一個(gè)比較關(guān)鍵的地方,就是要在客戶端發(fā)送消息的時(shí)候把客戶端的Messager通過消息傳送到服務(wù)器端
(msg.replyTo =mReplyMessager)
private class MyServiceConnection implements ServiceConnection {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Messenger messenger = new Messenger(service);Message msg = Message.obtain();//通過msg把客戶端的Messager傳送到服務(wù)器端(關(guān)鍵代碼)msg.replyTo =mReplyMessager;try {messenger.send(msg);} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {} }這樣一來,服務(wù)器端和客戶端就能很好的實(shí)現(xiàn)跨進(jìn)程通信了.
如果需要傳送數(shù)據(jù)的話,可以通過Bundle設(shè)置數(shù)據(jù),除了基本數(shù)據(jù)類型,還可以通過消息傳送可序列化的對(duì)象
發(fā)送方:
Message msg = Message.obtain();Bundle bundle = new Bundle();//傳輸序列化對(duì)象//bundle.putParcelable();//bundle.putSerializable();msg.setData(bundle);接收方:
Bundle data = msg.getData();//獲取數(shù)據(jù)//data.getSerializable()//data.getParcelable()3.2.3 弊端
上面我們已經(jīng)實(shí)現(xiàn)了跨進(jìn)程通信,但是這里面其實(shí)是有弊端的,服務(wù)端處理客戶端的消息是串行的,必須一個(gè)一個(gè)來處理,所以如果是并發(fā)量比較大的時(shí)候,通過Messager來通信就不太適合了
3.2.4 注意
上面演示的是應(yīng)用內(nèi)跨進(jìn)程通信,綁定服務(wù)可以通過顯示意圖來綁定,但是如果是跨應(yīng)用的進(jìn)程間通信,那么就需要用到隱式意圖了.這里有一點(diǎn)需要注意的就是,在5.0以后隱式意圖開啟或者綁定service要setPackage(Service的包名),不然會(huì)報(bào)錯(cuò)
mIntent = new Intent();//設(shè)置Package為Service的包名mIntent.setPackage("com.xiayu.ipcservice");mIntent.setAction("myMessager");3.3 AIDL
上面提到過通過Meaager跨進(jìn)程不適合并發(fā)量大的情況,那么如果并發(fā)量大的話,我們用什么來處理呢?那就可以通過AIDL來進(jìn)行,這里是Google的描述
Note: 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.主要意思就是你可以用Messager處理簡(jiǎn)單的跨進(jìn)程通信,但是高并發(fā)量的要用AIDL
我們還是先演示一下同一個(gè)應(yīng)用內(nèi)的跨進(jìn)程通信
3.3.1 服務(wù)端
首先我們創(chuàng)建一個(gè)Service
public class AIDLService extends Service {@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;} }然后在清單文件里面設(shè)置Service的進(jìn)程
<service android:name=".AIDLService"android:process=":xiayu"/>然后右鍵選擇新建AIDL文件,Android Studio就會(huì)幫你在你的aidl目錄的同名文件夾下面創(chuàng)建一個(gè)AIDL文件
// IShop.aidl package com.xiayu.aidldemo;interface IShop {//此方法是創(chuàng)建aidl自帶的方法,告知你可以使用那些數(shù)據(jù)類型void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString); }
在AIDL文件里面會(huì)有一個(gè)接口,并聲明了一個(gè)方法,那個(gè)方法主要是告訴你AIDL支持哪些數(shù)據(jù)類型傳輸,所以我們把這個(gè)方法刪掉,我們?cè)僮约郝暶饕粋€(gè)方法,用于之后的調(diào)用
(注意:每次修改了AIDI文件后,需要同步一下才會(huì)生效,因?yàn)槊看瓮胶?Android Studio會(huì)在 項(xiàng)目/build/generated/source/aidl/debug 目錄下生成相應(yīng)的java文件)
interface IShop {//自己聲明的方法,用于之后的調(diào)用void sell(); }我們?cè)赟ervice中創(chuàng)建一個(gè)Binder,并在onbind的時(shí)候返回
public class AIDLService extends Service{private Binder mBinder = new IShop.Stub() {@Overridepublic void sell() throws RemoteException {System.out.println("客官,您需要點(diǎn)什么?");}};@Nullable@Overridepublic IBinder onBind(Intent intent) {return mBinder;} }3.3.2客戶端
創(chuàng)建自定義一個(gè)類實(shí)現(xiàn)ServiceConnection
private class XiayuConnection implements ServiceConnection{@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//綁定成功時(shí)會(huì)調(diào)用這個(gè)方法}@Overridepublic void onServiceDisconnected(ComponentName name) {} }綁定服務(wù),當(dāng)綁定成功時(shí)會(huì)走Connection的onServiceConnected方法,并把Binder傳過來
mIntent = new Intent(this, AIDLService.class); mXiayuConnection = new XiayuConnection(); bindService(mIntent, mXiayuConnection, BIND_AUTO_CREATE);在onServiceConnected方法里面通過asInterface獲取服務(wù)器傳過來的對(duì)象,并調(diào)用服務(wù)端的方法
@Override public void onServiceConnected(ComponentName name, IBinder service) {//獲取到服務(wù)器傳過來的對(duì)象IShop iShop = IShop.Stub.asInterface(service);try {iShop.sell();} catch (RemoteException e) {e.printStackTrace();} }現(xiàn)在客戶端就可以調(diào)用sell方法來進(jìn)行跨進(jìn)程通信了,但目前只能傳輸基本數(shù)據(jù)類型的數(shù)據(jù),那么如果想要傳其他數(shù)據(jù)呢?那么我們接著往下講
3.3.3 通過AIDL傳送復(fù)雜數(shù)據(jù)
首先我們要知道AIDL支持那么數(shù)據(jù)類型
- 1.基本數(shù)據(jù)類型
- 2.實(shí)現(xiàn)了Parcelable接口的對(duì)象
- 3.List:只支持ArrayList,并且里面的元素需要時(shí)AIDL支持的
- 4.Map:只支持HashMap,并且里面的key和value都需要是被AIDL支持的
那么我們定義一個(gè)對(duì)象Product實(shí)現(xiàn)Parcelable接口,如何實(shí)現(xiàn)Parcelable我這里也不重復(fù)介紹了,如果不了解的朋友可以看看筆者之前寫的這篇文章
序列化–Serializable與Parcelable
http://blog.csdn.net/yulyu/article/details/56481665
Product我們?cè)O(shè)置了兩個(gè)字段
public class Product implements Parcelable {public String name;public int price;... }接著我們需要在aidl文件夾的相同目錄創(chuàng)建一個(gè)相同文件名的aidl文件
(注意,這里我們是要通過new File的方式創(chuàng)建,并且要自己輸入文件后綴aidl,如果你用new AIDL的方式創(chuàng)建的話,他會(huì)提示你Interface Name must be unique)?
接著我們需要在這個(gè)aidl文件里面輸入包名,并且聲明一下變量為Parcelable類型
(注意,這里聲明的時(shí)候是用小寫的parcelable)
// Product.aidl package com.xiayu.aidldemo;parcelable Product;我們回到之前的IShop.aidl,刪掉之前的sell方法,并再創(chuàng)建兩個(gè)新方法
// IShop.aidl package com.xiayu.aidldemo;import com.xiayu.aidldemo.Product;interface IShop {Product buy();void setProduct(in Product product);}這里有三個(gè)需要注意的地方
(1)IShop.aidl雖然跟Product.aidl在同一個(gè)包下,但是這里還是需要手動(dòng)import進(jìn)來
(2)這里聲明方法時(shí),需要在參數(shù)前面增加一個(gè)tag,這個(gè)tag有三種,in,out,inout,這里表示的是這個(gè)參數(shù)可以支持的流向:
- 1.in: 這個(gè)對(duì)象能夠從客戶端到服務(wù)器,但是作為返回值從服務(wù)器到客戶端的話數(shù)據(jù)不會(huì)傳送過去(不會(huì)為null,但是字段都沒有賦值)
- 2.out: 這個(gè)對(duì)象能夠作為返回值從服務(wù)器到客戶端,但是從客戶端到服務(wù)器數(shù)據(jù)會(huì)為空(不會(huì)為null,但是字段都沒有賦值)
- 3.inout: 能從客戶端到服務(wù)器,也可以作為返回值從服務(wù)器到客戶端
用一張圖來總結(jié):?
(不要都設(shè)為inout,要看需求來設(shè)置,因?yàn)闀?huì)增加開銷)
(3)默認(rèn)實(shí)現(xiàn)Parcelable的模版只支持in ,如果需要需要支持out或inout需要手動(dòng)實(shí)現(xiàn)readFromParcel方法
@Override public void writeToParcel(Parcel dest, int flags) {dest.writeString(this.name);dest.writeInt(this.price); } //手動(dòng)實(shí)現(xiàn)這個(gè)方法 public void readFromParcel(Parcel dest) {//注意,這里的讀取順序要writeToParcel()方法中的寫入順序一樣name = dest.readString();price = dest.readInt(); }現(xiàn)在就可以在客戶端中通過IShop調(diào)用方法來進(jìn)行通信了
@Override public void onServiceConnected(ComponentName name, IBinder service) {IShop iShop = IShop.Stub.asInterface(service);try {//調(diào)用方法進(jìn)行通信iShop.setProduct(mProduct);Product buy = iShop.buy();} catch (RemoteException e) {e.printStackTrace();} }3.4 不同應(yīng)用間的多進(jìn)程通信(AIDL)
上面我們介紹了同一個(gè)應(yīng)用內(nèi)的進(jìn)程間通信,接下來我們就來介紹不同應(yīng)用之間的進(jìn)程間通信
3.4.1 服務(wù)器端
首先我們需要把Product.java放到aidl目錄相同名字的文件夾下(如果要提供服務(wù)給其他app,最好把需要的對(duì)象都放在aidl目錄下,這樣比較容易拷貝)?
但是這個(gè)時(shí)候你運(yùn)行程序的話,編譯會(huì)提示說找不到Product,那是因?yàn)锳ndroid Studio默認(rèn)會(huì)去java目錄下找,這時(shí)候需要在build.gradle文件 android{ } 中間增加一段代碼,讓aidl目錄里面的java文件也能被識(shí)別
sourceSets {main {java.srcDirs = ['src/main/java', 'src/main/aidl']} }接著我們?yōu)镾ervice增加intent-filter,這樣其他應(yīng)用才能通過隱式意圖綁定服務(wù),服務(wù)器端的修改就結(jié)束了
<service android:name=".AIDLService"android:process=":xiayu"><intent-filter><action android:name="action.xiayu"/></intent-filter> </service>3.4.2 客戶端
我們需要?jiǎng)?chuàng)建一個(gè)新的應(yīng)用來作為客戶端,并且把服務(wù)器端的aidl目錄下的所有文件都拷貝過來,這里要注意的就是里面的目錄不能改變,需要與以前一致
點(diǎn)擊同步,Android Studio會(huì)自動(dòng)生成相應(yīng)的java文件供我們使用
這個(gè)時(shí)候我們需要通過隱式意圖來綁定服務(wù)了
(注意:5.0以后隱式意圖開啟或者綁定service要setPackage,不然會(huì)報(bào)錯(cuò))
mIntent.setAction("action.xiayu");mIntent.setPackage("com.xiayu.aidldemo");接下來的操作就和之前一樣了,創(chuàng)建一個(gè)類實(shí)現(xiàn)ServiceConnection
private class XiayuConnection implements ServiceConnection {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//TODO}@Overridepublic void onServiceDisconnected(ComponentName name) {} }綁定服務(wù)
bindService(mIntent, mXiayuConnection, BIND_AUTO_CREATE);通過ServiceConnection的onServiceConnected里面的IBinder進(jìn)行通信
@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {IShop iShop = IShop.Stub.asInterface(service);try {iShop.setProduct(mProduct);Product buy = iShop.buy();System.out.println("buy=" + buy.price);} catch (RemoteException e) {e.printStackTrace();}}解除綁定的時(shí)候釋放資源
public void unbind(View v) {unbindService(mXiayuConnection);mXiayuConnection = null;mIShop = null; }這樣我們就可以通過獲得的IShop進(jìn)行不同應(yīng)用之間的進(jìn)程間通信了
最后再提幾點(diǎn)用到服務(wù)時(shí)需要注意的地方(很簡(jiǎn)單,但是有些人經(jīng)常會(huì)忽略這幾點(diǎn))
- 1: startService和stopService需要用同一個(gè)Intent對(duì)象
- 2: bindService和unbindService需要用同一個(gè)ServiceConnection對(duì)象
- 3: 5.0以后隱式意圖開啟或者綁定service要setPackage(包名)
原文地址: http://blog.csdn.net/yulyu/article/details/56676262
總結(jié)
以上是生活随笔為你收集整理的进程间通信--IPC的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Telephony分析(
- 下一篇: android调试神器Stetho