Binder、AIDL学习笔记

本文是任玉刚《Android开发艺术探索》的学习笔记,介绍Binder的使用以及上层原理

什么是Binder

  • 直观上,Binder是Android的一个类,实现IBider借口
  • 从IPC角度,Binder是Android的一种跨进程通信方式
  • 从硬件角度,Binder是一种虚拟的物理设备,设备驱动是/dev/binder
  • 从Framework角度,Binder是ServiceManager连接各种Manager和ManagerService的桥梁
  • 从应用层角度,Binder是客户端和服务端进行通信的媒介。当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据。这里的服务包括普通服务和基于AIDL的服务

通过AIDL分析Binder的工作机制

Android开发中,Binder主要用在Service中,包括AIDL,其中普通Service中的Binder不涉及进程间的通信,所以较为简单,无法触及Binder的核心。

AIDL (Android Interface Definition Language) 是一种IDL 语言,编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程(IPC)的目的。如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数), 然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象。

实例
涉及三个文件:

Book.java 表示图书信息的类

package com.example.zzh.binderdemo;

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

public class Book implements Parcelable {
    public int bookId;
    public String bookName;
    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        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);
    }

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

    @Override
    public String toString() {
        return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
    }
}

Book.aidl Book类在AIDL中的声明

package com.example.zzh.binderdemo;
parcelable Book;

IBookManager.aidl 定义的接口

package com.example.zzh.binderdemo;

import com.example.zzh.binderdemo.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}   

编译后,在model工程的build/generated目录下,系统为IBookManager.aidl生成了Binder类IBookManager.java

其中这个类核心内容是内部类Stub和Stub的内部代理类Proxy

Stub是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程;

当二者在不同进程中时,方法调用需要走transact过程,由Proxy来完成。

介绍针对这两个类的买个方法的含义:

  • DESCRIPTOR

    Binder的唯一标识,一般用当前Binder的类名表示,如***.***.IBookManager

  • asInterface(android.os.IBinder obj)

    用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程,
    如果客户端和服务端位于同一进程,那么这个方法返回就是服务端的Stub对象本身,反则返回的是
    系统封装后的Stub.proxy对象。

  • asBinder

    返回当前Binder对象。

  • onTransact

    这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统封装后
    交由此方法来处理。当这个方法返回false时,那么客户端的请求会失败,因此可以利用这个特性来做
    权限验证(避免随便一个进程都能远程调用我们的服务)。

  • Proxy#getBookList Proxy#addBook

    这两个方法运行在客户端中。内部实现是:首先创建该方法所需要的输入型Parcel对象_data、输出型
    Parcel对象_reply和返回值对象List(addBook没有返回值);然后把该方法的参数信息写入_data中
    (如果有参数的话);接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;
    然后服务的onTransaction方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取
    出RPC过程的返回结果;最后返回_reply中的数据。

  • 当客户端发起远程请求时,由于当前线程会挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,那么不能在UI线程中发起此远程请求。
  • 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应采用同步的方式去实现,因为它已经运行一个线程中。

Binder的工作机制图

Binder两个重要的方法

Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候与服务端的Binder连接断裂,导致远程调用失败。

linkToDeath unlinkToDeath

给Binder设置一个死亡代理,当Binder死亡时,收到通知,可以重新发起连接请求从而恢复连接:

首先,声明一个DeathRecipient对象。当Binder死亡时候,系统就回调DeathRecipient接口方法binderDied

然后就可以移除之前绑定的Binder代理并重新绑定远程服务。

IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (mRemoteBookManager == null) {
            return;
        }
        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mRemoteBookManager = null;

        //TODO 这里重新绑定远程Service
    }
};

其次,在客户端绑定远程Service成功后,给binder设置死亡代理

IBookManager bookManager = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);

另外,通过Binder的方法isBinderAlive也可以判断Binder是否死亡。

基于AIDL的IPC

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

  • 服务端

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

  • 客户端

    首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,最后
    在Service中实现这个AIDL接口即可。

AIDL文件支持的数据类型

  • 基本数据类型(int、long、char、boolean等)
  • String和CharSequence
  • List: 只支持ArrayList, 里面每个元素都必须能够被AIDL支持
  • Map: 只支持HashMap, 里面的每个元素都必须被AIDL支持,包括key和value
  • Parcelabe: 所有实现了Parcelable接口的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

    其中自定义的Parcelable对象和AIDL对象必须要显式import进来,无论是否位于同一个包内。
    如果AIDL文件用到了Parcelabl对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为
    Parcelable类型。如上个实例中Book.aidl

RemoteCallbackList

如果服务端提供一些监听给客户端,那么我们需要采用RemoteCallbackList,才能实现在客户端取消
注册功能。RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。
其中原理是:虽然跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一
共同点,那就是它们底层的Binder是同一个,利用这个特性(Array

实例

远程服务端Service的实现

public class BookManagerService extends Service {

    private static final String TAG = "BMS";

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);

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

    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList =
        new RemoteCallbackList<>();

    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 {
            mListenerList.register(listener);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
    };

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

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

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

        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
        IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
        if (l != null) {
            l.onNewBookArrived(book);
        }
    }
        mListenerList.finishBroadcast();
}

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

客户端的实现

public class BookManagerActivity extends Activity {

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

    private IBookManager mRemoteBookManager;

    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);
                    break;
                default:
                    break;
            }
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                mRemoteBookManager = bookManager;
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "onServiceConnected: query book list, list type:" + list.getClass().getCanonicalName());
                Log.i(TAG, "onServiceConnected: query book list:" + list.toString());
                Book newBook = new Book(3, "Android开发艺术探索");
                bookManager.addBook(newBook);
                Log.i(TAG, "add Book:" + newBook);
                List<Book> newList = bookManager.getBookList();
                Log.i(TAG, "onServiceConnected: query book list:" + newList.toString());
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteBookManager = null;
            Log.e(TAG, "binder died.");
        }
    };

    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 onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if (mRemoteBookManager != null
            && mRemoteBookManager.asBinder().isBinderAlive()) {
            try {
                Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
                mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }
}

在AIDL中使用权限验证功能的方式(保证权限验证成功的一方才能连接远程服务)

  • 在onBind中验证,验证不通过返回null,这样验证失败的客户端直接无法绑定服务。
  • 在服务端的onTransact方法中进行权限验证,如果验证失败就返回false,这样服务端就不会终止执行AIDL中的方法从而达到保护服务端的效果。

猜你喜欢

转载自blog.csdn.net/zhanhong39/article/details/78766531