博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

进程间通信之AIDL

Posted on 2017-07-05 10:03  行歌  阅读(567)  评论(0编辑  收藏  举报

一、引言

  AIDL是android内部进程通信接口的描述语言,是实现跨进程方法调用的一大利器,其中Binder和Messenger的实现机制都是AIDL。

二、使用下面结合示例说明其使用过程:

  本次示例的基本要求是完成一个图书馆图书入库和在库图书列表查询的demo,

  1、为了完成这个功能,我们首先需要一个实体类Book,这个实体类需要序列化,因为只有序列化以后的Book对象才能在AIDL中使用。

  2、接下来我们需要新建Book.aidl和IBookManager.aidl

         我们需要在Book.aidl声明这个Book类,并在IBookManager中导入Book.aidl并实现两个功能:addBook和getBookList

  3.reBuild项目,这样就会自动生成IbookManager.java这个AIDL文件。(如果查找不到Book类,请参看我的另外一篇文章,Binder的机制浅析)

  4.接下来的我们就需要在客户端和服务端完成对应的工作:

 下面简单介绍一下Service和Client中的实现内容。(具体代码在最后贴出)

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

 

 

 

三、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实现)。

四、健壮性设计

  因为Binder可以会因为特殊原因意外死亡,所以我们需要在Binder意外死亡时选择重新连接Binder,以提高通信的稳定性。

  1.  给Binder设置死亡监听,具体方法前面提到过

  2.  在onServiceDisconnected重新连接远程服务。