Android AIDL原理

前言

本文参考自Android开发艺术探索第二章,AIDL(Android接口定义语言)是Android提供的一种进程间通信机制,我们可以利用它定义客户端与服务端相互通信都认可的编程接口。先来看看AIDL的用法,然后通过源码理解下AIDL生成的java类文件

一、基本用法

下面我以一个简单的跨进程进行添加Book,查看BookList。这里使用两个App为例,首先编写一个Book类该类实现了Parcelable接口,代码如下

public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    private Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }
    public Book(int id, String name) {
        bookId = id;
        bookName = name;
    }
    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,IBookManager.aidl源码如下

// Book.aidl
parcelable Book;

// IBookManager.aidl
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
复制代码

然后把Book.java、Book.aidl、IBookManager.aidl分别复制到client、server两个module,接着在server这个module里面新建一个BookService,再在清单文件中配置IntentFilter,代码如下

class BookService : Service() {

    // 由于addBook方法运行在Binder线程中并发调用可能会出问题因此使用CopyOnWriteArrayList
    private val mBookList = CopyOnWriteArrayList<Book>()

    override fun onBind(intent: Intent?): IBinder? {
        return object : IBookManager.Stub() {
            override fun registerListener(listener: IOnNewBookArrivedListener?) {
                // listener 为IOnNewBookArrivedListener.Stub.Proxy实例
                mListener.register(listener)
            }
            override fun unregisterListener(listener: IOnNewBookArrivedListener?) {
                mListener.unregister(listener)
            }
            override fun getBookList(): MutableList<Book> {
                return mBookList
            }
            override fun addBook(book: Book?) {
                mBookList.add(book)
            }
        }
    }
}
<service android:name=".BookService" android:exported="true" android:enabled="true">
    <intent-filter>
        <action android:name="com.hefuwei.testaidl.bookService" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>
复制代码

最后在client这个module的MainActivity与BookService进行通信,MainActivity代码如下,需要注意的是Intent得设置将要启动的应用程序的包名这里就是Server这个module的包名

// 这里不需要使用DeathRecipient因为onServiceConnected其实就是使用DeathRecipient实现的,两者
// 的区别是onServiceDisconnected运行在主线程中,而DeathRecipient.binderDied运行在Binder线程池中
class MainActivity : AppCompatActivity() {
    private lateinit var mListener: IOnNewBookArrivedListener
    private var mManager: IBookManager? = null
    private var mBookID = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mConnection = object : ServiceConnection {
            override fun onServiceDisconnected(name: ComponentName?) {
                mManager = null
            }
            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                mManager = IBookManager.Stub.asInterface(service)
            }
        }
        val intent = Intent()
        intent.action = "com.hefuwei.aidl.BookService"
        intent.setPackage(packageName)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }
    fun addBook(v: View) {
        mManager?.addBook(Book(++mBookID, "第${mBookID}本书"))
    }
    fun allBooks(v: View) {
        Log.d("AIDL", mManager?.bookList?.toString())
    }
    override fun onDestroy() {
        super.onDestroy()
        unbindService(mConnection)
    }
}
复制代码

至此每当点击Client就可以与Server进行通信了,来总结下用法

  • 创建AIDL文件 IBookManager.aidl
  • 将 IBookManager.aidl拷贝到Client和Server下
  • Server创建一个服务,在onBind的时候返回一个IBookManager.Stub的实例
  • Server的清单文件中配置该服务(IntentFilter)
  • Client通过创建Intent(需要设置Server的包名)调用bindService进行绑定
  • ServerConnection的onServiceConnected回调中调用IBookManager.Stub.asInterface拿到IBookManager.Stub.Proxy对象
  • 调用IBookManager.Stub.Proxy的指定方法,即可完成与Server的通信

接下来看看系统为我们生成的IBookManager.java的源码

二、源码分析

生成的代码如下所示

public interface IBookManager extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.hefuwei.testaidl.aidl.IBookManager {
        // 1
        private static final java.lang.String DESCRIPTOR = "com.hefuwei.testaidl.aidl.IBookManager";
        // 2
        public Stub() {
            this.attachInterface(this, DESCRIPTOR); 
        }
        // 3
        public static com.hefuwei.testaidl.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.hefuwei.testaidl.aidl.IBookManager))) {
                return ((com.hefuwei.testaidl.aidl.IBookManager) iin);
            }
            return new com.hefuwei.testaidl.aidl.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        // 6
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) 
        throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.hefuwei.testaidl.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(descriptor);
                    com.hefuwei.testaidl.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.hefuwei.testaidl.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.hefuwei.testaidl.aidl.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;
            }

            @Override
            public java.util.List<com.hefuwei.testaidl.aidl.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.hefuwei.testaidl.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.hefuwei.testaidl.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            // 4
            @Override
            public void addBook(com.hefuwei.testaidl.aidl.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();
                }
            }
        }
        // 7
        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.hefuwei.testaidl.aidl.Book> getBookList() throws android.os.RemoteException;
    public void addBook(com.hefuwei.testaidl.aidl.Book book) throws android.os.RemoteException;
}
复制代码

重点代码解释如下

  1. 描述符,该值为IBookManager的全类名
  2. 根据AIDL的使用流程,Server会在onBind的时候返回一个IBookManager.Stub实例,调用了Stub的构造器内部调用Binder的attachInterface方法将当前实例以及描述符存到Binder实例中
  3. Client在onServiceConnect回调中会调用IBookManager.asInterface,该方法内部调用queryLocalInterface如果该方法返回不为null,那么就能证明Server和Client属于同一个进程,那么就直接强转,不然就返回一个IBookManager.Stub.Proxy对象
  4. Client调用asInterface方法后拿到了一个IBookManager.Stub.Proxy实例,然后会调用addBook方法,如果相同进程会直接调用,不同进程就会调用到注释5处
  5. 先获取两个Parcel实例一个用于存储参数另一个存储返回值(Parcel内部维护了一个Parcel数组,数组中有元素时会取数组中的没有会创建一个),然后在_data中先写入当前的描述符,接着如果book不为null在_data中写入1和book其中1表示数据为一个,如果为null就写入0表示没有数据,然后调用transact方法将_data、_reply传入,该方法会挂起当前的线程(如果方法没有被设置为oneWay),然后系统会在Binder线程池中调用Server端对应的Binder实例的onTransact方法,也就是注释6的地方
  6. 方法内部从data这个Parcel实例中读取一个int,如果为0那么表示没有参数直接调用Server端实现类的addBook将null传入,如果不是0就根据data创建一个Book实例,然后调用addBook将创建的实例传入,然后调用reply.writeNoExceptio指明没有异常发生,由于addBook没有返回值所有就直接结束了。getBookList其实流程也差不多就不解释了
  7. 常量,用来标志调用的是哪个方法

三、注册监听

上面只说到了客户端调用服务端的方法,但是假设客户端想要监听服务端书籍数量的变化,一种做法是一直不停的轮询去请求,另一种做法是使用观察者模式注册监听,这里讲讲怎么注册观察者,首先创建IOnNewBookArrivedListener.aidl文件,代码如下

interface IOnNewBookArrivedListener {
    void onBookArrived(in Book book);
}
复制代码

然后修改下IBookManager.aidl添加注册和解除注册两个方法

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}
复制代码

接着将这两个aidl文件同步给Server和Client,然后修改Client的MainActivity的代码

class MainActivity : AppCompatActivity() {
    private lateinit var mConnection: ServiceConnection
    private lateinit var mListener: IOnNewBookArrivedListener
    private var mManager: IBookManager? = null
    private var mBookID = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mConnection = object : ServiceConnection {
            override fun onServiceDisconnected(name: ComponentName?) {
                mManager = null
            }
            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                mManager = IBookManager.Stub.asInterface(service)
            }
        }
        mListener = object : IOnNewBookArrivedListener.Stub() {
            override fun onBookArrived(book: Book?) {
                // 运行在客户端的Binder线程池中
                runOnUiThread {
                    Toast.makeText(this@MainActivity, "${book?.id}", Toast.LENGTH_SHORT).show()
                }
            }
        }
        val intent = Intent()
        intent.action = "com.hefuwei.aidl.BookService"
        intent.setPackage(packageName)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }
    fun addBook(v: View) {
        mManager?.addBook(Book(++mBookID, "第${mBookID}本书"))
    }
    fun allBooks(v: View) {
        Log.d("AIDL", mManager?.bookList?.toString())
    }
    fun register(v: View) {
        mManager?.registerListener(mListener)
        Toast.makeText(this, "注册", Toast.LENGTH_SHORT).show()
    }
    fun unregister(v: View) {
        mManager?.unregisterListener(mListener)
        Toast.makeText(this, "解除注册", Toast.LENGTH_SHORT).show()
    }
    override fun onDestroy() {
        super.onDestroy()
        unbindService(mConnection)
    }
}
复制代码

最后修改下服务端的BookService代码

class BookService : Service() {
    private val mBookList = CopyOnWriteArrayList<Book>()
    private val mListener = RemoteCallbackList<IOnNewBookArrivedListener>()
    override fun onBind(intent: Intent?): IBinder? {
        return object : IBookManager.Stub() {
            override fun registerListener(listener: IOnNewBookArrivedListener?) {
                // listener 为IOnNewBookArrivedListener.Stub.Proxy实例
                mListener.register(listener)
            }
            override fun unregisterListener(listener: IOnNewBookArrivedListener?) {
                mListener.unregister(listener)
            }
            override fun getBookList(): MutableList<Book> {
                return mBookList
            }
            override fun addBook(book: Book?) {
                mBookList.add(book)
            }
        }
    }
    override fun onCreate() {
        super.onCreate()
        Executors.newScheduledThreadPool(1).scheduleWithFixedDelay({
            val n = mListener.beginBroadcast()
            for (i in 0 until n) {
                mListener.getBroadcastItem(i).onBookArrived(Book(12, "12书"))
            }
            mListener.finishBroadcast()
        }, 0, 3, TimeUnit.SECONDS)
    }
}
复制代码

注意这里需要使用RemoteCallbackList来存储listener,因为registerListener的入参是一个IOnNewBookArrivedListener.Stub.Proxy对象,每次IPC都会创建一个新的对象,这样就没法解除注册了。RemoteCallbackList内部维护了一个Key为Binder实例的Map,虽然每次会创建一个新的Proxy对象,但是这个Proxy对象的mRemote成员一直是同一个因此可以进行区分。而当服务端的数据发送变化后就可以从RemoteCallbackList取出Proxy对象调用客户端的方法

注意:如果通过AIDL传递一个Binder对象会先调用Parcel.writeStrongBinder将Binder写入,然后在目标线程取出时会调用Parcel.readStrongBinder将Binder读出,如果写入端和读出端位于两个进程,读取到的时候会是一个BinderProxy对象而不是原先的Binder对象

转载于:https://juejin.im/post/5cb537d7f265da038f7736c3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值