美文网首页重学Android
写给小白看的 Binder 使用指南

写给小白看的 Binder 使用指南

作者: SunnyGL | 来源:发表于2020-03-18 23:14 被阅读0次

    相信大家第一次看到 aidl 自动生成的 Binder 类时,内心都是一脸懵逼的。

    “Binder 怎么这么复杂?”

    Binder 的底层实现是很复杂,但使用起来一点都不复杂,而且非常简单。本篇博客内容正如标题所示,专门写给那些不了解 Binder 的小白看的,开始我们会剔除 Binder 使用过程中涉及到的代理模式,希望大家轻松看完的同时,还能够感受到原来 Binder 使用起来这么简单!想了解 Binder 底层原理的,文章最后我会推荐一篇收藏多年,写的极佳的 Binder 解析文章。废话有点多,Talk is cheap. Show me the code.

    Binder 简单使用

    我们先不用 aidl,自己手动写一个最简单的使用 Binder 跨进程通信案例代码——APP 的主进程作为 Client 端,其他进程为 Server 端,Server 端有一个存储 Book 对象的集合,Client 端可以向 Server 端添加书籍和获取所有书籍。

    所有代码都会贴在下面,为了让大家读起来更加轻松,简化掉了很多的非必要的代码,大家写代码时,千万别生搬下面的代码😄。

    Book.java

    /**
     * Book 实体类
     */
    public class Book implements Parcelable {
    
        private int bookId;
    
        private String bookName;
    
        Book(int bookId, String bookName) {
            this.bookId = bookId;
            this.bookName = bookName;
        }
    
        public int describeContents() {
            return 0;
        }
    
        public void writeToParcel(Parcel out, int flags) {
            out.writeInt(bookId);
            out.writeString(bookName);
        }
    
        public static final Parcelable.Creator<Book> CREATOR = new Parcelable.
                Creator<Book>() {
            public Book createFromParcel(Parcel in) {
                return new Book(in);
            }
    
            public Book[] newArray(int size) {
                return new Book[size];
            }
        };
    
        private Book(Parcel in) {
            bookId = in.readInt();
            bookName = in.readString();
        }
    
        @Override
        public String toString() {
            return "Book{" +
                    "bookId=" + bookId +
                    ", bookName='" + bookName + '\'' +
                    '}';
        }
    }
    

    Server.java

    /**
     * 服务端
     */
    public class Server extends Service {
    
        /**
         * getBookList 方法标记
         */
        public static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION);
        /**
         * addBook 方法标记
         */
        public static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
    
        private List<Book> bookList = new ArrayList<>();
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return new IBookManager();
        }
    
        /**
         * Binder
         */
        private class IBookManager extends Binder implements IInterface {
    
            @Override
            public IBinder asBinder() {
                return this;
            }
    
            @Override
            protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
                    throws RemoteException {
                switch (code) {
                    case TRANSACTION_getBookList:
                        reply.writeTypedList(bookList);
                        return true;
                    case TRANSACTION_addBook:
                        Book book = Book.CREATOR.createFromParcel(data);
                        bookList.add(book);
                        return true;
                    default:
                        return super.onTransact(code, data, reply, flags);
    
                }
            }
        }
    }
    

    Client.java

    /**
     * 客户端
     */
    public class Client extends AppCompatActivity {
    
        private TextView mTextView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mTextView = findViewById(R.id.text_view);
    
            bindService();
        }
    
        private void bindService() {
            Intent intent = new Intent(this, Server.class);
            bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        }
    
        private ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                addBook(service);
    
                List<Book> bookList = getBook(service);
    
                StringBuilder stringBuilder = new StringBuilder();
                for (Book book : bookList) {
                    stringBuilder.append(book.toString()).append("\n");
                }
                mTextView.setText(stringBuilder.toString());
            }
    
            private void addBook(IBinder service) {
                Parcel _data = Parcel.obtain();
                Book book = new Book(2, "Android开发艺术探索");
                try {
                    book.writeToParcel(_data, 0);
                    service.transact(Server.TRANSACTION_addBook, _data, null, 0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                } finally {
                    _data.recycle();
                }
            }
    
            private List<Book> getBook(IBinder service) {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                List<Book> bookList = null;
                try {
                    service.transact(Server.TRANSACTION_getBookList, _data, _reply, 0);
                    bookList = _reply.createTypedArrayList(Book.CREATOR);
                } catch (RemoteException e) {
                    e.printStackTrace();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return bookList;
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    }
    

    最后别忘了给 Server 端在 Manifest 里声明运行在其他进程。
    AndroidManifest.xml 部分代码

    <service
        android:name=".Server"
        android:process=":remote" />
    

    上面的代码足够简单,相信大家看过后都能发现原来 Binder 使用起来这么简单。Book 实现了 Parcelable 序列化接口,这样 Book 对象就能进行跨进程传输。

    Server 是一个 Service,在其内部有一个名为 IBookManager 的内部类,IBookManager 继承自 Binder,并且在 Server 的 onBind 方法中,我们 new 了一个 IBookManager 返回了回去,IBookManager 就是我们跨进程通信的信使。

    Client 是一个 Activity,我们在其 onCreate 方法内绑定了 Server 端服务,在 ServiceConnection 的 onServiceConnected 回调中,我们能获取到 IBinder 对象,IBinder 是一个接口,而且 Binder 实现了它,我们通过 IBinder 对象就可以调用 Binder 内部实现的方法。 接着在 addBook 和 getBook 方法内,我们都调用了 IBinder.transact 方法,transact 方法就是向远端的 Binder 对象发送消息,告诉 Server 端我们需要什么数据,我们要调用那个方法,远端 Binder 的 onTransact 方法会接收到消息,transact 方法有四个参数。

    参数 含义
    code 此值应介于 FIRST_CALL_TRANSACTIONLAST_CALL_TRANSACTION 之间,会传递到远端 Binder 的 onTransact 方法内,用于区分 Client 端的调用意图。
    data 此参数中写入待发送的数据,不可为 null
    reply 此参数接收从远端 Binder 发送回来的数据,可以 null
    flags

    远端 Binder 的 onTransact 方法同样有上面的四个参数,只不过 data 变为 Binder 的数据接收参数,replay 变为 Binder 的数据回传参数。

    在 Client 端,通过如下代码将一本新的书籍信息添加到远端服务。

    book.writeToParcel(_data, 0);
    service.transact(Server.TRANSACTION_addBook, _data, null, 0);
    

    在服务端 Binder 的 onTransact 方法内,首先会根据 code 参数判断一下 Client 端的调用意图,发现 code 为 TRANSACTION_addBook,意图为添加一本书,那么就通过如下代码接收 Client 发过来的书籍,添加到 list 中。

    Book book = Book.CREATOR.createFromParcel(data);
    bookList.add(book);
    

    至此,就完成了从 Client 端发送一个 Book 对象到 Server 端。
    Client 端获取 Server 端 Book 集合时,通过如下代码。

    service.transact(Server.TRANSACTION_getBookList, _data, _reply, 0);
    bookList = _reply.createTypedArrayList(Book.CREATOR);
    

    同样是使用 transact 给 Server 发送一条消息,这次因为要接收 Server 端回传数据,所以 reply 参数不为空,然后通过 Book 的反序列化实例化出 Book 集合。最后再看下 Server 端是如何回传数据的。

    reply.writeTypedList(bookList);
    

    回传很简单,调用 reply 的 writeTypedList 方法,把 Book 集合写入进去即可。

    通过上面的案例,我们会发现 Binder 的基础使用,只不过是 Client 端发送一条消息,Server 端接收消息,处理消息而已。但是我们还会发现,Client 端和 Server 端都要单独去写 Binder 的发送消息和接收消息,对象的序列化等代码,如果要在多个地方进行跨进程通信,每个地方都单独去实现,这样我们的代码就会显得很繁琐,于是我们可以使用代理模式,改造一下上面的代码。

    使用代理模式改造

    首先 Book 实体类无需改变,然后我们需要新建一个接口类,用于规定远程服务端功能。

    /**
     * 接口
     */
    public interface IBookManager extends IInterface {
    
        /**
         * getBookList 方法标记
         */
        int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION);
        /**
         * addBook 方法标记
         */
        int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
    
        List<Book> getBookList() throws RemoteException;
    
        void addBook(Book book) throws RemoteException;
    }
    

    Server 端就两个功能,获取 Book 列表和添加一个 Book 对象。接着我们创建 Stub 类,使 IBookManager 最终的实现类能够进行跨进程通信。

    /**
     * 辅助 Server 实现跨进程通信
     */
    public abstract class Stub extends Binder implements IBookManager {
    
        @Override
        public IBinder asBinder() {
            return this;
        }
    
        public static IBookManager asInterface(IBinder iBinder) {
            return new Proxy(iBinder);
        }
    
        @Override
        public boolean onTransact(int code, @NonNull Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case TRANSACTION_getBookList:
                    List<Book> bookList = this.getBookList();
                    reply.writeTypedList(bookList);
                    return true;
                case TRANSACTION_addBook:
                    Book book = Book.CREATOR.createFromParcel(data);
                    this.addBook(book);
                    return true;
                default:
                    return super.onTransact(code, data, reply, flags);
            }
        }
    }
    

    Stub 继承自 Binder,实现了 IBookManager 接口,并且 Stub 是一个抽象类,内部只实现了 Binder 的两个方法,使其能够进行跨进程通信。还要关注一下 asInterface 方法,客户端就是通过此方法拿到 Server 端的代理类。关于 IBookManager 接口真正的实现地方,我们会在 Server 端代码中具体编写。在创建 Server 端代码之前,我们先创建 Proxy 类。

    /**
     * Server 代理类
     */
    public class Proxy implements IBookManager {
        private IBinder mServer;
    
        Proxy(IBinder remote) {
            mServer = remote;
        }
    
        @Override
        public IBinder asBinder() {
            return mServer;
        }
    
        @Override
        public List<Book> getBookList() throws RemoteException {
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            List<Book> bookList;
            try {
                mServer.transact(Server.TRANSACTION_getBookList, _data, _reply, 0);
                bookList = _reply.createTypedArrayList(Book.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return bookList;
        }
    
        @Override
        public void addBook(Book book) throws RemoteException {
            Parcel _data = Parcel.obtain();
            book.writeToParcel(_data, 0);
            try {
                mServer.transact(Server.TRANSACTION_addBook, _data, null, 0);
            } finally {
                _data.recycle();
            }
        }
    }
    

    Proxy 同样实现了 IBookManager 接口,但在其实现方法中,只是调用了 mServer 对象的 transact 方法,实现和 Server 端的通信。接着我们创建 Server 类。

    /**
     * 服务端
     */
    public class Server extends Service {
    
        private List<Book> bookList = new ArrayList<>();
    
        @Override
        public IBinder onBind(Intent intent) {
            return stub;
        }
    
        private Stub stub = new Stub() {
            @Override
            public List<Book> getBookList() {
                return bookList;
            }
    
            @Override
            public void addBook(Book book) {
                bookList.add(book);
            }
        };
    }
    

    我们 Server 端的代码是如此简洁,仅仅是使用匿名内部类的方式,就让 Server 端具备了跨进程通信的能力。最后我们创建 Client 类。

    /**
     * 客户端
     */
    public class Client extends AppCompatActivity {
    
        private TextView mTextView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mTextView = findViewById(R.id.text_view);
    
            bindService();
        }
    
        private void bindService() {
            Intent intent = new Intent(this, Server.class);
            bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        }
    
        private ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IBookManager iBookManager = Stub.asInterface(service);
                try {
                    Book book = new Book(1, "Android开发艺术探索");
                    iBookManager.addBook(book);
    
                    List<Book> bookList = iBookManager.getBookList();
                    StringBuilder stringBuilder = new StringBuilder();
                    for (Book b : bookList) {
                        stringBuilder.append(b.toString()).append("\n");
                    }
                    mTextView.setText(stringBuilder.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    }
    

    Client 端代码也是非常简洁,只是在 serviceConnection 的 onServiceConnected 回调中,通过 Stub.asInterface 方法拿到 Server 端的代理类,然后正常的调用 Server 端的 getBookList 和 addBook 方法即可。对于 Server 和 Client 端来说,无需关心跨进程通信的细节,只需要正常调用 IBookManager 内的方法即可,这就是代理模式的好处。

    如果你用 aidl 去实现上述功能,你会发现上述大部分代理模式相关代码就是 aidl 自动生成代码的拆分。

    源码地址:https://github.com/GaozyDev/EasyBinder

    如果你还想细入了解 Binder 实现的底层细节,那么一定要看下这篇文章。

    《写给 Android 应用工程师的 Binder 原理剖析》

    相关文章

      网友评论

        本文标题:写给小白看的 Binder 使用指南

        本文链接:https://www.haomeiwen.com/subject/iwmhyhtx.html