IPC機制之Binder機制

前言

Binder是Android的一個類,它實現了IBinder接口。從IPC角度來講,Binder是Android中的一種跨進程通訊方式;從Android應用層來講,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,經過這個Binder對象,客戶端就能夠獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務java

在Android開發中,Binder主要用在Service中,包括AIDL(Android Interface Defination Language)和Messenger,其中普通Service中的Binder不涉及進程間通訊,因此較爲簡單,沒法觸及Binder的核心,而Messenger的底層實際上是AIDL,因此這裏選擇AIDL來分析Binder的工做機制。android

使用示例

新建三個文件,Book.java、Book.aidl和IBookManager.aidl,代碼以下: Book.javabash

package com.example.runningh.myapplication.aidl;

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

public class Book implements Parcelable {
    public int bookId;
    public String bookName;

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {

        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

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

    private Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }
}
複製代碼

Book .java是一個表示圖書信息的類,實現了Parcelable接口。服務器

Book.aidl:app

package com.example.runningh.myapplication.aidl;

parcelable Book;
複製代碼

Book.aidl是Book類在AIDL中的聲明。ide

IBookManager.aidl:ui

package com.example.runningh.myapplication.aidl;

import com.example.runningh.myapplication.aidl.Book;
import com.example.runningh.myapplication.aidl.IOnNewBookArrivedListener;

interface IBookManager {

    List<Book> getBookList();

    void addBook(in Book book);
}
複製代碼

IBookManager.adil是咱們定義的一個接口,裏面有兩個方法:getBookList和addBook,其中getBookList用於從遠程服務端獲取圖書列表,而addBook用於往圖書列表中添加一本書。this

注意:在IBookManager.aidl中要手動導入Book類,不支持自動導入,這是AIDL的特殊之處。spa

經過執行build操做後,系統爲IBookManager.aidl生成的Binder類,在gen目錄下的source文件夾下可找到IBookManager.java類。線程

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/RunningH/Documents/TencentGame/ThirdParty/ThemeSkinning/MyApplication2/app/src/main/aidl/com/example/runningh/myapplication/aidl/IBookManager.aidl
 */
package com.example.runningh.myapplication.aidl;
public interface IBookManager extends android.os.IInterface {
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.example.runningh.myapplication.aidl.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.example.runningh.myapplication.aidl.IBookManager";
        
        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        
        /**
         * Cast an IBinder object into an com.example.runningh.myapplication.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.example.runningh.myapplication.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.example.runningh.myapplication.aidl.IBookManager))) {
                return ((com.example.runningh.myapplication.aidl.IBookManager)iin);
            }
            return new com.example.runningh.myapplication.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.example.runningh.myapplication.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.example.runningh.myapplication.aidl.Book _arg0;
                    if ((0!=data.readInt())) {
                        _arg0 = com.example.runningh.myapplication.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.example.runningh.myapplication.aidl.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.example.runningh.myapplication.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.example.runningh.myapplication.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.example.runningh.myapplication.aidl.Book.CREATOR);
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            
            @Override public void addBook(com.example.runningh.myapplication.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();
                }
            }
        }
    }


    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.example.runningh.myapplication.aidl.Book> getBookList() throws android.os.RemoteException;
    public void addBook(com.example.runningh.myapplication.aidl.Book book) throws android.os.RemoteException;
}
複製代碼

首先這個類聲明瞭兩個方法getBookList和addBook,顯然這就是咱們在IBookMananger.aidl中所聲明的方法,同時它還聲明瞭兩個整型的id分別用於標識這兩個方法,這兩個id用於標識在transact過程當中客戶端所請求的究竟是哪一個方法。接着,它聲明瞭一個內部類Stub,這個Stub就是一個Binder類,當客戶端和服務端都位於同一個進程時,方法調用不會跨進程的transact過程,而當二者位於不一樣進程時,方法調用須要走transact過程,這個邏輯由Stub的內部代理Proxy來完成。

咱們看一下怎麼使用這個類,經過調用過程來熟悉其中的方法。

咱們新建一個工程並在新建BookManagerActivity、BookManagerService並將BookManagerService放在另一個進程中(在manifest中聲明)。在BookManagerActivity經過BindService的方式和BookManagerService綁定。

package com.example.runningh.myapplication.aidl;

import android.app.Activity;
import android.app.ListActivity;
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.annotation.Nullable;
import android.util.Log;

import com.example.runningh.myapplication.R;

import java.util.List;

/**
 * Created by RunningH on 2017/11/26.
 */

public class BookManagerActivity extends Activity {
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager iBookManager = IBookManager.Stub.asInterface(service); //注意這裏調用了Stub的asInterface方法,經過服務端的IBinder對象找到
            try {
                List<Book> list = iBookManager.getBookList();
                Log.i("ABC", "query book list, list type:" + list.getClass().getCanonicalName());
                Log.i("ABC", "query book list:" + list.toString());
                Book newBook = new Book(3, "HTML");
                iBookManager.addBook(newBook);
                Log.i("ABC", "add book:" + newBook);
                List<Book> newList = iBookManager.getBookList();
                Log.i("ABC", "query book list:" + newList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}
複製代碼

BookManagerService.java:

package com.example.runningh.myapplication.aidl;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Created by RunningH on 2017/11/26.
 */

public class BookManagerService extends Service {
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "IOS"));
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
複製代碼
  • BookManagerService中new了一個Stub對象,並重寫addBook和getBookList方法。這樣Stub的構造方法被調用,構造方法中調用了Binder的attachInterface將該Binder對象(Stub就是一個Binder對象)和DESCRIPTOR(Binder的惟一標識,通常用Binder的類名錶示)聯繫起來。
  • 上面生成的Stub對象(也就是Binder對象)經過BinderService的方式傳遞給客戶端,也就是BookManagerActivity的mConnection的onServiceConnected方法中service對象。經過調用IBookManager.Stub.asInterface方法獲得客戶端所須要的AIDL接口類型對象。這是區分進程的,若是客戶端和服務端處於同一個進程,那麼執行queryLocalInterface(DESCRIPTOR)返回就不爲空(還記得上面生成Stub對象的時候將該對象和DESCRIPTOR綁定嗎?這裏就是取出來),因此此方法返回的就是服務端的Stub對象自己。若是是不一樣的進程則返回的是系統封裝或的Stub.proxy對象。對應於下面的則是new了一個Stub.Proxy對象。
public static com.example.runningh.myapplication.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.example.runningh.myapplication.aidl.IBookManager))) {
          return ((com.example.runningh.myapplication.aidl.IBookManager)iin);
    }
    return new com.example.runningh.myapplication.aidl.IBookManager.Stub.Proxy(obj);
}
複製代碼
  • 先看若是處於同一個進程的狀況,獲得服務端的Stub對象後,直接調用getBookList和addBook方法。由於Stub對象實現了IBookManager接口,並實現了其中的getBookList和addBook方法,因此這裏實際調用的是getBookList和addBook方法。這樣就實現了客戶端和服務端的交互。
  • 再來看不一樣進程的狀況,咱們獲得的是一個Stub.Proxy對象。在客戶端調用getBookList和addBook方法,實際上調用的是Stub.Proxy對象的getBookList和addBook方法。
private static class Proxy implements com.example.runningh.myapplication.aidl.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.example.runningh.myapplication.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.example.runningh.myapplication.aidl.Book> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(com.example.runningh.myapplication.aidl.Book.CREATOR);
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

    @Override public void addBook(com.example.runningh.myapplication.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();
        }
    }
}
複製代碼
  • getBookList方法中調用了mRemote.transact方法,第一個參數標識調用哪個方法,第二個參數標識須要傳遞進去的參數,第三個參數標識須要返回的數據,第四個參數通常爲0。調用transact方法發起RPC(遠程過程調用)請求,同時當前線程掛起(等待數據返回),而後服務端的onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,並從_reply中取出RPC過程的返回結果。
  • addBook方法調用了mRemote.transact方法,參數的意思同上同樣。因爲addBook不須要返回值,因此它不須要從_reply中取出返回值。
  • 上面兩個方法都調用了transact方法,那麼該方法被調用後會觸發服務端的Stub對象的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_getBookList: {
            data.enforceInterface(DESCRIPTOR);
            java.util.List<com.example.runningh.myapplication.aidl.Book> _result = this.getBookList();
            reply.writeNoException();
            reply.writeTypedList(_result);
            return true;
        }
        case TRANSACTION_addBook: {
            data.enforceInterface(DESCRIPTOR);
            com.example.runningh.myapplication.aidl.Book _arg0;
            if ((0!=data.readInt())) {
                _arg0 = com.example.runningh.myapplication.aidl.Book.CREATOR.createFromParcel(data);
            }
            else {
                _arg0 = null;
            }
            this.addBook(_arg0);
            reply.writeNoException();
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}
複製代碼
  • 經過onTransact方法,咱們經過code參數做爲標識具體調用的哪個方法,好比TRANSACTION_getBookList就是調用getBookList方法,這裏作的操做是調用Stub對象的getBookList方法而且將結果寫入到reply(若是該操做須要結果返回的話)中而後返回。

總結:上述就是Binder的工做機制,咱們須要注意的是當客戶端發起遠程請求時,因爲當前線程會被掛起直至服務器端進程返回數據,因此若是一個遠程方法是很耗時的,那麼不能在UI線程中發起遠程請求;其次因爲服務端的Binder方法運行在Binder的線程池中,因此Binder方法無論是否耗時都是應該採用同步的方法去實現,由於它已經運行在一個線程中了。

參考:這篇文章是對《Android開發藝術探索》中IPC機制一章中的Binder內容進行的總結。這本書的做者任玉剛是一個大牛,這本書也是很通俗易懂,對Android開發的進階有很是大的幫助。

相關文章
相關標籤/搜索