進程間通訊之AIDL

1、引言java

  AIDL是android內部進程通訊接口的描述語言,是實現跨進程方法調用的一大利器,其中Binder和Messenger的實現機制都是AIDL。android

2、使用下面結合示例說明其使用過程:服務器

  本次示例的基本要求是完成一個圖書館圖書入庫和在庫圖書列表查詢的demo,併發

  一、爲了完成這個功能,咱們首先須要一個實體類Book,這個實體類須要序列化,由於只有序列化之後的Book對象才能在AIDL中使用。app

  二、接下來咱們須要新建Book.aidl和IBookManager.aidldom

         咱們須要在Book.aidl聲明這個Book類,並在IBookManager中導入Book.aidl並實現兩個功能:addBook和getBookListide

  3.reBuild項目,這樣就會自動生成IbookManager.java這個AIDL文件。(若是查找不到Book類,請參看個人另一篇文章,Binder的機制淺析)ui

  4.接下來的咱們就須要在客戶端和服務端完成對應的工做:this

 下面簡單介紹一下Service和Client中的實現內容。(具體代碼在最後貼出)atom

Service:服務端的工做的關鍵其實在於實現Binder,而這個Binder的獲取就是去實例化IBookManager.Stub這個內部類,並實現AIDL接口中聲明的方法 ,最後在onBind中返回這個Binder實例便可。

Client:客戶端則首先須要綁定遠程服務,而後在綁定成功後將服務端返回的IBinder對象轉換爲AIDL接口,而後就能夠經過這個接口去調用服務端的遠程方法了。(這裏須要注意的是,若是服務端中實現的方法爲耗時方法,在客戶端中對它進行遠程調用的時候須要在子線程中進行,原理在Binder機制中談到過,由於客戶端在進行遠程調用的時候,會掛起自身,等待服務端的反饋,這個時候若是在主線程則會堵塞主線程)

  上面的例子至關因而客戶端遠程調用服務端中的方法,下面講的一個擴充至關因而服務端遠程調用客戶端中的方法

  場景描述:假設還有一個需求是用戶但願圖書館在新書入庫的時候通知他

  不難發現這其實就是觀察者模式的經典引用。這個時候咱們須要增長一個AIDL接口,每一個用戶須要實現這個接口並向圖書館申請新書的提醒功能,而且能夠隨時取消這個提醒。而服務端則會維持一個listener的註冊表,當新書到庫時,遍歷並通知當前已經註冊了提醒功能的用戶新書到庫。

  這邊咱們建立一個接口IOnNewBookArrivedListener.aidl,而且在原有的接口中新增兩個方法,註冊和註銷。

  Service:在服務端,咱們首先須要實現註冊和註銷兩個方法,而後在實現一個新的方法用於不斷生成新書,並在新書到來時遠程調用註冊在案的全部listener的onNewBookArrived方法。

  Client:首先客戶端須要註冊IOnNewBookArrivedListener到遠程服務器,而且在Activity退出時解除註冊;另外,當有新書時,服務端會在有新書時回調客戶單的IOnNewBookArrivedListener方法,固然這個方法是在客戶端的Binder線程池中進行的,所以,爲了進行UI操做,咱們須要使用一個Handler來將其切換到主線程中執行。

  具體代碼:

  實體類:

package com.pignet.library.aidl;

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

/**
 * Created by DB on 2017/7/1.
 */

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

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

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

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

    public Book() {
    }

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

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

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

  AIDL:

  

// Book.aidl
package com.pignet.library.aidl;

// Declare any non-default types here with import statements

parcelable Book;
// IBookManager.aidl
package com.pignet.library.aidl;

// Declare any non-default types here with import statements
import com.pignet.library.aidl.Book;
import com.pignet.library.aidl.IOnNewBookArrivedListener;

interface IBookManager{
    List<Book> getBookList();
    void addBook(in Book book);

    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
// IOnNewBookArrivedListener.aidl
package com.pignet.library.aidl;

import com.pignet.library.aidl.Book;


interface IOnNewBookArrivedListener {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */

    void onNewBookArrived(in Book newBook);
}

 

  Service端:

package com.pignet.library;

import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.util.Log;

import com.pignet.library.aidl.Book;
import com.pignet.library.aidl.IBookManager;
import com.pignet.library.aidl.IOnNewBookArrivedListener;

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

/**
 * Created by DB on 2017/7/2.
 */

public class BookManagerService extends Service {




    private  static  final String TAG ="BookManagerService";
    //原子類,Service是否銷燬
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
    //併發容器BookList
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    //併發容器 Listeners
    private RemoteCallbackList<IOnNewBookArrivedListener> mListeners = new RemoteCallbackList<>();
    //實例化Binder,實例化AIDL中的Stub內部類,
    private Binder mBinder = new IBookManager.Stub(){

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

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

        }

        /**
         * 註冊監聽器,監聽圖書進庫
         * @param listener
         * @throws RemoteException
         */
        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListeners.register(listener);
        }

        /**
         * 解除註冊監聽器,取消監聽圖書進庫
         * @param listener
         * @throws RemoteException
         */
        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListeners.unregister(listener);

        }

    };


    //初始化圖書館中的圖書
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1,"Android"));
        mBookList.add(new Book(2,"Java"));
        //啓動後臺線程
        new Thread(new ServiceWorker()).start();

    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.pignet.library.permission.ACCESS_BOOK_SERVICE");
        if(check == PackageManager.PERMISSION_DENIED){
            Log.d(TAG, "failed ");
            return null;
        }
        return mBinder;
    }

    //後臺線程,負責不斷產生新書入庫
    private class ServiceWorker implements Runnable{

        @Override
        public void run() {

            while(!mIsServiceDestroyed.get()){
                try{
                    Thread.sleep(5000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
               int bookId = mBookList.size()+1;
                Book newBook = new Book(bookId,"new Book#"+bookId);
                Log.d(TAG, "run: "+bookId);

                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        private void onNewBookArrived(Book newBook) throws RemoteException {
            mBookList.add(newBook);

            final int N= mListeners.beginBroadcast();

            for(int i=0;i<N;i++){
                IOnNewBookArrivedListener listener = mListeners.getBroadcastItem(i);
                if(listener!=null){
                    try{
                        listener.onNewBookArrived(newBook);
                    }catch (RemoteException e){
                        e.printStackTrace();
                    }
                }
            }
            mListeners.finishBroadcast();
        }


    }
}

client:

package com.pignet.library;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.pignet.library.aidl.Book;
import com.pignet.library.aidl.IBookManager;
import com.pignet.library.aidl.IOnNewBookArrivedListener;

import org.w3c.dom.Text;

import java.util.List;

public class BookManagerActivity extends AppCompatActivity {
    Button btnGetBookList;
    Button btnAddBook;
    TextView tvBookList;
    EditText etBook;
    IBookManager bookManager;
    private  static StringBuilder stringBuilder;


    private static final  String TAG="BookManagerActivity";
    private static final  int MESSAGE_NEW_BOOK_ARRIVED=1;

    private IBookManager mRemoteBookManager;
    //創建鏈接


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnGetBookList = (Button) findViewById(R.id.btn_getBookList);
        btnAddBook = (Button) findViewById(R.id.btn_addBook);
        tvBookList = (TextView) findViewById(R.id.tv_bookList);
        etBook = (EditText) findViewById(R.id.et_addBook);

        //綁定Service
        Intent intent =new Intent(BookManagerActivity.this,BookManagerService.class);
        
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
        //增長圖書
        btnAddBook.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if(etBook.getText().toString().length()>1){
                    Log.d(TAG, "add Book");
                    Book book = null;
                    try {
                        book = new Book(bookManager.getBookList().size()+1,etBook.getText().toString());
                        bookManager.addBook(book);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }

            }
        });

        //獲取圖書列表
        btnGetBookList.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                new Thread(new Runnable() {

                    @Override
                    public void run() {
                        try {

                            List<Book> list = bookManager.getBookList();
                              stringBuilder=new StringBuilder();

                            for(int i=0;i<list.size();i++){
                                stringBuilder.append(list.get(i).getBookName()+",");
                            }

 
                            BookManagerActivity.this.runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    
                                    tvBookList.setText(stringBuilder.toString());
                                }
                            });

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

                    }
                }).start();

            }
        });
    }

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MESSAGE_NEW_BOOK_ARRIVED:
                     //Log.d(TAG, "receive new book:"+msg.obj);
                    Toast.makeText(BookManagerActivity.this,"receive new book:"+msg.obj,Toast.LENGTH_SHORT).show();

                    break;
                default:
                    super.handleMessage(msg);
            }

        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager=bookManager;
            try {
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Intent intent =new Intent(BookManagerActivity.this,BookManagerService.class);
            mRemoteBookManager=null;
            Log.d(TAG, "binder died"+"正在嘗試重連");
           bindService(intent,mConnection, Context.BIND_AUTO_CREATE);



        }

    };

    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub(){
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget();

        }

    };
    //註銷並解綁
    @Override
    protected void onDestroy() {


        //客戶端銷燬時取消監聽器
        if(mRemoteBookManager!=null&&mRemoteBookManager.asBinder().isBinderAlive()){

            try{
                Log.d(TAG, "unregister listener:"+mOnNewBookArrivedListener);
                mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        //解除綁定
        unbindService(mConnection);
        super.onDestroy();
    }
}

 

 

 

3、RemoteCallbackList:

   引入這個例子主要是爲了介紹一下在服務端遠程調用客戶端方法的例子,此外,更加劇要的一點是爲了引入一個特殊的容器:RemoteCallbackList,這個容器是系統專門用於刪除跨進程listener的接口。

  RemoteCallbackList是一個泛型,支持管理任何的AIDL接口,它的工做原理很簡單,在它的內部又一個Map結構專門用來保存全部的AIDL回調,這個Map的key是IBinder類型,value是Callback類型,在Callback中封裝了真正的遠程listener。

  由於屢次跨進程傳輸客戶端的同一個對象會在服務器中生成不一樣的對象(由於經歷了序列化和反序列化,在Binder中不能傳遞對象,只能傳遞序列化後的對象),這樣咱們就不能直接根據對象自己來進行listener的刪除,而利用RemoterCallbackList的Map咱們就能夠根據其鍵Binder來實現解註冊。

         經過遍歷服務端的全部的listener來找出那個和解註冊listener具備相同Binder對象的服務端listener而後將其刪除。此外,這個容器在客戶端進程終止後會自動移除客戶端所註冊的listener,而且這個容器內部實現了線程同步的功能(synchronized實現)。

4、健壯性設計

  由於Binder能夠會由於特殊緣由意外死亡,因此咱們須要在Binder意外死亡時選擇從新鏈接Binder,以提升通訊的穩定性。

  1.  給Binder設置死亡監聽,具體方法前面提到過

  2.  在onServiceDisconnected從新鏈接遠程服務。

相關文章
相關標籤/搜索