進程間通訊 --IPC

*本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈javascript

前言:

進程間通訊(Inter-Process Communication),簡稱IPC,就是指進程與進程之間進行通訊.通常來講,一個app只有一個進程,可是可能會有多個線程,因此咱們用得比較多的是多線程通訊,好比handler,AsyncTask.java

可是在一些特殊的狀況下,咱們app會須要多個進程,或者是咱們在遠程服務調用時,就須要跨進程通訊了android

1.設置多進程

Android設置多進程的步驟很簡單,只用在清單文件中爲四大組件加上process屬性緩存

<service android:name=".MessagerService"
             android:process=":messager">
</service>複製代碼

( :messager 最終的進程名會變成 包名+:messager)服務器

雖然多進程設置起來很簡單,可是使用的時候卻會有一系列的問題微信

(兩個進程對應的是不一樣的內存區域)多線程

  • 1.Application對象會建立屢次
  • 2.靜態成員不共用
  • 3.同步鎖失效
  • 4.單例模式失效
  • 5.數據傳遞的對象必須可序列化

2.可序列化

進程間通訊傳遞的對象是有嚴格要求的,除了基本數據類型,其餘對象要想能夠傳遞,必須可序列化,Android實現可序列化通常是經過實現Serializable或者是Parcelable併發

若是你在進程通訊中不須要傳非基本數據類型的對象,那麼你能夠不瞭解序列化,可是可序列化是進程間通訊的基礎,因此仍是建議不瞭解的朋友先熟悉一下app

筆者以前介紹過序列化的相關知識,這裏就不重複介紹了less

序列化--Serializable與Parcelable

blog.csdn.net/yulyu/artic…

3.通訊

跨進程通訊的方法有不少,好比經過Intent傳遞,經過AIDL以及Messager通訊,經過socket通訊,這裏主要介紹的是基於Binder的AIDL和Messager

3.1 Intent

Intent進行數據的傳遞是咱們平時最經常使用的,他的原理實際上是對於Binder的封裝,可是他只能作到單向的數據傳遞,因此並不能很好的實現跨進程通訊,咱們這裏就不展開來介紹了

3.2 Messager

Messager的底層也是基於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 android:name=".XiayuService"
             android:process=":xiayu"
 />複製代碼

在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的進程

<service android:name=".AIDLService"
             android:process=":xiayu"
    />複製代碼

而後右鍵選擇新建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

blog.csdn.net/yulyu/artic…

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,這樣其餘應用才能經過隱式意圖綁定服務,服務器端的修改就結束了

<service android:name=".AIDLService"
         android:process=":xiayu">
    <intent-filter> <action android:name="action.xiayu"/> </intent-filter> </service>複製代碼

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(包名)

熱門文章

相關文章
相關標籤/搜索