Android多進程之Binder的使用

Android多進程系列

Binder是什麼

  • Binder是Android的一個類,實現了IBinder接口
  • 從IPC角度來講,Binder是Android中的一種跨進程通訊方式
  • Binder還能夠理解爲一種虛擬的物理設備,設備驅動是/dev/binder,Linux中沒有
  • 從Android Framework角度來講,Binder是ServiceManger鏈接各類Manger(ActivityManager、WindowManager等)和相應的ManagerService的橋樑
  • 從Android應用層來講,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候會返回服務端的Binder對象,經過這個Binder對象能夠調用服務端的服務

使用Binder

生成Binder類
  • 能夠經過2中方式生成Binder:經過AIDL文件讓系統自動生成;咱們本身手動編寫Binder
經過AIDL文件生成Binder
  • 首先須要準備一個Parcelable對象
public class Book implements Parcelable {

    public int bookId;
    public String bookName;

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

    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];
        }
    };

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}
複製代碼
  • 而後編寫AIDL文件

新建AIDL文件,AS中會自動建立aidl的目錄

// Book.aidl
package com.xxq2dream.aidl;

parcelable Book;

複製代碼
// IBookManager.aidl
package com.xxq2dream.aidl;

// Declare any non-default types here with import statements
//必須顯示導入須要的Parcelable對象
import com.xxq2dream.aidl.Book;

//除了基本類型,其餘參數都要標上方向,in表示輸入型參數,out表示輸出型參數,inout表示輸入輸出參數
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
複製代碼

最好把AIDL相關的文件都放在一個目錄下

  • 文件編寫完成之後經過Make Project命令就能夠生成對應的Binder類

經過make讓系統自動生成Binder類

生成的Binder類

模擬客戶端和服務端的進程間通訊
  • 首先編寫一個Service類,並設置android:process屬性,開啓在不一樣的進程
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();

    private Binder mBinder = new BookManagerImpl(){
        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.e(TAG, "getBookList-->"+ System.currentTimeMillis());
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Log.e(TAG, "addBook-->");
            mBookList.add(book);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind-->"+ System.currentTimeMillis());
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate-->"+ System.currentTimeMillis());
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "IOS"));
    }
}
複製代碼
  • 註冊Service
//AndroidManifest.xml
<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        android:name="com.xxq2dream.service.BookManagerService"
        android:process=":remote" />
</application>
複製代碼

能夠看到2個進程

  • 客戶端的話就簡單在activity中經過bindService方法綁定服務端,而後經過返回的Binder調用服務端的方法
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e(TAG, "ServiceConnection-->"+ System.currentTimeMillis());
            //經過服務端回傳的Binder獲得客戶端所須要的AIDL接口類型的對象,即咱們上面的IBookManager
            IBookManager bookManager = BookManagerImpl.asInterface(iBinder);
            try {
                // 經過AIDL接口類型的對象bookManager調用服務端方法
                List<Book> list = bookManager.getBookList();
                Log.e(TAG, "query book list, list type:" + list.getClass().getCanonicalName());
                Log.e(TAG, "query book list:" + list.toString());
                Book newBook = new Book(3, "Android 進階");
                bookManager.addBook(newBook);
                Log.e(TAG, "add book:" + newBook);
                List<Book> newList = bookManager.getBookList();
                Log.e(TAG, "query book list:" + newList.toString());

            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e(TAG, "binder died");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, BookManagerService.class);
        //綁定服務,後面會回調onServiceConnected方法
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //解綁服務
        unbindService(mConnection);
        super.onDestroy();
    }
}
複製代碼
  • 經過以上的幾個步驟咱們就實現了一個簡單的進程間通訊的例子

客戶端進程日誌
服務端進程日誌

調用過程
  • 經過打印的日誌咱們能夠大概分析上面的例子中各個方法的調用過程,這裏咱們分析下調用服務端獲取書本列表的過程
  • 客戶端調用bindService方法後,BookManagerService建立,調用Binder類的構造方法建立Binder
  • 而後BookManagerService的onBind方法將建立的Binder返回給客戶端
  • 客戶端的onServiceConnected方法被調用,而後調用Binder的asInterface方法獲得AIDL接口類型的對象bookManager
  • 調用bookManager的getBookList方法實際上調用的是Binder類中的Proxy類對應的getBookList方法
  • 在Proxy類對應的getBookList方法中調用Binder的transact方法發起遠程過程調用請求,同時當前線程掛起,服務端的onTransact方法會被調用
  • 服務端的onTransact方法被調用,經過code找到具體要調用的方法,這裏是TRANSACTION_getBookList
  • 最後會調用BookManagerService中的mBinder對象對應的getBookList方法,將書籍列表mBookList返回,返回的結果在Parcel變量reply中
  • Proxy類中的getBookList方法經過result = reply.createTypedArrayList(Book.CREATOR);獲取到服務端返回的數據
  • 客戶端onServiceConnected方法中接收到數據,調用過程結束
須要注意的地方
  • 客戶端調用遠程請求時客戶端當前的線程會被掛起,直到服務端進程返回數據,因此不能在UI線程中發起遠程請求
  • 客戶端的onServiceConnected和onServiceDisconnected方法都運行在UI線程中,不能夠在裏面直接調用服務端的耗時方法
  • 服務端的Binder方法運行在Binder線程池中,因此Binder方法無論是否耗時都應該採用同步的方式去實現
  • 同上面一點,服務端的Binder方法須要處理線程同步的問題,上面的例子中CopyOnWriteArrayList支持併發讀寫,自動處理了線程同步
  • AIDL中可以使用的List只有ArrayList,但AIDL支持的是抽象的List。所以雖然服務端返回的是CopyOnWriteArrayList,可是在Binder中會按照List的規範去訪問數據並最終造成一個新的ArrayList傳遞給客戶端

結語

  • 以上只是Binder的簡單應用,Binder的使用過程當中仍是有不少問題須要注意的
  • 好比Binder意外死亡之後怎麼辦?
  • 如何註冊監聽回調,當服務端有新消息後立刻通知註冊回調的客戶端?如何解除註冊?
  • 如何進行權限驗證等

歡迎關注個人微信公衆號,和我一塊兒學習一塊兒成長!
複製代碼

AntDream
相關文章
相關標籤/搜索