Android 开发艺术探索笔记之进程通信 AIDL 的使用

为什么使用AIDL?
    Messenger 是使用串行方式处理客户端发送过来的消息,如果有大量的并发请求,则Messenger 就不合适了,Messenger 主要用于传递消息,如果我们需要跨进程调用服务端的方法,Messenger 就不发做到了,但 AIDL 则可以实现

使用AIDL 进行进程间通信的流程:

服务端
  1. 服务端首先要创建一个 Service 用来监听客户端的请求
  2. 创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明
  3. 最后在 Service 中实现这个 AIDL 接口

客户端
  1. 首先需要绑定服务端的 Service
  2. 绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属类型,接着就可以调用AIDL中的方法了

AIDL 接口的创建

目录结构

Android 开发艺术探索笔记之进程通信 AIDL 的使用_第1张图片


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 CREATOR = new Creator() {
        @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 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 的规范,我们仍然需要显示的导包进来,否则就会抛出如下异常
Android 开发艺术探索笔记之进程通信 AIDL 的使用_第2张图片
如果 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 mBookList = new CopyOnWriteArrayList();
    //创建Binder 对象,即实现IBookManager 接口
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List 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 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 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 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 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 mBookList = new CopyOnWriteArrayList();

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    //存储注册监听的客户端集合
    private CopyOnWriteArrayList mListenerLiCopyOnWriteArrayList(); 

    private Binder                                          mBinder       = new IBookManager.Stub() {
        @Override
        public List 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 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 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 
Android 开发艺术探索笔记之进程通信 AIDL 的使用_第3张图片
ThirdActivity
Android 开发艺术探索笔记之进程通信 AIDL 的使用_第4张图片
从上面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();

Android 开发艺术探索笔记之进程通信 AIDL 的使用_第5张图片
注意:
    客户端远程调用服务端的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端线程会被挂起,此时如果服务端方法执行比较耗时,则会导致客户端线程长时间阻塞在这里,如果此时客户端线程是 UI 线程,则会导致客户端ANR , 因此如果我们明确知道某个远程方法是耗时的,则要避免在客户端的 UI 线程中去访问远程方法。
      由于客户端的 onServiceConnected 和 onServiceDisconnected 方法运行在 UI线程中,故也不可以在他们里面直接调用服务端的耗时方法。
    服务端方法本身就运行在服务端的Binder 线程池中,故服务端的方法本身就可以进行大量的耗时操作, 此时切记不要在服务端开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做

改造服务端getBookList 方法,如下所示:
  @Override
        public List getBookList()
                throws RemoteException
        {
            SystemClock.sleep(5000);
            return mBookList;
        }
连续单机几次,就出现了 ANR

Android 开发艺术探索笔记之进程通信 AIDL 的使用_第6张图片


如要避免这种 ANR, 只需把调用放在非UI 线程即可

 public void onButtonClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (mRemoteBookManager != null) {
                    try {
                        List 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 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 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 验证?
定义权限后,就可在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 即可

  2.我们可以在服务端的 onTransact 方法中进行权限验证,如果验证失败就直接返回 falls,这样服务端就不会终止执行 AIDL 中的方法从而达到保护服务端的效果,
可以采用permission 验证,具体实现方式和第一种一样,也可以采用 Uid 和Pid 来做验证,通过getCallingUid 和 getCallingPid 可以获取到客户端所属应用的 Uid 和Pid,通过这两个参数我们可以做一些验证工作,比如验证包名。下面代码既验证了 permission,又验证了包名,一个应用如果想远程调用服务中的方法,首先要使用我们自定义的权限
 

其次包名必须以 “com.example.yhadmin” 开始,否则调用服务端的方法会失败

    

 private Binder                                        mBinder             = new IBookManager.Stub() {
        @Override
        public List 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属性等进行权限验证




你可能感兴趣的:(IPC)