摘要:前言進程間通信簡稱就是指進程與進程之間進行通信一般來說一個只有一個進程但是可能會有多個線程所以我們用得比較多的是多線程通信比如但是在一些特殊的情況下我們會需要多個進程或者是我們在遠程服務調用時就需要跨進程通信了設置多進程設置多進程的步驟很
前言:
進程間通信(Inter-Process Communication),簡稱IPC,就是指進程與進程之間進行通信.一般來說,一個app只有一個進程,但是可能會有多個線程,所以我們用得比較多的是多線程通信,比如handler,AsyncTask.
但是在一些特殊的情況下,我們app會需要多個進程,或者是我們在遠程服務調用時,就需要跨進程通信了
1.設置多進程Android設置多進程的步驟很簡單,只用在清單文件中為四大組件加上process屬性
( :messager 最終的進程名會變成 包名+:messager)
雖然多進程設置起來很簡單,但是使用的時候卻會有一系列的問題
(兩個進程對應的是不同的內存區域)
1.Application對象會創建多次
2.靜態成員不共用
3.同步鎖失效
4.單例模式失效
5.數據傳遞的對象必須可序列化
2.可序列化進程間通信傳遞的對象是有嚴格要求的,除了基本數據類型,其他對象要想可以傳遞,必須可序列化,Android實現可序列化一般是通過實現Serializable或者是Parcelable
如果你在進程通信中不需要傳非基本數據類型的對象,那么你可以不了解序列化,但是可序列化是進程間通信的基礎,所以還是建議不了解的朋友先熟悉一下
筆者之前介紹過序列化的相關知識,這里就不重復介紹了
序列化--Serializable與Parcelable
http://blog.csdn.net/yulyu/ar...
3.通信跨進程通信的方法有很多,比如通過Intent傳遞,通過AIDL以及Messager通信,通過socket通信,這里主要介紹的是基于Binder的AIDL和Messager
3.1 IntentIntent進行數據的傳遞是我們平時最常用的,他的原理其實是對于Binder的封裝,但是他只能做到單向的數據傳遞,所以并不能很好的實現跨進程通信,我們這里就不展開來介紹了
3.2 MessagerMessager的底層也是基于Binder的,其實應該說他是在AIDL的基礎上封裝了一層
一般來說安卓中使用Binder主要是通過綁定服務(bindService),服務端(這里指的不是后臺,是指其中一個進程)主要是運行Service,客戶端通過bindService獲取到相關的Binder,Binder就作為橋梁進行跨進程的通信.
這里我們先演示同一個應用內的多進程通信
3.2.1 服務器端首先我們先創建一個Service,
public class XiayuService extends Service{ @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
并在清單文件中配置他的進程
在Service里面創建一個Hander用來接受消息
private final static Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { System.out.println("地瓜地瓜,我是土豆,我是土豆, 聽到請回答,聽到請回答"); } };
在Service里面創建一個Messager,并把Handler放入其中
private final static Messenger mMessenger = new Messenger(mHandler);
重寫onbind方法,返回Messager里面的Binder
public IBinder onBind(Intent intent) { return mMessenger.getBinder(); }3.2.2 客戶端
創建一個對象實現ServiceConnection
private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { //當連接上服務后會調用這個方法 //TODO } @Override public void onServiceDisconnected(ComponentName name) { } }
綁定服務
Intent intent = new Intent(MainActivity.this, XiayuService.class); MyServiceConnection myServiceConnection = new MyServiceConnection(); bindService(intent, myServiceConnection, BIND_AUTO_CREATE);
綁定服務后,會調用ServiceConnection的onServiceConnected方法,通過Messager發送消息,服務器端的Handler就能夠收到消息了
private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { //通過Binder創建Messager Messenger messenger = new Messenger(service); //創建msg Message msg = Message.obtain(); try { //通過Messager發送消息 messenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }
這樣的話我們就能夠通過bindService獲取到一個包含Binder的Messager進行通信了,但是我們目前只實現了客戶端對服務器端傳遞消息,那么服務器端如何對客戶端傳遞消息呢?
我們先對服務器端的代碼進行修改,首先修改Service的Handler
(關鍵代碼是 Messenger messenger = msg.replyTo)
private final static Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { System.out.println("地瓜地瓜,我是土豆,我是土豆, 聽到請回答,聽到請回答"); //獲取Messager Messenger messenger = msg.replyTo; //創建消息 Message msg_reply = Message.obtain(); try { //發送 messenger.send(msg_reply); } catch (RemoteException e) { e.printStackTrace(); } } };
接著我們在客戶端也增加一個Handler和Messager來處理消息
private final static Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { System.out.println("土豆,土豆,我是地瓜,我已收到你的消息"); } }; private final static Messenger mReplyMessager = new Messenger(mHandler);
還有一個比較關鍵的地方,就是要在客戶端發送消息的時候把客戶端的Messager通過消息傳送到服務器端
(msg.replyTo =mReplyMessager)
private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { Messenger messenger = new Messenger(service); Message msg = Message.obtain(); //通過msg把客戶端的Messager傳送到服務器端(關鍵代碼) msg.replyTo =mReplyMessager; try { messenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }
這樣一來,服務器端和客戶端就能很好的實現跨進程通信了.
如果需要傳送數據的話,可以通過Bundle設置數據,除了基本數據類型,還可以通過消息傳送可序列化的對象
發送方:
Message msg = Message.obtain(); Bundle bundle = new Bundle(); //傳輸序列化對象 //bundle.putParcelable(); //bundle.putSerializable(); msg.setData(bundle);
接收方:
Bundle data = msg.getData(); //獲取數據 //data.getSerializable() //data.getParcelable()3.2.3 弊端
上面我們已經實現了跨進程通信,但是這里面其實是有弊端的,服務端處理客戶端的消息是串行的,必須一個一個來處理,所以如果是并發量比較大的時候,通過Messager來通信就不太適合了
3.2.4 注意上面演示的是應用內跨進程通信,綁定服務可以通過顯示意圖來綁定,但是如果是跨應用的進程間通信,那么就需要用到隱式意圖了.這里有一點需要注意的就是,在5.0以后隱式意圖開啟或者綁定service要setPackage(Service的包名),不然會報錯
mIntent = new Intent(); //設置Package為Service的包名 mIntent.setPackage("com.xiayu.ipcservice"); mIntent.setAction("myMessager");3.3 AIDL
上面提到過通過Meaager跨進程不適合并發量大的情況,那么如果并發量大的話,我們用什么來處理呢?那就可以通過AIDL來進行,這里是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處理簡單的跨進程通信,但是高并發量的要用AIDL
我們還是先演示一下同一個應用內的跨進程通信
3.3.1 服務端首先我們創建一個Service
public class AIDLService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
然后在清單文件里面設置Service的進程
然后右鍵選擇新建AIDL文件,Android Studio就會幫你在你的aidl目錄的同名文件夾下面創建一個AIDL文件
// IShop.aidl package com.xiayu.aidldemo; interface IShop { //此方法是創建aidl自帶的方法,告知你可以使用那些數據類型 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); }
在AIDL文件里面會有一個接口,并聲明了一個方法,那個方法主要是告訴你AIDL支持哪些數據類型傳輸,所以我們把這個方法刪掉,我們再自己聲明一個方法,用于之后的調用
(注意:每次修改了AIDI文件后,需要同步一下才會生效,因為每次同步后,Android Studio會在 項目/build/generated/source/aidl/debug 目錄下生成相應的java文件)
interface IShop { //自己聲明的方法,用于之后的調用 void sell(); }
我們在Service中創建一個Binder,并在onbind的時候返回
public class AIDLService extends Service{ private Binder mBinder = new IShop.Stub() { @Override public void sell() throws RemoteException { System.out.println("客官,您需要點什么?"); } }; @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } }3.3.2客戶端
創建自定義一個類實現ServiceConnection
private class XiayuConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { //綁定成功時會調用這個方法 } @Override public void onServiceDisconnected(ComponentName name) { } }
綁定服務,當綁定成功時會走Connection的onServiceConnected方法,并把Binder傳過來
mIntent = new Intent(this, AIDLService.class); mXiayuConnection = new XiayuConnection(); bindService(mIntent, mXiayuConnection, BIND_AUTO_CREATE);
在onServiceConnected方法里面通過asInterface獲取服務器傳過來的對象,并調用服務端的方法
@Override public void onServiceConnected(ComponentName name, IBinder service) { //獲取到服務器傳過來的對象 IShop iShop = IShop.Stub.asInterface(service); try { iShop.sell(); } catch (RemoteException e) { e.printStackTrace(); } }
現在客戶端就可以調用sell方法來進行跨進程通信了,但目前只能傳輸基本數據類型的數據,那么如果想要傳其他數據呢?那么我們接著往下講
3.3.3 通過AIDL傳送復雜數據首先我們要知道AIDL支持那么數據類型
1.基本數據類型
2.實現了Parcelable接口的對象
3.List:只支持ArrayList,并且里面的元素需要時AIDL支持的
4.Map:只支持HashMap,并且里面的key和value都需要是被AIDL支持的
那么我們定義一個對象Product實現Parcelable接口,如何實現Parcelable我這里也不重復介紹了,如果不了解的朋友可以看看筆者之前寫的這篇文章
序列化--Serializable與Parcelable
http://blog.csdn.net/yulyu/ar...
Product我們設置了兩個字段
public class Product implements Parcelable { public String name; public int price; ... }
接著我們需要在aidl文件夾的相同目錄創建一個相同文件名的aidl文件
(注意,這里我們是要通過new File的方式創建,并且要自己輸入文件后綴aidl,如果你用new AIDL的方式創建的話,他會提示你Interface Name must be unique)
接著我們需要在這個aidl文件里面輸入包名,并且聲明一下變量為Parcelable類型
(注意,這里聲明的時候是用小寫的parcelable)
// Product.aidl package com.xiayu.aidldemo; parcelable Product;
我們回到之前的IShop.aidl,刪掉之前的sell方法,并再創建兩個新方法
// IShop.aidl package com.xiayu.aidldemo; import com.xiayu.aidldemo.Product; interface IShop { Product buy(); void setProduct(in Product product); }
這里有三個需要注意的地方
(1)IShop.aidl雖然跟Product.aidl在同一個包下,但是這里還是需要手動import進來
(2)這里聲明方法時,需要在參數前面增加一個tag,這個tag有三種,in,out,inout,這里表示的是這個參數可以支持的流向:
1.in: 這個對象能夠從客戶端到服務器,但是作為返回值從服務器到客戶端的話數據不會傳送過去(不會為null,但是字段都沒有賦值)
2.out: 這個對象能夠作為返回值從服務器到客戶端,但是從客戶端到服務器數據會為空(不會為null,但是字段都沒有賦值)
3.inout: 能從客戶端到服務器,也可以作為返回值從服務器到客戶端
用一張圖來總結:
(不要都設為inout,要看需求來設置,因為會增加開銷)
(3)默認實現Parcelable的模版只支持in ,如果需要需要支持out或inout需要手動實現readFromParcel方法
@Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); dest.writeInt(this.price); } //手動實現這個方法 public void readFromParcel(Parcel dest) { //注意,這里的讀取順序要writeToParcel()方法中的寫入順序一樣 name = dest.readString(); price = dest.readInt(); }
現在就可以在客戶端中通過IShop調用方法來進行通信了
@Override public void onServiceConnected(ComponentName name, IBinder service) { IShop iShop = IShop.Stub.asInterface(service); try { //調用方法進行通信 iShop.setProduct(mProduct); Product buy = iShop.buy(); } catch (RemoteException e) { e.printStackTrace(); } }3.4 不同應用間的多進程通信(AIDL)
上面我們介紹了同一個應用內的進程間通信,接下來我們就來介紹不同應用之間的進程間通信
3.4.1 服務器端首先我們需要把Product.java放到aidl目錄相同名字的文件夾下(如果要提供服務給其他app,最好把需要的對象都放在aidl目錄下,這樣比較容易拷貝)
但是這個時候你運行程序的話,編譯會提示說找不到Product,那是因為Android Studio默認會去java目錄下找,這時候需要在build.gradle文件 android{ } 中間增加一段代碼,讓aidl目錄里面的java文件也能被識別
sourceSets { main { java.srcDirs = ["src/main/java", "src/main/aidl"] } }
接著我們為Service增加intent-filter,這樣其他應用才能通過隱式意圖綁定服務,服務器端的修改就結束了
3.4.2 客戶端
我們需要創建一個新的應用來作為客戶端,并且把服務器端的aidl目錄下的所有文件都拷貝過來,這里要注意的就是里面的目錄不能改變,需要與以前一致
點擊同步,Android Studio會自動生成相應的java文件供我們使用
這個時候我們需要通過隱式意圖來綁定服務了
(注意:5.0以后隱式意圖開啟或者綁定service要setPackage,不然會報錯)
mIntent.setAction("action.xiayu"); mIntent.setPackage("com.xiayu.aidldemo");
接下來的操作就和之前一樣了,創建一個類實現ServiceConnection
private class XiayuConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { //TODO } @Override public void onServiceDisconnected(ComponentName name) { } }
綁定服務
bindService(mIntent, mXiayuConnection, BIND_AUTO_CREATE);
通過ServiceConnection的onServiceConnected里面的IBinder進行通信
@Override public 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(); } }
解除綁定的時候釋放資源
public void unbind(View v) { unbindService(mXiayuConnection); mXiayuConnection = null; mIShop = null; }
這樣我們就可以通過獲得的IShop進行不同應用之間的進程間通信了
最后再提幾點用到服務時需要注意的地方(很簡單,但是有些人經常會忽略這幾點)
1: startService和stopService需要用同一個Intent對象
2: bindService和unbindService需要用同一個ServiceConnection對象
3: 5.0以后隱式意圖開啟或者綁定service要setPackage(包名)
熱門文章嘿,程序猿,你該學點經濟學了!
面試時,你被問到過 TCP/IP 協議嗎?
Glide-源碼詳解
漸進式加載-基礎講解
活用productFlavors
onTouch事件傳遞
那些年我們解決滑動沖突時遇過的坑
進程間通信--AIDL
序列化--Serializable與Parcelable
如何解決內存溢出以及內存泄漏
Okhttputils終極封裝
FaceBook推出的調試神器
Android代碼優化工具
Glide-入門教程
Glide-圖片預處理(圓角,高斯模糊等)
Glide-圖片的壓縮
Glide-內存緩存與磁盤緩存
Glide-自定義緩存
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67003.html
摘要:多進程通信之一命名管道。多進程通信之三信號量與共享內存。共享內存是最快是進程間通信方式,因為個進程之間并不需要數據復制,而是直接操控同一份數據。的一些書籍中甚至不建議新手輕易使用這種進程間通信的方式,因為這是一種極易產生死鎖的解決方案。 [原文地址:https://blog.ti-node.com/blog...] 往往開啟多進程的目的是為了一起干活加速效率,前面說了不同進程之間的內存...
閱讀 2064·2021-09-22 15:43
閱讀 8617·2021-09-22 15:07
閱讀 1078·2021-09-03 10:28
閱讀 2052·2021-08-19 10:57
閱讀 1061·2020-01-08 12:18
閱讀 2972·2019-08-29 15:09
閱讀 1521·2019-08-29 14:05
閱讀 1640·2019-08-29 13:57