天天看點

Android多程序之Binder的使用

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檔案
// 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);
}           
  • 檔案編寫完成以後通過Make Project指令就可以生成對應的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>           
  • 用戶端的話就簡單在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意外死亡以後怎麼辦?
  • 如何注冊監聽回調,當服務端有新消息後馬上通知注冊回調的用戶端?如何解除注冊?
  • 如何進行權限驗證等

歡迎關注我的微信公衆号,和我一起學習一起成長!

繼續閱讀