天天看點

IPC

一、Linux知識點

  • Linux環境下,程序位址空間互相獨立,每個程序各自有不同的使用者位址空間。
  • 程序隔離:是一個程序不能直接操作或通路另一個程序
  • 核心空間(Kernel  Space)是Linux核心運作空間,使用者空間(User  Space)是使用者程式運作空間;核心空間的資料是共享的,而使用者空間則不可以資料共享。
  • 系統調用:使用者空間需要通路核心空間,就需要借助系統調用來實作,這是使用者空間通路核心空間的唯一方式,提升了系統安全性和穩定性。
  • 使用者程序互動:通過系統函數和核心空間進行互動,在核心中開辟一塊緩存區,程序1把資料從使用者空間拷貝到核心緩存區,程序2再從核心緩存區把資料讀取,這就是一次IPC,需要拷貝兩次。

        copy_from_user   将使用者空間的資料拷貝到核心空間的核心緩存區

        copy_to_user   将核心空間的核心緩存區的資料拷貝到使用者空間

IPC

    缺點:接收資料的緩存是要由接收方提供,但接收方不知道需要多大的緩存

    方案:

  1. 開辟盡量大的,浪費空間,
  2. 先擷取消息大小,浪費時間
  • Linux提供了多種程序間通信機制,這些都是需要核心空間做支援,主要有管道(pipe),信号(sinal),消息隊列(Message),共享記憶體(Share Memory),套接字(socket)。
IPC
  • 程序A和程序B可以通過系統函數和核心空間進行互動,得copy兩次,效率低下,接收資料的緩存是要由接收方提供,但接收方不知道需要多大的緩存(解決方式1:開辟盡量大的,浪費空間,方式2:先擷取消息大小,浪費時間)

        copy_from_user   将使用者空間的資料拷貝到核心空間的核心緩存區

        copy_to_user   将核心空間的核心緩存區的資料拷貝到使用者空間

IPC
  • 記憶體映射

       記憶體映射(Memory-mapped)使一個磁盤檔案與存儲空間中的一個緩存區相映射,當從緩存區中取資料,就相當于讀檔案中的相應位元組,于此類似,将資料存入緩存區,則相應的位元組就自動寫入檔案。

      使用這種方法,首先應通知核心,将一個指定檔案映射到存儲區域中,這個映射工作通過mmap函數來實作。

IPC

二、Binder

1、Binder驅動

     Binder不是Linux系統核心的一部分,要實作IPC通行,得益于Linux的動态核心可加載子產品(LKM)的機制;子產品是具有獨立功能的程式,它可以被單獨編譯,但是不能獨立運作,它在運作時被連結到核心作為核心的一部分運作。Android系統通過動态添加一個核心子產品運作在核心空間,這個核心子產品就是Binder驅動。

2、Binder  IPC底層通信原理

IPC

通信步驟如下:

  1. Binder驅動在核心空間建立一個資料接收緩存區
  2. 建立核心緩存區與資料接收緩存區(在核心中)之間的映射關系,以及資料接收緩存區和接收程序使用者空間位址的映射關系
  3. 發送方程序通過系統調用copy_from_user()函數将資料copy到核心中的核心緩存區中,由于核心緩存區和接收程序的使用者空間存在記憶體映射,是以也就相當于把資料發送到了接收程序的使用者空間,整個過程隻用了1次拷貝,就完成了一次單行通行。  

3、Binder通信過程

  1. 一個程序通過Binder驅動将自己注冊成為ServiceManager
  2.  servicer通過驅動向ServiceManager中注冊Binder(Service的binder實體),驅動為這個Binder建立實體的引用,并在ServiceManager中加入到查找表。
  3. client通過名字,在Binder驅動下從ServiceManager中擷取對Binder實體的引用,通過這個引用就能實作和Server程序的通信。
IPC

4、Binder通信中的代理模式

     A程序想擷取B程序的object時,驅動并不會真的把object傳回給A,而是傳回了一個object看起來一模一樣的代理對象objectProxy,在A程序中調用objectProxy的方法,會通知Binder驅動,Binder查詢自己維護的表單,發現是B程序object的代理對象,于是通知B程序調用該方法,并要求B程序把傳回結構發給自己。當驅動拿到B程序傳回的結果後會轉發給A程序,一次通信就完成了。

三、程序間通信

(1)程序間通信方式對比

名稱 優點 缺點 适用場景
intent 簡單易用 資料類型為了Bundle支援的 四大元件的程序間通信
檔案共享 簡單易用 不适合高并發 資料共享
AIDL 功能強大,支援一對多并發實時通信 線程同步 複雜的程序間通信,最常用
Messenger 比aidl簡單 串行 簡單的程序間通信
ContentProvider 資料共享
RemoteViews
Socket 跨主機,通信範圍廣 傳輸原始的位元組流 網絡通信

(2)開啟多程序

         在Androidmenifest中給四大元件指定android:process開啟多程序模式

(3)多程序造成的問題(各個程序都是獨立的虛拟機)

  • 靜态成員和單例模式完全失效
  • 線程同步機制完全失效
  • SharedPreferences的可靠性下降:因為sp不支援兩個程序并發進行讀寫,有一定幾率導緻資料丢失
  • Application會多次建立

四、序列化

(1)Bundle傳遞對象為啥需要序列化

         因為Bundle傳遞資料時隻支援基本資料類型,是以在傳遞對象時需要序列化轉換成可存儲或可傳輸的本質狀态(位元組流)。

(2)序列化的兩種方式

Serializable接口 Parcelable接口
平台 java提供的 android提供的
序列化原理 将一個對象裝換成可存儲或可存儲的狀态 将一個對象程序分解,分解後的每一部分都是傳遞可支援的資料類型
優缺點 簡單,效率低,開銷大,大量I/O操作 高效,使用較麻煩
使用場景 适合将對象序列化到儲存設備或将對象系列化通過網絡裝置傳輸 主要用在記憶體的序列化

(3)代碼示例

public class Student implements Serializable {
    private static final long serialVersionUID = -4211449776975163975L;
    
    private String name;
}

public class Student implements Parcelable {
  
    private String name;

    protected Student(Parcel in) {
        name = in.readString();
    }

    //序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    //反序列化
    public static final Creator<Student> CREATOR = new Creator<Student>() {
        @Override
        public Student createFromParcel(Parcel in) {
            return new Student(in);
        }

        @Override
        public Student[] newArray(int size) {
            return new Student[size];
        }
    };
}
           

五、Messenger

       Messenger是一種輕量級的IPC方案,它的底層實作是AIDL,它是以串行的方式來處理用戶端發來的消息,是以在服務端我們不需要考慮線程同步的問題。

public Messenger(Handler target) {
  mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
  mTarget = IMessenger.Stub.asInterface(target);
}
           

實作事例:

     服務端:onbind方法傳回自己封裝好的Messenger,msg擷取的replyTo屬性是用戶端封裝的messenger

public class MessengerService extends Service {

    private static class Messengerhandler extends Handler {
       @Override
       public void handleMessage(Message msg) {
          switch (msg.what){
            case 1:
                //接受用戶端用msg鍵發送的消息
                msg.getData().getString("msg")

                //給用戶端發消息
                Messenger  client=msg.replyTo;
                client.send(message);             

                break;
          }
          super.handleMessage(msg);
       }
   }

private final Messenger mMessenger = new Messenger(new Messengerhandler());

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
   }
}
           

      用戶端:onserviceConnected擷取服務端傳回的messenger,發送消息時給msg.replyTo指派為自己封裝的messenger

private  Messenger  mService;

void bindMyMessengerService(){
    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            sendData();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };
    Intent intent = new Intent(this,MessengerService.class);
    bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}

private Messenger mCilentMessenger = new Messenger(new ClientMessenger());
private static class ClientMessenger extends Handler{
    @Override
    public void handleMessage(Message msg) {
       switch (msg.what){
          case 1:
           //服務端發來的消息
           msg.getData().getString("reply"));
           break;
}


void sendData(){
    Message msg = Message.obtain(null,1);
     msg.replyTo = mCilentMessenger;//設定用戶端接受消息的Messenger
    Bundle data = new Bundle();
    data.putString("msg","用戶端的消息");
    msg.setData(data);
    try{
        mService.send(msg);
    }catch (RemoteException e) {
        e.printStackTrace();
    }
}
           

六、AIDL

(1)AIDL的定義

         全稱Android  Interface  Definition  Language,Android接口定義語言;如果在一個程序中要調用另一個程序中對象的方法,可使用AIDL生成可序列化的參數,AIDL會生成一個服務端對象的代理類,通過它用戶端實作間接調用服務端對象的方法。

本質是系統提供了一套可快速實作BInder的工具。

(2)AIDL檔案支援的資料類型:

  • 基本資料類型(int  long  char boolean  double等)
  • String和CharSequence
  • List:隻支援ArrayList,裡面每個元素都必須能夠被AIDL支援
  • Map:值支援HashMap,裡面的每個元素都必須被AIDL支援,包括key和value
  • Parcelable :所有實作了Parcelable接口的對象
  • AIDL:所有的AIDL接口本身也可以在AIDL檔案中使用, 其中自定義的Parcelable對象和AIDL對象必須顯示import進來,并且還要建立同名的.aidl檔案

(3)aidl運用

  服務端:

(1)建立一個aidl類型的檔案(裡面定義了一個接口),檔案最好在main檔案夾下建立一個檔案夾aidl,不要在java檔案夾下

package aidl;

// Declare any non-default types here with import statements
interface MyAIDLService {
    
    int getNum(int num1,int num2);
}
           

然後build下項目,我們可以在build-->genetated-->aidl-->debug下生一個同名的aidl檔案,說明aidl檔案便宜成功。如下圖:

IPC

(2)建立Service,并建立中間類,中間類要實作上面定義的aidl.Stub,執行個體代碼如下

package shop.lantian.com.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;

import aidl.MyAIDLService;

public class AidlService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyServiceBind();
    }

    class MyServiceBind extends MyAIDLService.Stub {

        @Override
        public int getNum(int num1, int num2) throws RemoteException {
            return num1 + num2;
        }
    }
}
           

(3)配置AndroidManifest.xml,執行個體如下,

<service android:name=".AidlService">
      <intent-filter>
        <action android:name="shop.lantian.com.service.AidlService"/>
      </intent-filter>
 </service>
           

用戶端:

(1)複制服務端的aidl檔案原封不動的複制到用戶端(注意,包名路徑要一模一樣)

IPC

(2)建立ServiceConnection接口的執行個體,覆寫onServiceConnected方法和onServiceDisConnected,重點在OnServiceConnection方法

private MyAIDLService mBinder;

    public class MainServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinder = MyAIDLService.Stub.asInterface(service);
            if (mBinder != null) {
                try {
                    int end = mBinder.getNum(1, 2);
                    Log.e("aaa", String.valueOf(end));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
           

(3)調用service

MainServiceConnection connection = new MainServiceConnection();
 Intent intent = new Intent();
 intent.setAction("shop.lantian.com.service.AidlService");
 //從 Android 5.0開始 隐式Intent綁定服務的方式已不能使用,是以這裡需要設定Service所在服務端的包名
 intent.setPackage("shop.lantian.com.service");
 bindService(intent, connection, BIND_AUTO_CREATE);
           

七、AIDL源碼分析

       Binder是Android的一個類,它實作了IBinder的接口,是Android中的一種跨程序通信方式。

public interface IBookManager extends android.os.IInterface {
/**
 * Local-side IPC implementation stub class.
 */
    public static abstract class Stub extends android.os.Binder implements com.xsx.ipcdemo.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.xsx.ipcdemo.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.xsx.ipcdemo.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.xsx.ipcdemo.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.xsx.ipcdemo.IBookManager))) {
                return ((com.xsx.ipcdemo.IBookManager) iin);
            }
            return new com.xsx.ipcdemo.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.xsx.ipcdemo.Booker> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.xsx.ipcdemo.Booker _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.xsx.ipcdemo.Booker.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.xsx.ipcdemo.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.xsx.ipcdemo.Booker> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.xsx.ipcdemo.Booker> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.xsx.ipcdemo.Booker.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.xsx.ipcdemo.Booker booker) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((booker != null)) {
                        _data.writeInt(1);
                        booker.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public java.util.List<com.xsx.ipcdemo.Booker> getBookList() throws android.os.RemoteException;

    public void addBook(com.xsx.ipcdemo.Booker booker) throws android.os.RemoteException;
}
           
  • 實作AIDL的接口都得實作android.os.IInterface接口
  • 内部類Stub類,BInder的實作類,服務端聽過這個類來提供服務。
  • DESCRIPTOR----唯一辨別,辨別的是哪個Stub,一般用目前Binder的類名的完整路徑表示,    
  • asInterface(android.os.IBinder   obj)

        用于将服務端的Binder對象轉換為用戶端所需要的AIDL接口類型的對象,這種轉換過程是區分程序的,如果用戶端和服務端位于同一程序,那麼此方法傳回的就是服務端的Stub本身,否則傳回的是系統封裝後的Stub.proxy對象。

  • asBinder()    根據目前調用情況傳回代理Proxy的Binder對象
  • onTransact()

       運作在服務端中的Binder線程池中,當用戶端發起跨程序請求時,遠端請求會通過系統底層封裝後交由此方法來處理

       public  Boolean  onTransact(int code,android.os.parcel  data,android.os.Parcel  reply,int  flags)

       傳回值為false辨別用戶端的請求會失敗,可以利用這個特性來做權限驗證;code是辨別用戶端調用的方法,data是目标方法所需要的參數,reply是目标方法執行完畢後,向reply中寫入傳回值。

  • transact()

   運作在用戶端,當用戶端發起遠端請求的同時将目前線程挂起,之後調用服務端的onTransact()直到遠端請求傳回,目前線程才繼續執行

  • Proxy#用戶端定義的method

        當用戶端調用此方法時,首先建立該方法所需要的輸入型Parcel對象_data,輸出型Parcel對象_reply和傳回值對象,然後把該方法的參數資訊寫入data中,接着調用transact方法來發起RPC(遠端過程調用)請求,同時目前線程挂起,然後服務端的onTransact方法會被調用,知道RPC過程傳回後,目前線程繼續執行,并從_reply中取出傳回的結果。

注:當用戶端發起請求時,由于目前線程會被挂起直至伺服器程序傳回資料,是以如果一個遠端方法是很耗時的,那麼不能再UI線程中發起遠端請求;其次由于服務端的Binder方法運作在BInder的線程池中,是以Binder方法不管是否耗時都應該采用同步的方式去實作,因為它已經運作在一個線程中了。