Binder + AMS + AIDL大雜燴

參考文章:html

Binder

強推博客:java

按照參考文章裏說的,咱們最好先學習如下AIDL。android

AIDL

參考文章:安全

文章思路:bash

  • 爲何要設計這門語言?
  • 它有什麼語法?
  • 如何使用AIDL文件完成跨進程通訊?

AIDL是什麼?

AIDL 是 Android 接口定義語言。數據結構

AIDL 設計的初衷?

用來解決跨進程通訊,其實跨進程通訊還可使用BroadcastReceiverMessenger,可是BroadcastReceiver佔用的系統資源比較多,若是是頻繁的跨進程通訊是不可取的。Messenger進行跨進程通訊時只能同步,不能異步。app

AIDL 語法

  • 文件類型:xx.aidl
  • 支持類型:
    • Java中的八種基本數據類型,包括 byte,short,int,long,float,double,boolean,char。
    • String 類型。 CharSequence類型。
    • List類型:List中的全部元素必須是AIDL支持的類型之一,或者是一個其餘AIDL生成的接口,或者是定義的parcelable(下文關於這個會有詳解)。List可使用泛型。
    • Map類型:Map中的全部元素必須是AIDL支持的類型之一,或者是一個其餘AIDL生成的接口,或者是定義的parcelable。Map是不支持泛型的
  • 定向tag: 表示跨進程通訊中數據的流向。
    • in: 數據只能從客戶端流向服務端
    • out:數據只能從服務端流向客戶端
    • inout: 表示能夠在服務端和客戶端之間雙向流通。in 爲定向 tag 的話表現爲服務端將會接收到一個那個對象的完整數據,可是客戶端的那個對象不會由於服務端對傳參的修改而發生變更;out 的話表現爲服務端將會接收到那個對象的的空對象,可是在服務端對接收到的空對象有任何修改以後客戶端將會同步變更;inout 爲定向 tag 的狀況下,服務端將會接收到客戶端傳來對象的完整信息,而且客戶端將會同步服務端對該對象的任何變更。
  • 兩種AIDL文件:
    • 用來定義parcelable對象,以供其餘AIDL文件使用AIDL中非默認支持的數據類
    • 定義接口方法

下面是例子。異步

  1. 看一下整體的文件佈局:
    aidl.png

上面的5個文件就是咱們編寫的文件了。 2. 先編寫AIDL文件夾裏的文件 直接右鍵,new 一個 AIDL文件便可。 ide

AIDL2.png

Book.aidl 函數

aidl3.png

BookManager.aidl

aidl4.png

這裏要確保Book.java文件和Book.aidl文件在同一文件夾下,同時又能被編譯器找到,因此咱們要在build.gradle中加入:

sourceSets {
    main {
        java.srcDirs = ['src/main/java', 'src/main/aidl']
    }
}
複製代碼
  1. 編寫服務端代碼
public class AIDLService extends Service {

    public final String TAG = this.getClass().getSimpleName();

    //包含Book對象的list
    private List<Book> mBooks = new ArrayList<>();

    //由AIDL文件生成的BookManager
    private final BookManager.Stub mBookManager = new BookManager.Stub() {
        @Override
        public List<Book> getBooks() throws RemoteException {
            synchronized (this) {
                Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
                if (mBooks != null) {
                    return mBooks;
                }
                return new ArrayList<>();
            }
        }


        @Override
        public void addBook(Book book) throws RemoteException {
            synchronized (this) {
                if (mBooks == null) {
                    mBooks = new ArrayList<>();
                }
                if (book == null) {
                    Log.e(TAG, "Book is null in In");
                    book = new Book();
                }
                //嘗試修改book的參數,主要是爲了觀察其到客戶端的反饋
                book.setPrice(2333);
                if (!mBooks.contains(book)) {
                    mBooks.add(book);
                }
                //打印mBooks列表,觀察客戶端傳過來的值
                Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
            }
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Book book = new Book();
        book.setName("Android開發藝術探索");
        book.setPrice(28);
        mBooks.add(book);   
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
        return mBookManager;
    }
}
複製代碼

在清單文件中進行配置:

<service
    android:name=".service.AIDLService"
    android:exported="true">
        <intent-filter>
            <action android:name="com.lypeer.aidl"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
</service>
複製代碼
  1. 回到java文件夾下,編寫咱們的客戶端代碼
public class AIDLActivity extends AppCompatActivity {

    //由AIDL文件生成的Java類
    private BookManager mBookManager = null;

    //標誌當前與服務端鏈接情況的布爾值,false爲未鏈接,true爲鏈接中
    private boolean mBound = false;

    //包含Book對象的list
    private List<Book> mBooks;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
    }

    /**
     * 按鈕的點擊事件,點擊以後調用服務端的addBookIn方法
     *
     * @param view
     */
    public void addBook(View view) {
        //若是與服務端的鏈接處於未鏈接狀態,則嘗試鏈接
        if (!mBound) {
            attemptToBindService();
            Toast.makeText(this, "當前與服務端處於未鏈接狀態,正在嘗試重連,請稍後再試", Toast.LENGTH_SHORT).show();
            return;
        }
        if (mBookManager == null) return;

        Book book = new Book();
        book.setName("APP研發錄In");
        book.setPrice(30);
        try {
            mBookManager.addBook(book);
            Log.e(getLocalClassName(), book.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 嘗試與服務端創建鏈接
     */
    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setAction("com.lypeer.aidl");
        intent.setPackage("com.lypeer.ipcserver");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            attemptToBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(getLocalClassName(), "service connected");
            mBookManager = BookManager.Stub.asInterface(service);
            mBound = true;

            if (mBookManager != null) {
                try {
                    mBooks = mBookManager.getBooks();
                    Log.e(getLocalClassName(), mBooks.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(getLocalClassName(), "service disconnected");
            mBound = false;
        }
    };
}
複製代碼

以後就能夠開始通訊啦。 若是發現有問題,檢查下面配置:

aidl6.png

AIDL 原理

其實在寫完AIDL文件後,編譯器會幫咱們自動生成一個同名的 .java 文件。(圖是截圖參考文章的,寫的時候忘記截了~~~)

aidl5.png

  1. 從客戶端開始
public void onServiceConnected(ComponentName name, IBinder service) 
    mBookManager = BookManager.Stub.asInterface(service);
}
複製代碼

這裏的傳遞給了咱們一個service對象,通過Debug,發現它是AIDLService

aidl7.png

不深究它是怎麼來的。咱們接着看。 咱們看到這裏調用了asInterface方法,咱們點擊查看一下源碼:

public static com.lypeer.ipcclient.BookManager asInterface(android.os.IBinder obj) {
    //驗空
    if ((obj == null)) {
        return null;
    }
    //DESCRIPTOR = "com.lypeer.ipcclient.BookManager",搜索本地是否已經
    //有可用的對象了,若是有就將其返回
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.lypeer.ipcclient.BookManager))) {
        return ((com.lypeer.ipcclient.BookManager) iin);
    }
    //若是本地沒有的話就新建一個返回
    return new com.lypeer.ipcclient.BookManager.Stub.Proxy(obj);
}
複製代碼

返回一個proxy對象。這裏proxy對象就是客戶端跟服務端進行溝通的橋樑了。 2. proxy中方法作了什麼 咱們接着看proxy方法中作了什麼:

@Override
public java.util.List<com.lypeer.ipcclient.Book> getBooks() throws android.os.RemoteException {
    //很容易能夠分析出來,_data用來存儲流向服務端的數據流,
    //_reply用來存儲服務端流回客戶端的數據流
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.util.List<com.lypeer.ipcclient.Book> _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        //調用 transact() 方法將方法id和兩個 Parcel 容器傳過去
        mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
        _reply.readException();
        //從_reply中取出服務端執行方法的結果
        _result = _reply.createTypedArrayList(com.lypeer.ipcclient.Book.CREATOR);
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    //將結果返回
    return _result;
}
複製代碼
  • transact方法:這是客戶端和服務端通訊的核心方法。調用這個方法以後,客戶端將會掛起當前線程,等候服務端執行完相關任務後通知並接收返回的 _reply 數據流。關於這個方法的傳參,這裏有兩點須要說明的地方:
    • 方法 ID :transact() 方法的第一個參數是一個方法 ID ,這個是客戶端與服務端約定好的給方法的編碼,彼此一一對應。在AIDL文件轉化爲 .java 文件的時候,系統將會自動給AIDL文件裏面的每個方法自動分配一個方法 ID。
    • 第四個參數:transact() 方法的第四個參數是一個 int 值,它的做用是設置進行 IPC 的模式,爲 0 表示數據能夠雙向流通,即 _reply 流能夠正常的攜帶數據回來,若是爲 1 的話那麼數據將只能單向流通,從服務端回來的 _reply 流將不攜帶任何數據。 注:AIDL生成的 .java 文件的這個參數均爲 0。

經過一個方法能夠總結一下proxy方法的工做流程:

  1. 生成_data和_reply數據流,將它傳給服務端

  2. 經過transact方法傳遞給服務端,並請求服務端調用指定方法

  3. 接收_reply數據流,並從中取出服務端返回的數據(服務端是如何返回_reply數據流回來的,這點是底層封裝好的,咱們沒必要要知道)

  4. 咱們把數據分紅Pacel傳給服務端了,服務端幹了些啥? 前面說了客戶端經過調用 transact() 方法將數據和請求發送過去,那麼理所固然的,服務端應當有一個方法來接收這些傳過來的東西:在 BookManager.java 裏面咱們能夠很輕易的找到一個叫作 onTransact() 的方法。咱們看看這個方法是怎麼寫的:

@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_getBooks: {
            //省略
            return true;
        }
        case TRANSACTION_addBook: {
            //省略
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
複製代碼

能夠看到,它在接收了客戶端的 transact() 方法傳過來的參數後,什麼廢話都沒說就直接進入了一個 switch 選擇:根據傳進來的方法 ID 不一樣執行不一樣的操做。接下來看一下每一個方法裏面它具體作了些什麼,以 getBooks() 方法爲例:

case TRANSACTION_getBooks: {
    data.enforceInterface(DESCRIPTOR);
    //調用 this.getBooks() 方法,在這裏開始執行具體的事務邏輯
    //result 列表爲調用 getBooks() 方法的返回值
    java.util.List<com.lypeer.ipcclient.Book> _result = this.getBooks();
    reply.writeNoException();
    //將方法執行的結果寫入 reply ,
    reply.writeTypedList(_result);
    return true;
}
複製代碼

很簡單,調用服務端的具體實現,而後獲取返回值寫入到reply流。這裏的this.getBooks()方法就是由子類去實現的,也就是咱們的AIDLService中的bookManager

aidl8.png

好了,整個流程結束了,如今咱們來總結一下:

  • 獲取客戶端傳遞過來的數據,根據方法id執行相應的操做
  • 將傳遞過來的數據取出來,若是須要返回數據給服務端,那就封裝成_data傳回。不然,執行相應操做。
  • 服務端接收到數據後,將須要回傳的數據寫入_reply流,傳回客戶端。

整個流程的序列圖是:

aidl9.png

這張圖,跟咱們以前ActivityThread那張圖很像,由於本質上二者都是採用了Binder進行通訊。

總結:

若是要使用Binder進行通訊,那麼你首先須要定義一個協議BookManager,而後你須要有兩個代理,一個是服務端代理,也是最終真正作事的類--AIDLService,還須要一個本地代理類--BookMangerProxy,它只負責封裝數據傳遞給服務端,而後服務端在onTransact中拿到數據進行處理便可。

Binder學習

1 用戶空間和內核空間

Android系統基於Linux內核,即Linux Kernel。Linux Kernel是操做系統的核心,獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了給Kernel提供必定的保護機制,因而就把Kernel和上層的應用程序抽象的隔離開,分別稱之爲內核空間(Kernel Space)和用戶空間(User Space)

2 Binder驅動定義

Android使用的Linux內核擁有着很是多的跨進程通訊機制,好比管道,System V,Socket等,可是出於安全和性能的考慮,採用了一種全新的通訊方式——Binder。Binder自己並非Linux內核的一部分,可是通過Linux的動態可加載內核模塊機制(LKM),Binder模塊在運行時被連接到內核做爲內核的一部分在內核空間運行。

模塊是具備獨立功能的程序,它能夠被單獨編譯,可是不能獨立運行。它在運行時被連接到內核做爲內核的一部分在內核空間運行。

在Android系統中,這個運行在內核空間的,負責各個用戶進程經過Binder通訊的內核模塊叫作Binder驅動;

總結:

  • Binder是運行在內核態的,單不屬於內核態。
  • 用戶進程之間進行通訊只能經過內核空間。

3 爲何使用Binder?

  • 安全

傳統的進程通訊方式對於通訊雙方的身份並無作出嚴格的驗證,只有在上層協議上進行架設;好比Socket通訊ip地址是客戶端手動填入的,均可以進行僞造;而Binder機制從協議自己就支持對通訊雙方作身份校檢,於是大大提高了安全性。這個也是Android權限模型的基礎。

  • 性能

在移動設備上,普遍地使用跨進程通訊確定對通訊機制自己提出了嚴格的要求;Binder相對出傳統的Socket方式,更加高效

4 Binder通訊模型

比喻:打電話

SM : 通訊錄,記錄着你要找的人的電話號碼 Binder:基站

整個通訊流程如圖:

binder.png

5 Binder機制跨進程原理

上文Binder通訊圖給出了四個角色:Client,Server,SM,driver。可是咱們仍然不清楚他們是怎麼幹活的? 看下圖:

binder2.png

畫畫太爛,看一下原來的圖吧。

binder3.png

整個流程簡述爲:

  1. Server向SM註冊,告訴本身是誰,有什麼能力(IInterface接口),對應到場景中也就是告訴SM,本身叫"zhangsan",有一個對象object,能夠add
  2. Client向SM查詢zhangsanobject
  3. 驅動在數據流過期,返回一個如出一轍的代理類
  4. Client調用代理類的add方法
  5. 代理類封裝方法參數,發送給Binder驅動
  6. Binder驅動拿到這個參數,通知Server進程調用object對象的add方法,並返回結果
  7. Server返回結果,Binder返回結果給Client

一句話總結就是:

Client進程只不過是持有了Server端的代理;代理對象協助驅動完成了跨進程通訊。

總結四個角色的做用:

  • Client進程(簡稱Client):通訊的發起進程。
  • Server進程(簡稱Server):通訊的響應進程。
  • ServiceManager進程(簡稱SM): 全部的Server將本身的信息註冊到SM,併爲Client提供查詢的功能。
  • Binder驅動:將SM爲Client查詢到的Server的目標數據轉換爲proxy,再傳遞給Client

深刻了解Java的Binder

IBinder/IInterface/Binder/BinderProxy/Stub是什麼?

  • IBinder:是一個接口,它表明了一種跨進程傳輸的能力
  • IInterface:表明的就是遠程server對象具備什麼能力。具體來講,就是aidl裏面的接口。
  • Java層的Binder類:表明的其實就是Binder本地對象。BinderProxy類是Binder類的一個內部類,它表明遠程進程的Binder對象的本地代理;這兩個類都繼承自IBinder, 於是都具備跨進程傳輸的能力;實際上,在跨越進程的時候,Binder驅動會自動完成這兩個對象的轉換。
  • Stub:繼承自Binder,說明他是Binder的本地對象;實現IInterface接口,說明具備遠程Server承諾給Client的能力;Stub是抽象類,說明具體的實現須要咱們本身完成,這裏使用了策略模式。

知道了這一點後,咱們能夠分析AIDL中各個類的做用了。

先從AIDL文件夾,也就是服務端中文件看起。

  1. ICompute.aidl
package com.example.test.app;
interface ICompute {
     int add(int a, int b);
}
複製代碼

而後用編譯工具編譯以後,能夠獲得對應的ICompute.java類:

public interface ICompute extends android.os.IInterface 
複製代碼

繼承自IInterface,說明ICompute是用來定義了Server端具備的能力。

  1. 接下來看的內部類Stub
/**
         * Cast an IBinder object into an com.example.test.app.ICompute interface,
         * generating a proxy if needed.
         */
        public static com.example.test.app.ICompute asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.example.test.app.ICompute))) {
                return ((com.example.test.app.ICompute) iin);
            }
            return new com.example.test.app.ICompute.Stub.Proxy(obj);
        }

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

複製代碼

Stub類繼承自Binder,意味着這個Stub其實本身是一個Binder本地對象,而後實現了ICompute接口,ICompute自己是一個IInterface,所以他攜帶某種客戶端須要的能力(這裏是方法add)。

  1. 而後看看asInterface方法
/**
 * Cast an IBinder object into an com.example.test.app.ICompute interface,
 * generating a proxy if needed.
 */
public static com.example.test.app.ICompute asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.example.test.app.ICompute))) {
        return ((com.example.test.app.ICompute) iin);
    }
    return new com.example.test.app.ICompute.Stub.Proxy(obj);
}
複製代碼

咱們在bind一個Service以後,在onServiceConnecttion的回調裏面,就是經過這個方法拿到一個遠程的service的,這個方法作了什麼呢?

首先看函數的參數IBinder類型的obj,這個對象是驅動給咱們的,若是是Binder本地對象,那麼它就是Binder類型,若是是Binder代理對象,那就是BinderProxy類型;而後,正如上面自動生成的文檔所說,它會試着查找Binder本地對象,若是找到,說明Client和Server都在同一個進程,這個參數直接就是本地對象,直接強制類型轉換而後返回,若是找不到,說明是遠程對象(處於另一個進程)那麼就須要建立一個Binde代理對象,讓這個Binder代理實現對於遠程對象的訪問。通常來講,若是是與一個遠程Service對象進行通訊,那麼這裏返回的必定是一個Binder代理對象,這個IBinder參數的其實是BinderProxy;

咱們知道,對於遠程方法的調用,是經過Binder代理完成的,在這個例子裏面就是Proxy類;Proxy對於add方法的實現以下:

Override
public int add(int a, int b) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeInt(a);
        _data.writeInt(b);
        mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}
複製代碼

它首先用Parcel把數據序列化了,而後調用了transact方法.

transact方法的做用:將Client線程掛起等待返回;驅動完成操做後調用Serer的onTransact方法,這個方法被結果返回給驅動,驅動以後喚醒掛起的Client並將結果返回。

整個過程結束。

6 Binder本地對象和Binder代理對象區別

  • Binder本地對象(Stub):實現IInterface,繼承Binder(is a Binder)
  • Binder代理對象(Proxy):實現IInterace,並持有IBinder引用(has a Binder)

比喻來講就是一個是真正的皇帝,一個是挾天子以令諸侯。

7.結合Activity的啓動流程看Binder

Activity的啓動流程中涉及如下類:

  • Launcher
  • Activity
  • Instrumentation
  • ActivityManagerNative
  • ActivityManagerProxy
  • ActivityManagerService
  • ActivityStack
  • ApplicationThreadProxy
  • ApplicationThread
  • ActivityThread

再進行講解Activity的啓動過程以前,咱們首先要知道各個類之間的關係,以及各個類的做用,另外還須要知道這些類是屬於Server端仍是Client端的。咱們知道,Android IPC 通訊採用的Binder,用到Binder就須要區分Server和Client.

類關係圖

Activity1.png

getService()方法舉例,說明調用順序,Activity採用的是遠程代理ActivityManagerProxyAMS在Client端的代理,同時,它也是個Binder對象,傳遞時將它傳遞給AMS,之後,AMS想對Activity作啥,就能夠經過它。

代理模式2.png

總的來講,就是:

  1. ActivityManager首先經過遠端代理ActivityManagerNative獲得本地代理ActivityManagerProxy
  2. ActivityManager要調用什麼方法,就經過本地代理ActivityManagerProxy的成員變量ActivityManagerNative去調用,其實就是將Binder對象ActivityManagerNative傳遞過去。

若是沒了解過Activity的啓動流程,請參考文章Activity啓動流程

分析Activity啓動流程中各個類對應的角色

  • IAcivityManger:是一個IInterface,表明遠程Service有什麼能力;
  • ActivityManagerNative:Binder本地對象(相似Stub
  • AtivityMangerService:真正的服務端, ActivityManagerNative的實現類
  • ActivityManagerProxy:Bidner代理對象
  • ActivityManager:不過是一個管理類而已,能夠看到真正的操做都是轉發給ActivityManagerNative進而交給他的實現ActivityManagerService 完成的。

總結:

  • ----------------------------- 進程間通訊步驟 -----------------------------

  • 1.Client 發起遠程調用請求 也就是RPC 到Binder。同時將本身掛起,掛起的緣由是要等待RPC調用結束之後返回的結果

  • 2.Binder 收到RPC請求之後 把參數收集一下,調用transact方法,把RPC請求轉發給service端。

  • 3.service端 收到rpc請求之後 就去線程池裏 找一個空閒的線程去走service端的 onTransact方法 ,實際上也就是真正在運行service端的 方法了,等方法運行結束 就把結果 寫回到binder中。

  • 4.Binder 收到返回數據之後 就喚醒原來的Client 線程,返回結果。至此,一次進程間通訊 的過程就結束了

  • ----------------------------- 進程間通訊步驟 -----------------------------

相關文章
相關標籤/搜索