Android 開發藝術探索筆記之進程通信 AIDL 的使用

爲什麼使用AIDL?
    Messenger 是使用串行方式處理客戶端發送過來的消息,如果有大量的併發請求,則Messenger 就不合適了,Messenger 主要用於傳遞消息,如果我們需要跨進程調用服務端的方法,Messenger 就不發做到了,但 AIDL 則可以實現

使用AIDL 進行進程間通信的流程:

服務端
  1. 服務端首先要創建一個 Service 用來監聽客戶端的請求
  2. 創建一個 AIDL 文件,將暴露給客戶端的接口在這個 AIDL 文件中聲明
  3. 最後在 Service 中實現這個 AIDL 接口

客戶端
  1. 首先需要綁定服務端的 Service
  2. 綁定成功後,將服務端返回的 Binder 對象轉成 AIDL 接口所屬類型,接着就可以調用AIDL中的方法了

AIDL 接口的創建

目錄結構



Book.class

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

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

    private 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 dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}
Book.aidl
// Book.aidl
package com.example.yhadmin.aidldemo.bean;
// Declare any non-default types here with import statements
parcelable Book;//由於是自定義的Book 類型數據,需要聲明爲 parcelable 類型
IBookManager.aidl
// IBookManager.aidl
package com.example.yhadmin.aidldemo;

// Declare any non-default types here with import statements
import com.example.yhadmin.aidldemo.bean.Book;
interface IBookManager {

   //除了基本數據類型,其他類型的參數都需要標上方向類型:in(輸入), out(輸出), inout(輸入輸出)
   List<Book> getBookList();
   void addBook(in Book book);
}
AIDL 中支持的數據類型:
  • 基本數據類型(int、long、char、boolean、double 等)
  • String 和 CharSequence
  • List:只支持 ArrayList,且裏面每個元素都必須能夠被 AIDL 支持
  • Map:只支持 HashMap,且裏面每個元素都必須能夠被 AIDL 支持,包括 key 和 value
  • Parcelable:所有實現了 Parcelable 接口的對象
  • AIDL:所有 AIDL 接口本身也可以在 AIDL 文件中使用

以上 6 種數據類型就是AIDL 所支持的所有類型,其中自定義的 Parcelable 對象和 AIDL 對象必須要顯示的 import 進來,不管它們是否和當前的 AIDL 文件位於同一個包內,比如這裏的 IBookManager.aidl 文件,雖然Book 這個類和 IBookManager.aidl 位於同一包中,但是遵循 AIDL 的規範,我們仍然需要顯示的導包進來,否則就會拋出如下異常

如果 AIDL 文件中用到了自定義的 Parcelable 對象,那麼必須新建一個和它同名的 AIDL 文件,並在其中聲明它爲 Parcelable 類型,如上所示的 Book類。

注意:
  • AIDL 中每個實現了Parcelable 接口的類都需要按照上面那種方式去創建相應的 AIDL 文件並聲明那個類爲 Parcelable。
  • AIDL 中除了基本數據類型,其他類型的參數必須標上方向:in、out或者inout, in 表示輸入型參數,out 表示輸出型參數,inout 表示輸入輸出型參數
  • 根據實際需要指定參數類型,不能一概使用out 或者 inout ,因爲這在底層實現是有開銷的
  • AIDL 接口中只支持方法,不支持聲明靜態常量

建議:爲了方便使用和管理,建議將所有和 AIDL 相關的類和文件全部放在同一個包中,這樣當客戶端是另外一個應用時,我們可以直接把整個包複製到客戶端工程中,這樣不容易出錯,

AIDL 的包結構在服務端和客戶端要保持一致,否則運行會出錯,這是因爲客戶端需要反序列化服務端中和 AIDL 接口相關的所有類,如果類的完整路徑不一致的話,就無法成功反序列化,程序就無法正常運行

例子:
服務端:
public class BookManagerService extends Service {

    private  static  final  String TAG = "BMS";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
    //創建Binder 對象,即實現IBookManager 接口
    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"));
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
這是一個服務端 Service 的典型實現
CopyOnWriteArrayList 支持併發讀/寫,AIDL 方法是在服務端的 Binder 線程池中執行,因此當多個客戶端同時連接的時候,會岑在多個線程同時訪問的情形,所以需要在 AIDL 方法中處理線程同步的問題,這裏採用 CopyOnWriteArrayList 來進行自動的線程同步

AIDL 中能夠使用的 List 只有 ArrayList,但這裏使用 CopyOnWriteArrayList(不是繼承自 ArrayList)爲什麼沒有報錯?能夠正常運行?
    AIDL 中所支持的是抽象的 List,而 List 只是一個接口,因此服務端返回的是 CopyOnWriteArrayList,但是在 Binder 中會按照 List 的規範去訪問數據並最終形成一個新的 ArrayList 傳遞給客戶端,故在服務端採用 CopyOnWriteArrayList 是完全可以的,類似的還有 ConcurrentHashMap

客戶端:
public class ThirdActivity
        extends AppCompatActivity
{
    private  static  final  String TAG = "BookManagerActivity";

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //將返回的 Binder 對象轉換爲 AIDL 接口
            IBookManager bookManager = IBookManager.Stub.asInterface(service);

            try {
                //通過 AIDL 接口調用服務端的遠程方法
                List<Book> bookList = bookManager.getBookList();
                Log.i(TAG,"query book list, list type:"+ bookList.getClass().getCanonicalName());
                Log.i(TAG,"query book list: "+ bookList.toString());

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

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    public static void acrtionStart(Context context){
        context.startActivity(new Intent(context, ThirdActivity.class));
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
        Button       btn    = findViewById(R.id.button);
        final Intent intent = new Intent(this, BookManagerService.class);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
            }
        });
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}
注意:服務端的方法有可能需要很久才能執行完畢,此時上述代碼就會導致 ANR,這裏只是爲了更好的理解AIDL 這麼寫而已

Log日誌:

使用 addBook 方法
public class ThirdActivity
        extends AppCompatActivity
{
    private  static  final  String TAG = "BookManagerActivity";

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);

            try {
                List<Book> bookList = bookManager.getBookList();
//                Log.i(TAG,"query book list, list type:"+ bookList.getClass().getCanonicalName());
                Log.i(TAG,"query book list: "+ bookList.toString());

                Book newBook = new Book(3, "Android 開發藝術探索");
                bookManager.addBook(newBook);
                Log.i(TAG,"add book: "+ newBook);

                List<Book> newList = bookManager.getBookList();
                Log.i(TAG,"query book list: "+ newList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    public static void acrtionStart(Context context){
        context.startActivity(new Intent(context, ThirdActivity.class));
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
        Button       btn    = findViewById(R.id.button);
        final Intent intent = new Intent(this, BookManagerService.class);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
            }
        });
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}
Log日誌

業務場景:
假設有一種需求:用戶不想時不時地去查詢圖書列表,他去問圖書館,”當有新書時能不能把書的信息告訴我呢?“,這是一種典型的觀察者模式,每個感興趣的用戶都觀察新書,當新書到的時候,圖書館就通知每一個對這本書感興趣的用戶,下面我們就模擬這種場景


首先,我們需要提供一個 AIDL 接口,每個用戶都需要實現這個接口並向圖書館申請新書的提醒功能,用戶也可以隨時取消這種提醒。
這裏選擇 AIDL 接口而不是普通接口,是因爲 AIDL 中無法使用普通接口。這裏我們創建一個 

IOnNewBookArrivedListener
// IOnNewBookArrivedListener.aidl
package com.example.yhadmin.aidldemo;

// Declare any non-default types here with import statements
import com.example.yhadmin.aidldemo.bean.Book;
interface IOnNewBookArrivedListener {
   void onNewBookArrived(in Book newBook);
}
修改 IBookManager 代碼,新增註冊與註銷監聽器代碼
// IBookManager.aidl
package com.example.yhadmin.aidldemo;

// Declare any non-default types here with import statements
import com.example.yhadmin.aidldemo.bean.Book;
import com.example.yhadmin.aidldemo.IOnNewBookArrivedListener;
interface IBookManager {

   //除了基本數據類型,其他類型的參數都需要標上方向類型:in(輸入), out(輸出), inout(輸入輸出)
   List<Book> getBookList();
   void addBook(in Book book);
   void registerListener(IOnNewBookArrivedListener listener);
   void unregisterListener(IOnNewBookArrivedListener listener);

}
BookManagerService 代碼
public class BookManagerService
        extends Service
{

    private static final String TAG = "BMS";

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

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    //存儲註冊監聽的客戶端集合
    private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerLiCopyOnWriteArrayList<IOnNewBookArrivedListener>(); 

    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 registerListener(IOnNewBookArrivedListener listener)
                throws RemoteException
        {
            if (!mListenerList.contains(listener)){
                mListenerList.add(listener);//添加監聽
            }else {
                Log.d(TAG,"already exists.");
            }
            Log.d(TAG,"regeisterListener,size:"+mListenerList.size());
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener)
                throws RemoteException
        {
            if (mListenerList.contains(listener)){
                mListenerList.remove(listener);//移除監聽
                Log.d(TAG,"unregister listener succeed.");
            }else {
                Log.d(TAG,"not found, can not unregister.");
            }
            Log.d(TAG,"unregeisterListener,size:"+mListenerList.size());
        }

    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "Ios"));
        new Thread(new ServiceWorker()).start();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    //開啓線程,5S 創建一本新書
    private class ServiceWorker implements Runnable{
        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()) {

                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                int bookId = mBookList.size() + 1;
                Book newBook  = new Book(bookId, "new book#" + bookId);

                try {
                    //通知註冊的客戶端,新書已到達
                    onNewBookArrived(newBook);

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

    private void onNewBookArrived(Book newBook)
            throws RemoteException
    {
        mBookList.add(newBook);//將新書添加到存儲書本的集合中
        Log.d(TAG,"onNewBookArrived, notify listeners:"+mListenerList.size());

        for (int i = 0; i < mListenerList.size(); i++) {//便利存儲的客戶端監聽集合
            IOnNewBookArrivedListener listener = mListenerList.get(i);
            Log.d(TAG,"onNewBookArrived, notify listeners:"+listener);
            listener.onNewBookArrived(newBook);//通過接口回調方式通知客戶端新書已到達
        }
    }

}
ThirdActivity 代碼
public class ThirdActivity
        extends AppCompatActivity
{
    private static final String TAG                       = "BookManagerActivity";
    private static final int    MESSAGE_NEW_BOOK_ARRAIVED = 1;
    private IBookManager mRemoteBookManager;

    //構建監聽器 Binder 對象
    /**
     * 當客戶端發起遠程請求時,由於當前線程會被掛起直至服務端進程返回數據,所以如果一個遠程方法是很耗時的,
     * 則不能在UI 線程中發起此遠程請求,爲了避免阻塞UI 線程出現ANR
     * 由於服務端的Binder 方法運行在 Binder 的線程池中,所以 Binder 方法不管是否耗時都應該採用同步的方式去實現,因爲它已經運行在一個線程中了
     */
    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook)
                throws RemoteException
        {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRAIVED, newBook)
                    .sendToTarget();
        }
    };

    //防止Handler 泄漏
    private static  class ClientHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRAIVED:
                    Log.d(TAG, "receive new book :" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    private ClientHandler mHandler    = new ClientHandler();

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //將返回的 IBinder 對象轉成 AIDL 接口所屬類型
            mRemoteBookManager = IBookManager.Stub.asInterface(service);

            try {
                List<Book> bookList = mRemoteBookManager.getBookList();
                //                Log.i(TAG,"query book list, list type:"+ bookList.getClass().getCanonicalName());
                Log.d(TAG, "query book list: " + bookList.toString());

                Book newBook = new Book(3, "Android 開發藝術探索");
                mRemoteBookManager.addBook(newBook);
                Log.d(TAG, "add book: " + newBook);

                List<Book> newList = mRemoteBookManager.getBookList();
                Log.d(TAG, "query book list: " + newList.toString());
                //註冊監聽器
                mRemoteBookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //連接斷開,釋放AIDL Binder對象
            mRemoteBookManager = null;
            Log.e(TAG, "Binder died.");
        }
    };


    public static void acrtionStart(Context context) {
        context.startActivity(new Intent(context, ThirdActivity.class));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
        Button       btn    = findViewById(R.id.button);
        final Intent intent = new Intent(this, BookManagerService.class);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            }
        });
    }

    @Override
    protected void onDestroy() {
        //如果連接持續,並且Binder未死亡
        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();
    }

}
Log 日誌
BookManagerService 

ThirdActivity

從上面log 可以看出,當我們按下 back 鍵退出 ThirdActivity 後,在解註冊的過程中,服務端無法找到我們之前註冊的那個 listener,最終由於服務端無法找到要解除的 listener 而宣告解註冊失敗。這種方式在日常開發中時常用到,但在多進程環境中卻無法奏效。

原因:
        Binder 會把客戶端傳遞過來的對象重新轉化並生成一個新的對象,雖然我們在註冊和解註冊過程中使用的是同一個客戶端,但是通過 Binder 傳遞到服務端後,卻會產生兩個全新的對象。而對象是不能跨進程傳輸的,對象的跨進程傳輸本質上都是反序列化的過程,這就是爲什麼 AIDL 中的自定義對象都必須要實現 Parcelable 接口的原因

解決辦法:
採用RemoteCallbackList
RemoteCallbackList 是系統專門提供的用於刪除跨進程 listener 的類,RemoteCallbackList 是一個泛型,支持管理任意的 AIDL 接口,從它的聲明可以看出,因爲所有的 AIDL 接口都繼承自 IInteface 接口

public class BookManagerService
        extends Service
{
        .....
        //註冊
        @Override
        public void registerListener(IOnNewBookArrivedListener listener)
                throws RemoteException
        {
           /* if (!mListenerList.contains(listener)){
                mListenerList.add(listener);//添加監聽
            }else {
                Log.d(TAG,"already exists.");
            }*/
            mListenerList.register(listener);
            Log.d(TAG,"regeisterListener,current size:"+mListenerList.getRegisteredCallbackCount());


        }
        //解註冊
        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener)
                throws RemoteException
        {
            /*if (mListenerList.contains(listener)){
                mListenerList.remove(listener);//移除監聽
                Log.d(TAG,"unregister listener succeed.");
            }else {
                Log.d(TAG,"not found, can not unregister.");
            }
            */
            mListenerList.unregister(listener);
            Log.d(TAG, "unregeisterListener,current size:"+mListenerList.getRegisteredCallbackCount());

        }
        


  private void onNewBookArrived(Book newBook)
            throws RemoteException
    {
        mBookList.add(newBook);//將新書添加到存儲書本的集合中
      /*  Log.d(TAG,"onNewBookArrived, notify listeners:"+mListenerList.size());

        for (int i = 0; i < mListenerList.size(); i++) {//便利存儲的客戶端監聽集合
            IOnNewBookArrivedListener listener = mListenerList.get(i);
            Log.d(TAG,"onNewBookArrived, notify listeners:"+listener);
            listener.onNewBookArrived(newBook);//通過接口回調方式通知客戶端新書已到達
        }*/
        final  int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
            if (null!=l){

                    l.onNewBookArrived(newBook);

            }
        }
        mListenerList.finishBroadcast();
    }
    ....
}
注意: RemoteCallbackList 並不是一個List ,不能像 List 一樣去操作它,遍歷RemoteCallbackList  必須要以下面的方式進行,其中 beginBroadcast 和 finishBroadcast 必須配套使用,哪怕我們僅僅是想要獲取 RemoteCallbackList 中的元素個數
final  int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
            if (null!=l){

                    l.onNewBookArrived(newBook);

            }
        }
        mListenerList.finishBroadcast();


注意:
    客戶端遠程調用服務端的方法,被調用的方法運行在服務端的 Binder 線程池中,同時客戶端線程會被掛起,此時如果服務端方法執行比較耗時,則會導致客戶端線程長時間阻塞在這裏,如果此時客戶端線程是 UI 線程,則會導致客戶端ANR ,因此如果我們明確知道某個遠程方法是耗時的,則要避免在客戶端的 UI 線程中去訪問遠程方法。
    由於客戶端的 onServiceConnected 和 onServiceDisconnected 方法運行在 UI線程中,故也不可以在他們裏面直接調用服務端的耗時方法。
    服務端方法本身就運行在服務端的Binder 線程池中,故服務端的方法本身就可以進行大量的耗時操作,此時切記不要在服務端開線程去進行異步任務,除非你明確知道自己在幹什麼,否則不建議這麼做

改造服務端getBookList 方法,如下所示:
  @Override
        public List<Book> getBookList()
                throws RemoteException
        {
            SystemClock.sleep(5000);
            return mBookList;
        }
連續單機幾次,就出現了 ANR


如要避免這種 ANR, 只需把調用放在非UI 線程即可

 public void onButtonClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (mRemoteBookManager != null) {
                    try {
                        List<Book> newList = mRemoteBookManager.getBookList();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
    }

同理,當遠程服務端需要調用客戶端的 listener 中的方法時,被調用的方法運行在客戶端的 Binder 池中,故我們同樣不可以在服務端調用客戶端耗時方法,比如針對 BookManagerService 的 onNewBookArrived 方法,在它內部調用了客戶端的 IOnNewBookArrivedListener 中的 onNewBookArrived 方法,如果客戶端的這個 onNewBookArrived  方法比較耗時的話,確保 BookManagerService 的onNewBookArrived 運行在非 UI 線程中,否則將導致服務端無法響應

private void onNewBookArrived(Book newBook)
            throws RemoteException
    {
        mBookList.add(newBook);//將新書添加到存儲書本的集合中
      /*  Log.d(TAG,"onNewBookArrived, notify listeners:"+mListenerList.size());

        for (int i = 0; i < mListenerList.size(); i++) {//便利存儲的客戶端監聽集合
            IOnNewBookArrivedListener listener = mListenerList.get(i);
            Log.d(TAG,"onNewBookArrived, notify listeners:"+listener);
            listener.onNewBookArrived(newBook);//通過接口回調方式通知客戶端新書已到達
        }*/
        final  int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
            if (null!=l){

                    l.onNewBookArrived(newBook);

            }
        }
        mListenerList.finishBroadcast();
    }
同時由於客戶端的 IOnNewBookArrivedListener 中的 onNewBookArrived 方法運行在客戶端的 Binder 池中,故不能在裏面訪問UI相關的內容,如要訪問,請用Handler 切換到主線程

Binder是可能意外死亡的,這往往是由於服務端進程意外停止導致的,此時我們需要重新連接服務。
  1. 給Binder 設置DeathRecipient 監聽,當Binder 死亡時,我們會收到 binderDied 方法的回調,在 binderDied  方法中我們可以重新綁定遠程服務
  2. 在onServiceDisconnected 中重連遠程服務

這兩種方法的區別在於:onServiceDisconnected  在客戶端的 UI 線程中被回調,而 binderDied 在客戶端的Binder 線程池中被回調,即在binderDied 方法中我們不能訪問 UI

DeathRecipient 方式
public class ThirdActivity
        extends AppCompatActivity
{
    private static final String TAG                       = "BookManagerActivity";
    private static final int    MESSAGE_NEW_BOOK_ARRAIVED = 1;
    private IBookManager mRemoteBookManager;

    //構建監聽器 Binder 對象
    /**
     * 當客戶端發起遠程請求時,由於當前線程會被掛起直至服務端進程返回數據,所以如果一個遠程方法是很耗時的,
     * 則不能在UI 線程中發起此遠程請求,爲了避免阻塞UI 線程出現ANR
     * 由於服務端的Binder 方法運行在 Binder 的線程池中,所以 Binder 方法不管是否耗時都應該採用同步的方式去實現,因爲它已經運行在一個線程中了
     */
    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook)
                throws RemoteException
        {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRAIVED, newBook)
                    .sendToTarget();
        }
    };

    //防止Handler 泄漏
    private static class ClientHandler
            extends Handler
    {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRAIVED:
                    Log.d(TAG, "receive new book :" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    private ClientHandler          mHandler        = new ClientHandler();
    //DeathRecipient 方式
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
        if (mRemoteBookManager==null)
            return;
        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
        mRemoteBookManager=null;
        //重新綁定服務
        Intent intent = new Intent(ThirdActivity.this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
    };

    private ServiceConnection      mConnection     = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //將返回的 IBinder 對象轉成 AIDL 接口所屬類型
            mRemoteBookManager = IBookManager.Stub.asInterface(service);
            //客戶端綁定遠程服務成功後,給binder 設置死亡代理
            try {
                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient,0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            try {

                List<Book> bookList = mRemoteBookManager.getBookList();
                //                Log.i(TAG,"query book list, list type:"+ bookList.getClass().getCanonicalName());
                Log.d(TAG, "query book list: " + bookList.toString());

                Book newBook = new Book(3, "Android 開發藝術探索");
                mRemoteBookManager.addBook(newBook);
                Log.d(TAG, "add book: " + newBook);

                List<Book> newList = mRemoteBookManager.getBookList();
                Log.d(TAG, "query book list: " + newList.toString());
                //註冊監聽器
                mRemoteBookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //連接斷開,釋放AIDL Binder對象
            mRemoteBookManager = null;
            Log.e(TAG, "Binder died.");
        }
    };


    public static void acrtionStart(Context context) {
        context.startActivity(new Intent(context, ThirdActivity.class));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
        Button       btn    = findViewById(R.id.button);
        final Intent intent = new Intent(this, BookManagerService.class);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            }
        });
    }

    @Override
    protected void onDestroy() {
        //如果連接持續,並且Binder未死亡
        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();
    }
}
如何在 AIDL 中使用權限驗證功能?
  1. 在onBind 中進行驗證,驗證不通過直接返回null ,這樣驗證失敗的客戶端直接無法綁定服務,至於驗證方式有很多種,比如使用permission 驗證,使用這種驗證方式,我們需要先在 AndroidMenifest 中聲明所需的權限,比如:
<permission android:name="com.example.yhadmin.aidldemo.permission.ACCESS_BOOK_SERVICE"
                android:protectionLevel="normal"/>
permission 驗證?
定義權限後,就可在BookManagerService 的onBinder 中做權限驗證了,如下所示:
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        
        int check = checkCallingOrSelfPermission("com.example.yhadmin.aidldemo.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mBinder;
    }
  一個應用來綁定我們的服務時,會驗證這個應用的權限,如果他沒有使用這個權限,則onBind 方法就會直接返回 null,最終這個應用無法綁定到我們的服務,這樣就達到了權限驗證的效果,這種方法同樣適用於 Messenger中。
        如果我們自己內部的應用想要綁定到我們的服務中,只需在它的 AndroidMenifest 文件中採用如下方式使用 permission 即可

<uses-permission android:name="com.example.yhadmin.aidldemo.permission.ACCESS_BOOK_SERVICE"/>
  2.我們可以在服務端的 onTransact 方法中進行權限驗證,如果驗證失敗就直接返回 falls,這樣服務端就不會終止執行 AIDL 中的方法從而達到保護服務端的效果,
可以採用permission 驗證,具體實現方式和第一種一樣,也可以採用 Uid 和Pid 來做驗證,通過getCallingUid 和 getCallingPid 可以獲取到客戶端所屬應用的 Uid 和Pid,通過這兩個參數我們可以做一些驗證工作,比如驗證包名。下面代碼既驗證了 permission,又驗證了包名,一個應用如果想遠程調用服務中的方法,首先要使用我們自定義的權限
 <uses-permission android:name="com.example.yhadmin.aidldemo.permission.ACCESS_BOOK_SERVICE"/>

其次包名必須以 “com.example.yhadmin” 開始,否則調用服務端的方法會失敗

    

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

        //註冊
        @Override
        public void registerListener(IOnNewBookArrivedListener listener)
                throws RemoteException
        {
           /* if (!mListenerList.contains(listener)){
                mListenerList.add(listener);//添加監聽
            }else {
                Log.d(TAG,"already exists.");
            }*/
            mListenerList.register(listener);
            Log.d(TAG,
                  "regeisterListener,current size:" + mListenerList.getRegisteredCallbackCount());


        }

        //解註冊
        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener)
                throws RemoteException
        {
            /*if (mListenerList.contains(listener)){
                mListenerList.remove(listener);//移除監聽
                Log.d(TAG,"unregister listener succeed.");
            }else {
                Log.d(TAG,"not found, can not unregister.");
            }
            */
            mListenerList.unregister(listener);
            Log.d(TAG,
                  "unregeisterListener,current size:" + mListenerList.getRegisteredCallbackCount());

        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException
        {
            int check = checkCallingOrSelfPermission("com.example.yhadmin.aidldemo.permission.ACCESS_BOOK_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                return false;
            }
            String   packageName = null;
            String[] packages    = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName=packages[0];
            }
            if (!packageName.startsWith("com.example.yhadmin")){
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
    };
還可以爲Service 指定 android:permission屬性等進行權限驗證




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章