欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android binder

程序员文章站 2024-03-24 09:50:34
...

Binder在Android的重要性就不说了。看了很多文章,自己想总结一番,发现还是太菜,有那么多大牛已经说的很好,且都有系统性讲解。所以本篇参照《Android开发艺术探索》,重要记录下binder的使用,感受下其在IPC中的媒介作用,本篇作为笔记使用。

推荐文章

  1. 罗升阳Android进程间通信(IPC)机制Binder简要介绍和学习计划
  2. Gityuan Binder系列—开篇
  3. 图文详解 Android Binder跨进程通信的原理
  4. Android Binder 完全解析(一)概述

实在看不下去,就看第3篇吧。
这里借用一张图。
Android binder

明白几点
1. 各个进程相互隔离,数据不可共享。此处指的时用户空间。
2. 每个进程可分为用户空间、内核空间。用户空间数据不可共享,内核空间可以。
3. 1/2/3步骤的虚线表示我们开发中感受到的流程,也是Android想实现的CS流程。实际过程并不能直接联系。
4. Client、Server、Service Manager在用户空间,Binder驱动在内核空间。
5. 用户可以实现的时应用层内容,Client、Server;Service Manager和Binder是Android平台已经实现好了的。

下面我们看下Android中怎样使用Binder实现IPC。
< Android开发艺术探索 >

直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,他的设备驱动是/dev/binder,该通信方式linux中没有;从Android Framework 角度来说,Binder是ServiceManager 连接各种Manager(ActivityManager、WindowManager等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当binderService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务和数据,这里的服务包括普通服务和AIDL服务。

在Android开发中,Binder一般用在service,而一般的service的Binder并不涉及进程间通信,这里我们主要分析AIDL。

在Android studio 中创建aidl

AIDL是Android中定义的接口语言,用它能实现IPC跨进程通信。
studio中生成aidl还是很方便的,使用自动生成即可。
Android binder

之后,studio会自动为我们在main下创建一个aidl文件夹,里面的包名还是跟项目包名一致,看图。
Android binder

此时的IBookManager里面还没有东西,basicTypes只是一个举例,看英文解释(举例一些在AIDL中可以作为参数和返回值的基本类型),其实哪能就这些,还玩儿不玩儿了,可参考官方说明Android 接口定义语言 (AIDL)

interface IBookManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

分别在aild的对象包名下面,创建Book.java、Book.aidl,修改IBookManager.aidl,代码如下。
Book

package com.breezehan.ipc;

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

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

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

    protected 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.breezehan.ipc;

parcelable Book;

IBookManager.aidl

// IBookManager.aidl
package com.breezehan.ipc;

import com.breezehan.ipc.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

然后我们make project,就能在build中看到系统为我们生成的完整的IBookManager
Android binder

注意,注意!有人说,毛线啊,我都报错了好不!
Android binder

那是啊,没有错才有鬼呢。
因为默认我们的java文件都是在main->java下面的,你写在了adil下面,又没告诉studio,那肯定找不到了。
需要我们在module的build.gradle中设置。

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"

    defaultConfig {
       ***
    }
    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }
 }

分析IBookManager

上面三个类Book实现parcelable接口,Book.aidl是因为adil的特殊情况需要添加;IBookManager是一个接口,此处虽然和Book在同一个包中,但是仍需import,也是aidl特殊之处。
系统会根据IBookManager中的信息(比如里面我们添加了两个方法)生成对应的Binder类,看一下build->generated->source->aidl->com.breezehan.ipc

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\android_work\\workspace_studio\\AndroidKnowledgeSummary\\ipc\\src\\main\\aidl\\com\\breezehan\\ipc\\IBookManager.aidl
 */
package com.breezehan.ipc;

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.breezehan.ipc.IBookManager {
        // 1.Binder的唯一标识,一般使用当前类名表示,带包名。
        private static final java.lang.String DESCRIPTOR = "com.breezehan.ipc.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        // 2. 用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这里转换时区分进程的,如果客户端和服务端处于同一进程,那么返回的就是Stub对象本身,否则返回的就是系统封装后的Stub.Proxy对象。
        /**
         * Cast an IBinder object into an com.breezehan.ipc.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.breezehan.ipc.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.breezehan.ipc.IBookManager))) {
                return ((com.breezehan.ipc.IBookManager) iin);
            }
            return new com.breezehan.ipc.IBookManager.Stub.Proxy(obj);
        }
        // 3.返回当前binder对象
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        // 4.这个方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会被系统底层封装后交由此方法来处理。服务端通过code判断客户端请求的方法是哪个,接着从data取出目标方法所需参数(有参数的话),然后执行目标方法。当目标方法执行完毕后,就像reply中写入返回值(目标方法有返回值的话)。另外,onTransact有个boolean的返回值,当是false的时候,客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程就能调用我们的服务。
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.breezehan.ipc.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.breezehan.ipc.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.breezehan.ipc.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.breezehan.ipc.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
            // 5.这个方法运行在客户端,当客户端远程调用此方法时:首先创建本方法所需的输入型Parcel对象_data、输出型Parcel对象_reply以及返回值对象List;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact方法发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法调用,知道RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;然后返回_reply中的数据。
            @Override
            public java.util.List<com.breezehan.ipc.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.breezehan.ipc.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.breezehan.ipc.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.breezehan.ipc.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public java.util.List<com.breezehan.ipc.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.breezehan.ipc.Book book) throws android.os.RemoteException;
}

上面代码是自动生成的,这里只是格式化了一下。IBookManager本身是一个接口,这里又继承了IInterface接口(IInterface里面只有一个asBinder方法),但是想要在Binder中传输的接口都需要继承IInterface。
1. 里面有两个方法getBookList和addBook,这是我们在IBookManager.aidl文件中声明的。
2. 同时此处声明了一个内部类Stub,它是一个Binder,跨进程时,通过代理proxy的transact,最终在onTransact中根据不同id(不同id对应IBookManager的方法)执行相应的不同方法;同一个进程时,直接执行IBookManager中的方法。

两点需要注意:
1. 客户端发起远程请求时,会挂起一直到服务端返回数据,所以如果一个远程方法时很耗时的,不要在UI线程中发起此远程请求;
2. 由于服务端的Binder方法已经在Binder线程池,所以不管Binder方法是否耗时都应该采取同步的方式,因为已经在一个线程中了。下面是Binder机制图(来自Android开发艺术探索):
Android binder

自己实现Binder

如果我们想要实现完整的IPC,需要一个Service

public class BookService extends Service{
    private List<Book> mBookList = new ArrayList<>();
    private IBinder binder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

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

}

当然,清单文件中需配置

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

调用

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager iBookManager = IBookManager.Stub.asInterface(service);
            try {
                iBookManager.addBook(new Book(1, "Java编程思想"));
                iBookManager.addBook(new Book(2, "Android开发艺术探索"));
                List<Book> bookList = iBookManager.getBookList();
                Log.d(TAG, "onServiceConnected:" + bookList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService(new Intent(this, BookService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
    }

上面即是一个完整的IPC过程。
使用AIDL,我们不用关心很多东西,系统会帮助规范生成一系列的内容,那么我们能不能不借助AIDL,直接使用binder做到IPC呢。
先看service中binder的使用

private IBinder binder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

所以我们可以实现一个Stub的对象,并且实现了IBookManager的方法;总体上,一个Stub,一个IBookManager就可以完事儿了。其他还一样,如Service的onBind。

手动实现Binder步骤
(1)、声明一个AIDL性质的接口,只需要继承IInterface即可。

public interface IUserManager extends IInterface {
    public static final String DESCRIPTOR = "com.breezehan.ipc.IUserManager";

    public final static int TRANSACTION_getUserList = IBinder.FIRST_CALL_TRANSACTION + 0;
    public final static int TRANSACTION_addUser = IBinder.FIRST_CALL_TRANSACTION + 1;

    public List<User> getUserList() throws RemoteException;

    public void addUser(User user) throws RemoteException;
}

看,是不是感觉像回事儿,当前此处 两个方法对象的id常量,我们仿照系统自动生成的写法,就是一个规则。无论多少方法,我们类似添加即可。
其中的public、static、final其实不明写,接口中这些都是默认的。
(2)实现Stub类,和Stub类中的Proxy代理类,不过此处写出来和系统生成几乎是一样的。

public class UserManagerImpl extends Binder implements IUserManager {
    private List<User> implList = new ArrayList<>();

    public UserManagerImpl() {
        this.attachInterface(this, DESCRIPTOR);
    }

    public static IUserManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IInterface iIn = obj.queryLocalInterface(DESCRIPTOR);
        if (iIn != null && iIn instanceof IUserManager) {
            return (IUserManager) iIn;
        }
        return new UserManagerImpl.Proxy(obj);
    }

    @Override
    public List<User> getUserList() throws RemoteException {
        //逻辑代码
        return implList;
    }

    @Override
    public void addUser(User user) throws RemoteException {
        //逻辑代码
        implList.add(user);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;
            case TRANSACTION_addUser:
                data.enforceInterface(DESCRIPTOR);
                User _arg0;
                if (0 != data.readInt()) {
                    _arg0 = User.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                addUser(_arg0);
                reply.writeNoException();
                return true;
            case TRANSACTION_getUserList:
                data.enforceInterface(DESCRIPTOR);
                List<User> _result = getUserList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }

    static class Proxy implements IUserManager {
        private IBinder mRemote;

        public Proxy(IBinder remote) {
            this.mRemote = remote;
        }

        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public List<User> getUserList() throws RemoteException {
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            List<User> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(TRANSACTION_getUserList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(User.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public void addUser(User user) throws RemoteException {
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                if (user != null) {
                    _data.writeInt(1);
                    user.writeToParcel(_data, 0);
                } else {
                    _data.writeInt(0);
                }
                mRemote.transact(TRANSACTION_addUser, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }

        }

        @Override
        public IBinder asBinder() {
            return mRemote;
        }
    }
}

完整代码参考手动实现Binder

结语

搞了半天,自己写的跟系统生成的差不多吗。手动去写,加深下Binder的理解,也知道AIDL并不是实现Binder的必须,只不过是系统的快速实现。原理都一样。
当Binder服务端因为某些原因异常终止,客户端会调用失败,最关键的是我们不知道两者连接是否已经断裂。这就需要给Binder设置一个死亡代理,当Binder停止时,客户端会收到一个通知,这时就可以做一些操作。

private IBookManager iBookManager;
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (iBookManager != null) {
            iBookManager.asBinder().unlinkToDeath(deathRecipient, 0);
            iBookManager = null;
            //重新连接或者其他操作
        }
    }
};
private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        iBookManager = IBookManager.Stub.asInterface(service);
        try {
            service.linkToDeath(deathRecipient, 0);
            iBookManager.addBook(new Book(1, "Java编程思想"));
            iBookManager.addBook(new Book(2, "Android开发艺术探索"));
            List<Book> bookList = iBookManager.getBookList();
            Log.d(TAG, "onServiceConnected:" + bookList.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};