Android AIDL淺析及異步使用

AIDL:Android Interface Definition Language,即 Android 接口定義語言。java

AIDL 是什麼

Android 系統中的進程之間不能共享內存,所以,須要提供一些機制在不一樣進程之間進行數據通訊。 linux

爲了使其餘的應用程序也能夠訪問本應用程序提供的服務,Android 系統採用了遠程過程調用(Remote Procedure Call,RPC)方式來實現。與不少其餘的基於 RPC 的解決方案同樣,Android 使用一種接口定義語言(Interface Definition Language,IDL)來公開服務的接口。咱們知道 Android 四大組件中的 3 種(Activity、BroadcastReceiver和ContentProvider)均可以進行跨進程訪問,另一種 Android 組件 Service 一樣能夠。所以,能夠將這種能夠跨進程訪問的服務稱爲 AIDL(Android Interface Definition Language)服務。android

在介紹 AIDL 的使用以及其它特性前,咱們先來了解下 AIDL 的核心——Binder服務器

Android 的 Binder 機制淺析

看過一些關於 Binder 的文章,總得來講 Binder 機制的底層實現很複雜,至關複雜,要徹底搞清楚,得花大量的時間。從某種角度來講,我的以爲,對於 Binder,咱們只須要了解其上層原理以及使用方法便可。app

直觀來看,從代碼的角度來講,Binder 是 Android 系統源碼中的一個類,它實現了 IBinder 接口;從 IPC 角度來講,Binder 是 Android 中的一種跨進程通訊方式;從 Android Framework 角度來說,Binder 是 ServiceManager 鏈接各類 Manager(ActivityManager、WindowManager 等等)和相應 ManagerService 的橋樑;從 Android 應用層來講,Binder 是客戶端和服務端進行通訊的媒介,當 bindService 的時候,服務端會返回一個包含了服務端業務調用的 Binder 對象,經過這個 Binder 對象,客戶端就能夠和服務端進行通訊,這裏的服務包括普通服務和基於 AIDL 的服務。異步

接下來,咱們經過一個 AIDL 示例,來分析 Binder 的工做機制。在工程目錄中新建一個名爲 aidl 的 package,而後新建 Book.Java、Book.aidl(建立此文件時,as 會提示已存在,須要先用其它命令,建立成功後再重命名爲 Book.aidl )和 IBookManager.aidl,代碼以下:ide

// Book.java
package com.cy.ipcsample.aidl;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * 數據類
 * @author cspecialy
 * @version v1.0.0
 * @date 2018/5/14 21:38
 */
public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

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

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(bookId);
        parcel.writeString(bookName);
    }
 
    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                '}';
    }
}

 

// Book.aidl
package com.cy.ipcsample.aidl;

parcelable Book;

 

// IBookManager.aidl
package com.cy.ipcsample.aidl;

import com.cy.ipcsample.aidl.Book;
import com.cy.ipcsample.aidl.IOnNewBookArrivedListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unRegisterListener(IOnNewBookArrivedListener listener);
}

建立好三個文件以後,編譯一下,as 會在 app/build/generated/source/aidl/debug 目錄下的 com.cy.ipcsample 包中生成一個名爲 IBookManager.Java 的類,以下圖所示:函數

這是系統生成的 Binder 類,接下來咱們要利用這個類來分析 Binder 的工做原理。其代碼以下:(生成的代碼格式很亂,能夠格式化代碼以後看)ui

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: G:\\Android\\Github\\Bugly-Android-Demo\\sample\\ipcsample\\src\\main\\aidl\\com\\cy\\ipcsample
 * \\aidl\\IBookManager.aidl
 */
package com.cy.ipcsample.aidl;

public interface IBookManager extends android.os.IInterface {
    public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException;

    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.cy.ipcsample.aidl.IBookManager {
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        private static final java.lang.String DESCRIPTOR = "com.cy.ipcsample.aidl.IBookManager";

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

        /**
         * Cast an IBinder object into an com.cy.ipcsample.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.cy.ipcsample.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.cy.ipcsample.aidl.IBookManager))) {
                return ((com.cy.ipcsample.aidl.IBookManager) iin);
            }
            return new com.cy.ipcsample.aidl.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.cy.ipcsample.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.cy.ipcsample.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.cy.ipcsample.aidl.Book.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.cy.ipcsample.aidl.IBookManager {
            private android.os.IBinder mRemote;

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

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            @Override
            public java.util.List<com.cy.ipcsample.aidl.Book> 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.cy.ipcsample.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.cy.ipcsample.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.cy.ipcsample.aidl.Book book) 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 ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }


        }
    }
}

可見,系統爲咱們生成了一個 IBookManager 接口,它繼承了 IInterface 這個接口,因此這裏要注意下,全部能夠在 Binder 中傳輸的接口,都須要繼承 IInterface 接口。this

接下來分析下,該類的工做機制。仔細看,能夠發現,該類主要分紅三個部分:

  • 定義自身方法( getBookList 方法和 addBook 方法);
  • 內部靜態類-Stub,該類繼承 Binder,同時實現 IBookManager 接口
  • Stub的內部代理類-Proxy,也實現 IBookManager 接口

第一部分咱們不用管它,主要看 Stub 類和 Proxy 類。在 Stub 中,首先聲明瞭兩個用於標識 IBookManager 方法的整型變量,這兩個變量用於標識在 transact 過程當中客戶端所請求的是哪一個方法。接着,是 asInterface 方法,該方法用於將服務端的 Binder 對象轉換成客戶端所需的 AIDL 接口類型的對象,該方法經過調用 Binder 的 queryLocalInterface 方法,判斷客戶端和服務端是否處於同一進程,若是客戶端、服務端處於同一進程,那麼此方法直接返回服務端的 Stub,不然,返回 Stub 的代理對象 Proxy。queryLocalInterface 實現以下:

/**
 * Use information supplied to attachInterface() to return the
 * associated IInterface if it matches the requested
 * descriptor.
 */
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

mOwner 是在 Stub 構造函數中傳進去的 this 參數。

接下來是 Stub 的代理類 Stub.Proxy,由上面的分析可知,Stub.Proxy 是運行在客戶端的(由 asInterface 方法返回給客戶端的對象),Stub.Proxy對象建立後,持有服務端的 Binder 對象,用於客戶端請求時調用服務端方法進行遠程調用。客戶端在向服務端發起請求時,調用 Stub.Proxy 的相應方法,Stub.Proxy 方法的流程以下:

  • 首先建立該方法所須要的輸入型 Parcel 對象 _data、輸出型對象 _reply 和返回值對象(若是有);
  • 而後把該方法的參數信息寫入 _data 中(若是有);
  • 接着調用 transact 方法進行 RPC(遠程過程調用)請求,同時當前線程掛起;
  • 而後在 transact 方法經過服務端的 Binder 對象調用服務端的 onTransact 方法,即 Stub 中的 onTransact 方法;
  • onTransact 方法返回後,當前線程繼續執行,並從 _reply 中取出 RPC 過程返回的結果;
  • 最後返回 _reply 中的數據(若是客戶端請求方法須要返回值)。

以上,就是系統生成的 IBookManager 的工做過程,須要注意下,服務端的 onTransact 方法是運行在 Binder 線程池中的。因爲 IBookManager.Stub 類繼承 Binder,因此上述分析即 Binder 的工做機制,簡單總結下:

  • 在客戶端和服務端鏈接時(通常經過 bindService 方法),服務端經過 asInterface 方法給客戶端返回一個 IInterface 接口類型的對象(若是客戶端和服務端在同一個進程,則返回服務端的 Binder 對象,不然返回 服務端 Binder 對象的代理);
  • 客戶端經過該對象向服務端進行請求,若是客戶端和服務端不在同一個進程,則經過該對象所代理的 Binder 對象進行 RPC 請求,不然,直接經過 Binder 對象調用服務端相應方法。

或者參考下圖理解下:

因而可知,Binder 在 AIDL 中承載着重要的職能,是 AIDL 的核心,理解了 Binder 的工做機制,其實在不少方面都頗有用。

AIDL 的使用

一套 AIDL 服務搭建的步驟以下:

  • 建立 .aidl 文件,系統生成相應的繼承 IInterface 的接口類(暴露給客戶端的接口)。
  • 建立一個 Service(服務端),實現 .aidl 文件中的接口。
  • 建立客戶端,綁定服務端的 Service。
  • 客戶端綁定服務端成功後,將服務端返回的 Binder 對象轉成 AIDL 接口所屬的 IInterface 類型。調用 AIDL 中的方法,實現和服務端的通訊。

接下來,咱們使用上面的 IBookManager 來實現 AIDL。

  1. 建立 .aidl 文件

直接使用上面建立好的 Book.aidl、IBookManager.aidl 文件便可

  1. 建立服務端

建立一個 Service,命名爲 BookManagerService,代碼以下:

package com.cy.ipcsample.aidl
    
    import android.app.Service
    import android.content.Intent
    import android.os.IBinder
    import android.os.RemoteCallbackList
    import android.util.Log
    import java.util.concurrent.CopyOnWriteArrayList
    
    class BookManagerService : Service() {
        private val TAG = "BookManagerService"
    
        private val mBookList = CopyOnWriteArrayList<Book>()
        private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>()
    
        /**
         * 實現 AIDL 接口的 Binder 對象,客戶端綁定服務端時,直接返回此 Binder 對象
         */
        private val mBinder = object : IBookManager.Stub() {
    
            override fun getBookList(): MutableList<Book> {
                return mBookList
            }
    
            override fun addBook(book: Book?) {
                mBookList.add(book)
            }
    
        }
    
        override fun onBind(intent: Intent): IBinder {
            return mBinder
        }
    
        override fun onCreate() {
            super.onCreate()
    
            // 建立兩本圖書
            mBookList.add(Book(1, "Android"))
            mBookList.add(Book(2, "iOS"))
        }
    }

而後在 AndroidManifest 中註冊 Service,注意啓動多進程:  

    <service
        android:name=".aidl.BookManagerService"
        android:process="com.cy.ipcsample.bookManagerService">
    </service>
  1. 客戶端綁定服務端的 Service

客戶端的建立,直接使用 Activity 便可,綁定遠程服務的代碼以下:

private val mConnection = object : ServiceConnection {
 
        override fun onServiceDisconnected(name: ComponentName?) {
        }
 
        /**
         * 鏈接遠程服務成功的回調
         */
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        }
 
    }

    // 綁定遠程服務
    bindService(Intent(this, BookManagerService::class.java),
            mConnection, Context.BIND_AUTO_CREATE)
  1. 與服務端通訊

與服務器綁定成功後,首先在 ServiceConnection 的回調中,將服務端返回的 Binder 對象轉換成 AIDL 接口所屬的對象,就能夠調用相應方法和服務端通訊了,代碼以下:

// 將服務端返回的 Binder 對象轉換成 IBookManager 對象
    val bookManager = IBookManager.Stub.asInterface(service)

    // 與服務端通訊
    try {
        // 獲取圖書列表
        val list = bookManager.bookList
        Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}")
        Log.i(TAG, "query book list: $list")

        // 添加一本圖書
        val book = Book(3, "Android開發藝術探索")
        bookManager.addBook(book)
        Log.i(TAG, "add book: $book")

        // 獲取圖書列表
        val newList = bookManager.bookList
        Log.i(TAG, "query book list: $newList")
    } catch (e: RemoteException) {
        e.printStackTrace()
    }

代碼中,咱們先查詢了服務端的圖書列表,接着向服務端添加一本書Android藝術開發探索,而後再次查詢看是否添加成功。運行下看 log,以下圖所示:

可見,運行結果和預期結果一致。完整的客戶端代碼以下:

package com.cy.ipcsample.aidl
    
    import android.content.ComponentName
    import android.content.Context
    import android.content.Intent
    import android.content.ServiceConnection
    import android.os.Bundle
    import android.os.IBinder
    import android.os.RemoteException
    import android.support.v7.app.AppCompatActivity
    import android.util.Log
    import com.cy.ipcsample.R
    
    class BookManagerActivity : AppCompatActivity() {
    
        private val TAG = "BookManagerActivity"
    
        private val mConnection = object : ServiceConnection {
    
            override fun onServiceDisconnected(name: ComponentName?) {
                Log.d(TAG, "binder died.")
            }
    
            /**
             * 鏈接遠程服務成功的回調
             */
            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                // 將服務端返回的 Binder 對象轉換成 IBookManager 對象
                val bookManager = IBookManager.Stub.asInterface(service)
     
                // 與服務端通訊
                try {
                    // 獲取圖書列表
                    val list = bookManager.bookList
                    Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}")
                    Log.i(TAG, "query book list: $list")
     
                    // 添加一本圖書
                    val book = Book(3, "Android開發藝術探索")
                    bookManager.addBook(book)
                    Log.i(TAG, "add book: $book")
     
                    // 獲取圖書列表
                    val newList = bookManager.bookList
                    Log.i(TAG, "query book list: $newList")
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }
     
        }
     
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_book_manager)
     
            // 綁定遠程服務
            bindService(Intent(this, BookManagerService::class.java),
                    mConnection, Context.BIND_AUTO_CREATE)
        }
     
        override fun onDestroy() {
            unbindService(mConnection)
            super.onDestroy()
        }
    }

到此,一個簡單的 AIDL 示例就完成了,固然,AIDL 的使用遠沒有那麼簡單,還有不少情景須要考慮的,好比:客戶端須要隨時服務端在狀態變化時同時客戶端,相似觀察這模式,那麼訂閱與反訂閱怎麼實現;Binder 意外死亡,怎麼重連等等,更多內容,能夠參考《Android藝術開發探索》電子書下載

AIDL 的異步調用

AIDL 的調用過程是同步仍是異步的?

這個問題其實看這一節的標題你們都知道了,AIDL 的調用過程是同步的。同時,上面分析 Binder 的機制時,也提到了,客戶端進行遠程 RPC 請求時,線程會掛起,等待結果,由此也可知,AIDL 的調用過程是同步的,下面來驗證下。

首先,在服務端的 BookManagerService 中實現的 Binder 對象的 getBookList 方法添加延時執行,以下圖所示:

而後,在客戶端的 BookManagerActivity 中添加一個按鈕,點擊按鈕時調用服務端 Binder 的 getBookList 作 RPC 請求。代碼以下:

運行後,連續點擊按鈕,結果以下圖所示:

由圖所知,連續點擊按鈕事後一段時間,出現了無響應錯誤( ANR ),由此可知,客戶端向服務端作 RPC 請求時,是同步的,也就是說:AIDL 的調用過程是同步的

AIDL 的調用過程是同步的,當咱們須要服務端作耗時操做時,確定是不能使用同步調用的,不然輕者影響用戶體驗,重者直接 ANR 或者應用崩潰。那麼如何使 AIDL 的調用過程是異步的呢?

其實也很簡單,只須要把調用放到非 UI 線程便可,若是要對調用的返回作 UI 更新的話,再經過 Handler 處理便可。以下圖所示:

本文參考

  • 《Android開發藝術探索》 第二章
相關文章
相關標籤/搜索