携手创造,共同生长!这是我参与「日新计划 8 月更文应战」的第4天,点击检查活动详情
前言
前面文章咱们重点剖析了Linux的进程划分,以及为了减少一次复制的内存映射技术,本篇文章咱们就来介绍Android中运用最多也最具有代表性的IPC办法:AIDL。
本篇文章主要侧重AIDL的运用细节,然后重点剖析AIDL生成的文件,经过手写AIDL生成文件来大致理解Binder机制,话不多说,直接开整。
正文
咱们先来看看AIDL的运用。
AIDL运用
AIDL的原理是经过Binder来完成,并且是C/S架构,所以AIDL分为服务端和客户端2个方面来运用:
- 服务端:服务端首要需求创立一个Service用来监听客户端的衔接恳求,然后创立一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最终在Service中完成这个AIDL接口。
从这个简略描述咱们知道,作为服务端有必要建立在Service上,这也十分好理解,Service作为四大组件之一,由Android体系统一办理,一起自己就能够被其他进程所发动;
- 客户端:客户端首要就需求绑定服务端的Service,绑定成功后,将服务端回来的Binder方针转成AIDL接口所属的类型,接着就能够调用AIDL接口中的办法了。
从客户端完成咱们可知这儿的关键便是Binder了,本篇文章后边咱们在运用AIDL时会简略阐明一些Binder相关类的效果。
为了更好地阐明AIDL运用的细节,这儿我运用俩个APP交互的办法,而不是一个APP开启多进程。
AIDL接口的创立
AIDL接口的的创立十分重要,首要是需求界说在不同进程之间传递的自界说类型,比如我这儿界说为Book:
//包名
package com.zyh.ipc;
import android.os.Parcel;
import android.os.Parcelable;
//有必要完成Parcelable接口
public class Book implements Parcelable {
public int BookId;
public String BookName;
...省掉
}
这是一个Book.java类,有必要完成Parcelable接口,然后便是寄存该类的目录,这儿推荐把触及到AIDL通讯的自界说类都放到一个相同的目录中,这是因为俩个进行跨进程通讯的APP会对自界说的类型方针进行序列化和反序列化,而这种序列化办法会记录类的信息,即便俩个APP中都界说了Book类,并且成员变量都相同,可是因为包名不相同,相同是无法反序列化的。
界说完自界说类型后,但凡在AIDL文件中用到的自界说Parcelable类型,都有必要新建一个和它同名的AIDL文件,并且在其间声明它为Parcelable类型,而AIDL文件的目录,能够运用AS直接创立出来,这儿在AIDL目录下创立一个Book.aidl:
//Book.aidl
package com.zyh.ipc;
parcelable Book;
再然后咱们就能够界说AIDL接口了,该接口便是界说IPC通讯的内容,留意这个接口和Java接口有一点不相同,它是只支撑办法的,不支撑声明静态常量,在本例中创立办理Book的接口IBookManager.aidl:
//包名
package com.zyh.ipc;
//有必要手动导入自界说的Parcelable类型
import com.zyh.ipc.Book;
//接口
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
这儿需求留意俩点:
- 便是前面界说的自界说Parcelable类型,在AIDL文件中有必要手动导入。
- 在AIDL文件中,并不是一切的数据类型都能够运用的,AIDL文件支撑的数据类型如下:
- 根本数据类型,包括int、long、char、boolean等;
- String和CharSequence;
- List只支撑ArrayList,里面元素有必要能被AIDL支撑;
- Map只支撑HashMap,里面的key和value的类型也有必要被AIDL支撑;
- Parcelable,即完成了Parcelable接口的方针。
创立完AIDL文件以及AIDL用到的自界说Parcelable类型,这时就需求把这些文件从服务端APP复制一份到客户端APP中,留意类的包名千万不要改变,当一端的类结构发生改变,在另一端有必要替换为相同的。
服务端的完成
在界说完AIDL接口后,需求Build一下项目,这时在app/build/generated/aidl_source_out_dir/debug目录下会生成一个和AIDL接口同名的java文件,即IBookManager.java,关于这个文件咱们后边会仔细剖析。
这儿先来看一下服务端怎么完成,前面说了需求经过Service,直接界说一个服务BookManagerService:
class BookManagerService : Service() {
//服务端的书籍列表,这儿需求运用线程安全的类来保存
private val mBookList = CopyOnWriteArrayList<Book>()
//完成接口的Binder
private val mBinder = object : IBookManager.Stub(){
override fun getBookList(): MutableList<Book> {
return mBookList
}
override fun addBook(book: Book?) {
mBookList.add(book!!)
}
}
//onBind回调中回来咱们界说的Binder
override fun onBind(intent: Intent): IBinder {
return mBinder
}
//在服务创立时,往Book列表中增加俩本书
override fun onCreate() {
super.onCreate()
mBookList.add(Book(11,"Android"))
mBookList.add(Book(22,"Ios"))
}
}
这个类触及的知识点比较多,咱们来挨个剖析:
- 保存Book的列表为什么要运用CopyOnWriteArrayList这个线程安全的类型,这是因为getBookList()和addBook()这俩个办法会在线程池中运转,当有多个客户端绑定该服务,一起调用这俩个办法,运用线程安全的集和能够确保数据正确。
相似的集和还有ConcurrentHashMap线程安全的集和。
- 这个onBind办法回来的类型是IBinder,这儿的IBinder是一个接口,能够这么简略理解:一切经过Binder机制进行IPC的方针,都有必要完成该类。
这儿mBinder是IBookManager.Stud类的实例,这个类是AIDL文件生成的文件,该类界说如下:
public static abstract class Stub extends android.os.Binder implements com.zyh.ipc.IBookManager
能够发现它承继至Binder,这个Binder便是IBinder的完成类,一起完成了IBookManager接口,依据IBinder接口的特性,这儿咱们就能够以为这个mBinder就具有了跨进程的才能。
客户端的完成
前面已然说了方针完成了IBinder就有了跨进程的才能,那能够幻想在客户端应该便是拿到刚刚在服务端界说的Binder方针,然后对这个Binder进行操作,所以客户端代码如下:
//经过bindService衔接客户端APP的服务
findViewById<Button>(R.id.bindService).setOnClickListener {
bindService(
//这儿的Intent除了需求action,还有必要设置服务端APP的包名
Intent("com.zyh.ipc.BookManagerService").setPackage("com.zyh.ipc"),
conn,
BIND_AUTO_CREATE
)
}
//服务衔接成功和断开的回调
private val conn = object : ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i("ipc", "onServiceConnected: ")
//经过asInterface办法把IBinder方针转成IBookManager类型
bookManager = IBookManager.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.i("ipc", "onServiceDisconnected: ")
}
}
//检查书籍列表
findViewById<Button>(R.id.getBook).setOnClickListener {
val list = bookManager?.bookList
//看一下这个list的类型
Log.i("ipc", "onCreate: ${list?.javaClass?.name}")
for (book in list!!) {
Log.i("ipc", "onServiceConnected: ${book.BookId} ${book.BookName}")
}
}
这儿的中心代码便是IBinder方针的asInterface了,这儿能够看成就获取到了服务端的Binder了(真实原理咱们后边再说)。
咱们看一下打印:
2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onCreate: java.util.ArrayList
2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onServiceConnected: 11 Android
2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onServiceConnected: 22 Ios
这儿会发现list的类型是ArrayList,为什么说这个呢 原因是刚开端咱们说AIDL支撑List,可是只支撑ArrayList,可是咱们在服务端代码中却用了CopyOnWriteArrayList,这个类界说:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
会发现它尽管是名字和ArrayList相关,可是它却不是承继至ArrayList,这就阐明晰数据在传输进程中,Binder对数据类型做了处理。
这儿还有留意一点,便是客户端调用完getBookList办法后,会挂起当时线程,直到等候成果回来,是一个同步办法,所以当在服务端该办法履行时间过长,在主线程调用该办法可能会导致ANR。
这儿咱们能够做个小小总结:
- 服务端和客户端运用相同的AIDL文件,会生成相同的类,而生成类IBookManager.Stub是承继至Binder,且完成了IBookManager接口。
- 服务端的效果便是完成IBookManager.Stub接口,完成事务需求。
- 客户端经过拿到Binder方针,然后转成IBookManager类型,和服务端进行通讯。
运用回调
在前面咱们现已说了AIDL的简略运用了,现在咱们加个功用,便是在AIDL中运用回调。
这个功用在开发车机、电视等体系中经常用,比如导航APP在运转时,一般需求在Launcher显示出导航的简略信息,这个导航实时信息便是导航APP经过回调给的。
在本章比如中,咱们增加一个回调监听,希望服务端每隔一段时间能主动回调当时Book的信息。首要咱们界说接口,这个接口也有必要界说为aidl类型:
// IOnBookListener.aidl
package com.zyh.ipc;
import com.zyh.ipc.Book;
//回来一切Book
interface IOnBookListener {
void onAllBook(in List<Book> books);
}
这儿为什么要界说为AIDL接口,因为接口中的办法是需求跨进程调用的,办法回调的数据是需求序列化和反序列化的,所以一般的Java接口肯定满足不了;
界说完新接口后,需求在本来接口中增加注册和反注册的办法:
package com.zyh.ipc;
//有必要手动导入
import com.zyh.ipc.Book;
import com.zyh.ipc.IOnBookListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
//增加监听
void registerListener(IOnBookListener listener);
//移除监听
void unregisterListener(IOnBookListener listener);
}
修正完AIDL接口,需求Build一下项目,然后在服务端进行简略修正:
class BookManagerService : Service() {
private val mBookList = CopyOnWriteArrayList<Book>()
//listener列表
private val mListenerList = CopyOnWriteArrayList<IOnBookListener>()
private val mBinder = object : IBookManager.Stub(){
override fun getBookList(): MutableList<Book> {
return mBookList
}
override fun addBook(book: Book?) {
mBookList.add(book!!)
}
override fun registerListener(listener: IOnBookListener?) {
if (mListenerList.contains(listener)){
Log.i("BMS", "registerListener: already exist")
}else{
mListenerList.add(listener)
}
}
override fun unregisterListener(listener: IOnBookListener?) {
if (mListenerList.contains(listener)){
mListenerList.remove(listener)
}else{
Log.i("BMS", "unregisterListener: not exist")
}
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
override fun onCreate() {
super.onCreate()
mBookList.add(Book(11,"Android"))
mBookList.add(Book(22,"Ios"))
//开启线程循环回调book信息
Thread{
while (true){
Thread.sleep(5000)
for (listener in mListenerList){
Log.i("BMS", "onCreate: 回调信息")
listener.onAllBook(mBookList)
}
}
}.start()
}
}
上面代码十分简略,和保存Book相同也新建一个listener列表,然后在onCreate办法中,每隔5s回调一次Book信息,一起对Binder完成类增加了注册和反注册的功用。
写完服务端代码,有必要把修正触及的文件复制到客户端,这一步千万不能犯错,在客户端中相同先Build,然后在服务衔接成功后,创立监听的实例,给注册到服务端:
private val conn = object : ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i("ipc", "onServiceConnected: ")
bookManager = IBookManager.Stub.asInterface(service)
//这儿留意有必要是创立IOnBookListener.Stub的实例
bookManager?.registerListener(object : IOnBookListener.Stub(){
override fun onAllBook(books: MutableList<Book>?) {
for (book in books!!) {
Log.i("ipc", "onAllBook: ${book.BookId} ${book.BookName}")
}
}
})
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.i("ipc", "onServiceDisconnected: ")
}
}
这儿要留意的上面现已注释了,便是在跨进程中,咱们界说监听的接口有必要是aidl类型的,而非一般的Java类型,所以客户端在注册时有必要是传递生成类Stub的方针,至于原因后边会细说;这儿创立了IOnBookListener.Stub的实例,可是经过跨进程后,客户端实例和服务端实例将不是同一个,上面代码的运转如下:
能够发现能够正常完成功用,每5S回调一次服务的Book列表信息。
已然咱们能够注册listener,当然也需求测试一下反注册listener,可是在反注册listener中,咱们会发现如下打印:
服务端居然提醒找不到该listener,并且客户端仍是一直在回调,这不是大坑吗 这便是AIDL运用回调时需求留意的点。
咱们在客户端新建listener方针,经过Binder传递到服务端后,会生成一个全新的方针,这是因为IPC的本质是序列化和反序列化,方针是需求序列化后才能够传输,所以这个listener在客户端和服务端俩个进程就不是一个方针。
已然不是同一个方针,那回调功用咋是正常的呢,这个后边剖析AIDL生成文件时细说,这儿先处理怎么正常反注册。
处理的办法十分简略,运用RemoteCallbackList来替代前面的CopyOnWriteArrayList集和,从名字就能够看出这个是专门用来寄存长途回调的,咱们能够简略看一下该类的界说:
public class RemoteCallbackList<E extends IInterface>
能够发现它是一个泛型类,而泛型类型是IInterface,这个接口很重要,界说如下:
//Binder接口的基类,当咱们界说一个跨进程的接口时,有必要承继这个接口
public interface IInterface
{
//因为跨进程的真实原理是经过Binder,所以经过这个把接口和Binder方针联系起来。
//咱们把接口转成Binder要用这个,而不是直接强转。
public IBinder asBinder();
}
RemoteCallbackList原理十分简略,在它的内部有一个Map结构专门用来保存一切AIDL回调,这个Map的key是IBinder类型,value是Callback类型:
ArrayMap<IBinder, Callback> mCallbacks
= new ArrayMap<IBinder, Callback>();
而当咱们注册一个回调时,其间的key和value是如下办法获取:
IBinder binder = callback.asBinder();
Callback cb = new Callback(callback, cookie);
unregister(callback);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
到这儿咱们就明白了,尽管说跨进程传输中,在客户端新建的方针和服务端不是同一个,可是这俩个方针对应的Binder方针是同一个,所以当注册和反注册时能够针对对应的Binder来进行增删。
一起RemoteCallbackList还有一个很有用的功用,便是当客户端进程停止后,它能主动移除客户端注册的listener,并且RemoteCallbackList内部主动完成了线程同步功用,所以咱们运用它来注册和反注册时,不需求做额定的线程同步作业。
重连机制
为了程序的健壮性,当服务端进程意外停止时,咱们需求从头衔接服务。在这儿从头衔接服务,有俩种办法:
- 能够给在服务端运转的Binder设置DeathRecipient监听回调,当Binder逝世时,咱们会收到binderDied办法的回调,在binderDied办法中咱们能够重连长途服务。
代码如下:
private val deathRecipient = object : IBinder.DeathRecipient {
override fun binderDied() {
Log.i("ipc", "binderDied: 调用 需用重连Service")
}
}
界说完deathRecipient后,在bindService成功的回调中给binder增加该逝世监听即可:
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i("ipc", "onServiceConnected: ")
bookManager = IBookManager.Stub.asInterface(service)
//注册Binder逝世回调
service?.linkToDeath(deathRecipient, 0)
bookManager?.registerListener(listener)
}
- 经过bindService的回调,即ServiceConnection,能够在其间的onServiceDisconnected办法中进行重连机制,代码如下:
override fun onServiceDisconnected(name: ComponentName?) {
Log.i("ipc", "onServiceDisconnected: ")
//需求进行重连
}
到这儿,AIDL的运用咱们根本就说完了。其实AIDL文件是一套java代码生成规矩,能够看成是给编译器来运用的一套规矩,经过AIDL文件,编译器生成契合跨进程通讯的java代码。 这样的好处便是不用让开发者编写杂乱的跨进程代码。
AIDL生成文件剖析
会运用AIDL仍是不行的,有必要要能看得懂AIDL文件所生成的java文件,因为在许多源码中,都是直接运用java代码来编码的。
前面说了当IPC时咱们需求界说.aidl接口,这个接口便是IPC的内容,当界说了一个IBookManager.aidl接口,这个接口肯定不能直接起效果,它会生成一个IBookManager.java,代码如下:
package com.zyh.ipc;
//IBookManager是一个接口,承继至IInterface
public interface IBookManager extends android.os.IInterface
{
//对IBookManager接口的默许完成
public static class Default implements com.zyh.ipc.IBookManager
{
@Override public java.util.List<com.zyh.ipc.Book> getBookList() throws android.os.RemoteException
{
return null;
}
@Override public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
//IPC通讯完成的存根类,这儿完成了IBookManager接口,一起承继了Binder
public static abstract class Stub extends android.os.Binder implements com.zyh.ipc.IBookManager
{
//仅有描述符
private static final java.lang.String DESCRIPTOR = "com.zyh.ipc.IBookManager";
//结构函数,相关接口
public Stub()
{
//Binder的办法,将特定的接口与Binder相关。当调用完该办法后,queryLocalInterface办法将会依据仅有描述符回来特定的接口
this.attachInterface(this, DESCRIPTOR);
}
//把一个IBinder方针转化成IBookManager方针,如果是跨进程通讯则需求生成一个署理类
public static com.zyh.ipc.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
//经过仅有描述符,找到跨进程的接口方针
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//如果跨进程的类型是IBookManager,阐明是没有跨进程的
if (((iin!=null)&&(iin instanceof com.zyh.ipc.IBookManager))) {
return ((com.zyh.ipc.IBookManager)iin);
}
//不然回来Stub.Proxy类型方针
return new com.zyh.ipc.IBookManager.Stub.Proxy(obj);
}
//这个是IInterface接口的办法,即需求把该接口强转为Binder方针
@Override public android.os.IBinder asBinder()
{
return this;
}
//交流的中心代码,回来值回来true则表明调用成功。
//code表明仅有标识符,即该调用哪个办法
//data表明客户端发送给服务端的数据
//reply表明服务端回来的数据
@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;
}
//获取book列表
case TRANSACTION_getBookList:
{
//验证当时接口是否匹配
data.enforceInterface(descriptor);
//这个this表明IBookManager接口的办法
java.util.List<com.zyh.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
//验证当时接口是否匹配
data.enforceInterface(descriptor);
com.zyh.ipc.Book _arg0;
if ((0!=data.readInt())) {
//data是Parcel数据,这儿依据CREATOR创立出Book方针
_arg0 = com.zyh.ipc.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
//相同是调用IBookManager接口的办法
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
//IBookManager接口的署理类,当真进行IPC通讯时,调用该类
private static class Proxy implements com.zyh.ipc.IBookManager
{
private android.os.IBinder mRemote;
//长途的IBinder方针
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
//IInterface接口办法
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List<com.zyh.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.zyh.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//调用刚刚界说的transact办法,会回调Stub类中的onTransact办法
boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
//当调用不成功且默许完成不为空时,即是非IPC通讯
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getBookList();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.zyh.ipc.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.zyh.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);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addBook(book);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.zyh.ipc.IBookManager sDefaultImpl;
}
//静态变量,每个办法对应一个静态变量
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 static boolean setDefaultImpl(com.zyh.ipc.IBookManager impl) {
//当客户端和服务端在同一个进程中会被调用
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.zyh.ipc.IBookManager getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
//java文件的接口
public java.util.List<com.zyh.ipc.Book> getBookList() throws android.os.RemoteException;
//java文件的接口
public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException;
}
上面生成的代码仍是蛮多的,看起来有点乱的原因是界说了太多内部类,首要便是IBookManager.java这个类,它承继了IInterface接口,一起它自己也是一个接口,一切能够在Binder中传输的接口都有必要承继至IInterface接口。
一起在IBookManager.java中界说了俩个办法,这俩个办法便是咱们在aidl中界说的办法,是IPC希望完成的功用接口。一起界说了俩个静态整形的id分别用来标识这俩个办法,这个俩个id用于标识在transact进程中客户端所恳求的到底是哪个办法。
然后是IBookManager.java中的第一个内部类:Default,该类是默许完成,能够无需重视。
IBookManager.java中第二个内部类:Stub,其间Stub的意思是”存根”,表明一些接口的完成类。该类是中心,它首要是承继至Binder,一起完成了IBookManager接口,下面具体罗列一下该类中的完成细节:
- DESCRIPTOR,这个是Binder的仅有表明,一般用当时Binder的类名表明。
- asInterface,用于将服务端的Binder方针转化成客户端所需的AIDL接口类型的方针,这种转化是区别进程的,如果客户端和服务端在同一个进程,那么此办法回来的便是服务端的Stub方针自身,不然回来的是封装后的Stub.proxy方针。
- asBinder,用于回来当时Binder方针。
- onTransact,该办法会运转在服务端的Binder线程池中,当客户端建议跨进程恳求时,长途恳求会经过体系底层封装后交由该办法来处理。
该办法的参数十分重要,服务端经过code能够确定客户端所恳求的方针办法是什么,然后从data中取出方针办法所需求的参数,然后履行方针办法,当方针办法履行结束后,会向reply中写入回来值。这儿留意,如果此办法回来false,那么客户端的恳求会失利,因此咱们能够利用这个特性来做权限验证,究竟咱们不希望随便一个进程都能长途调用服务。
- Proxy类,该类是Stub的内部类,当客户端和服务端不在一个线程时,asInterface就会创立Proxy类的方针,然后结构函数传入的IBinder方针仍是服务端的Binder方针。
- Proxy#getBookList,这个办法运转在客户端中,当客户端调用此办法时,它的内部完成是这样的:首要创立该办法所需求的输入型Parcel方针_data、输出型Parcel方针_reply和回来值方针List,然后把该办法的参数信息写入_data中,接着调用transact办法来建议RPC恳求,一起当时线程挂起;然后服务端的onTransact办法会被调用,知道RPC进程回来后,当时线程继续履行,并且从_reply中取出RPC进程回来的成果。
上面便是AIDL的生成文件的简略剖析。
手写一个Binder运用实例
在前面咱们也说了.aidl文件只是生成Java代码的规矩,并且这儿咱们也剖析了AIDL生成的文件,所以咱们就能够不用AIDL来手动创立Binder方针,这样相同能够完成IPC通讯。
- 先声明一个跨进程的接口,该接口只需求承继IInterface接口即可,IInterface有一个asBinder办法,这个接口界说如下:
public interface ICustomBookManager extends IInterface {
static final java.lang.String DESCRIPTOR = "com.zyh.ipc.IBookManager";
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.zyh.ipc.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException;
}
这儿界说了ICustomBookManager的Java接口,并且界说了俩个办法以及办法的仅有表明符号。
- 然后是新建完成该接口的Java类,一起承继至Binder:
//承继至Binder,一起完成ICustomBookManager接口
public class CustomBookManagerImpl extends Binder implements ICustomBookManager {
public CustomBookManagerImpl() {
this.attachInterface(this, DESCRIPTOR);
}
//有必要的办法,把IBinder方针转成接口类型
public static ICustomBookManager asInterface(IBinder obj){
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.zyh.ipc.CustomBookManagerImpl))) {
return ((com.zyh.ipc.CustomBookManagerImpl)iin);
}
return new com.zyh.ipc.Proxy(obj);
}
//模板代码,在服务端被调用
@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.zyh.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(descriptor);
com.zyh.ipc.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.zyh.ipc.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
@Override
public List<Book> getBookList() throws RemoteException {
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
}
//回来Binder
@Override
public IBinder asBinder() {
return this;
}
}
- 在上面代码咱们知道当是真的IPC时,是需求一个署理类的,由署理类来进行数据转化和接口恳求:
public class Proxy implements ICustomBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override
public List<Book> getBookList() throws RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.zyh.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.zyh.ipc.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(Book book) throws 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);
}
boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
能够发现上面代码和生成的代码简直相同,只不过可读性多了许多,里面关键的当地能够归纳如下几点:
- 跨进程的接口有必要要承继IInterface接口。
- 接口的Java完成类有必要承继至Binder。
- asInterface必不可少,能够把Binder方针转成接口类型,这儿因为要区别是否是跨进程,如果是跨进程需求调用Proxy署理类来辅佐。
- onTransact是调用transact后回调,是服务端处理恳求,在线程池中履行。
- Proxy署理类便是用来调用接口,以及办法相关参数的序列化与反序列化进程。
总结
不知不觉本篇文章的子树现已十分多了,内容较多,下面做个总结:
- 在AIDL运用篇,主要便是AIDL文件的写法要特别留意,以及运用回调和重连机制的运用。
- 在AIDL生成文件剖析中,咱们要清晰看出其不同内部类的效果。
- 在手写Binder通讯时,咱们需求知道IInterface的意义,以及完成接口和承继至Binder的Java类。
- 其间要会依据是否跨进程来选择是否运用署理类,其间onTransact是在服务端线程池中调用,而Proxy中的办法是同步履行,会挂起。
笔者水平有限,如有问题,欢迎我们谈论指正。