携手创造,共同生长!这是我参与「日新计划 8 月更文应战」的第4天,点击检查活动详情

前言

前面文章咱们重点剖析了Linux的进程划分,以及为了减少一次复制的内存映射技术,本篇文章咱们就来介绍Android中运用最多也最具有代表性的IPC办法:AIDL。

本篇文章主要侧重AIDL的运用细节,然后重点剖析AIDL生成的文件,经过手写AIDL生成文件来大致理解Binder机制,话不多说,直接开整。

正文

咱们先来看看AIDL的运用。

AIDL运用

AIDL的原理是经过Binder来完成,并且是C/S架构,所以AIDL分为服务端和客户端2个方面来运用:

  1. 服务端:服务端首要需求创立一个Service用来监听客户端的衔接恳求,然后创立一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最终在Service中完成这个AIDL接口。

从这个简略描述咱们知道,作为服务端有必要建立在Service上,这也十分好理解,Service作为四大组件之一,由Android体系统一办理,一起自己就能够被其他进程所发动;

  1. 客户端:客户端首要就需求绑定服务端的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:

Android IPC | 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);
}

这儿需求留意俩点:

  1. 便是前面界说的自界说Parcelable类型,在AIDL文件中有必要手动导入
  2. 在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"))
    }
}

这个类触及的知识点比较多,咱们来挨个剖析:

  1. 保存Book的列表为什么要运用CopyOnWriteArrayList这个线程安全的类型,这是因为getBookList()和addBook()这俩个办法会在线程池中运转,当有多个客户端绑定该服务,一起调用这俩个办法,运用线程安全的集和能够确保数据正确。

相似的集和还有ConcurrentHashMap线程安全的集和。

  1. 这个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。

这儿咱们能够做个小小总结:

  1. 服务端和客户端运用相同的AIDL文件,会生成相同的类,而生成类IBookManager.Stub是承继至Binder,且完成了IBookManager接口。
  2. 服务端的效果便是完成IBookManager.Stub接口,完成事务需求。
  3. 客户端经过拿到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的实例,可是经过跨进程后,客户端实例和服务端实例将不是同一个,上面代码的运转如下:

Android IPC | AIDL详解

能够发现能够正常完成功用,每5S回调一次服务的Book列表信息。

已然咱们能够注册listener,当然也需求测试一下反注册listener,可是在反注册listener中,咱们会发现如下打印:

Android IPC | AIDL详解

服务端居然提醒找不到该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内部主动完成了线程同步功用,所以咱们运用它来注册和反注册时,不需求做额定的线程同步作业。

重连机制

为了程序的健壮性,当服务端进程意外停止时,咱们需求从头衔接服务。在这儿从头衔接服务,有俩种办法:

  1. 能够给在服务端运转的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)
}
  1. 经过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接口,下面具体罗列一下该类中的完成细节:

  1. DESCRIPTOR,这个是Binder的仅有表明,一般用当时Binder的类名表明。
  2. asInterface,用于将服务端的Binder方针转化成客户端所需的AIDL接口类型的方针,这种转化是区别进程的,如果客户端和服务端在同一个进程,那么此办法回来的便是服务端的Stub方针自身,不然回来的是封装后的Stub.proxy方针。
  3. asBinder,用于回来当时Binder方针。
  4. onTransact,该办法会运转在服务端的Binder线程池中,当客户端建议跨进程恳求时,长途恳求会经过体系底层封装后交由该办法来处理。

该办法的参数十分重要,服务端经过code能够确定客户端所恳求的方针办法是什么,然后从data中取出方针办法所需求的参数,然后履行方针办法,当方针办法履行结束后,会向reply中写入回来值。这儿留意,如果此办法回来false,那么客户端的恳求会失利,因此咱们能够利用这个特性来做权限验证,究竟咱们不希望随便一个进程都能长途调用服务。

  1. Proxy类,该类是Stub的内部类,当客户端和服务端不在一个线程时,asInterface就会创立Proxy类的方针,然后结构函数传入的IBinder方针仍是服务端的Binder方针。
  2. Proxy#getBookList,这个办法运转在客户端中,当客户端调用此办法时,它的内部完成是这样的:首要创立该办法所需求的输入型Parcel方针_data、输出型Parcel方针_reply和回来值方针List,然后把该办法的参数信息写入_data中,接着调用transact办法来建议RPC恳求,一起当时线程挂起;然后服务端的onTransact办法会被调用,知道RPC进程回来后,当时线程继续履行,并且从_reply中取出RPC进程回来的成果。

上面便是AIDL的生成文件的简略剖析。

手写一个Binder运用实例

在前面咱们也说了.aidl文件只是生成Java代码的规矩,并且这儿咱们也剖析了AIDL生成的文件,所以咱们就能够不用AIDL来手动创立Binder方针,这样相同能够完成IPC通讯。

  1. 先声明一个跨进程的接口,该接口只需求承继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接口,并且界说了俩个办法以及办法的仅有表明符号。

  1. 然后是新建完成该接口的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;
    }
}
  1. 在上面代码咱们知道当是真的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;
    }
}

能够发现上面代码和生成的代码简直相同,只不过可读性多了许多,里面关键的当地能够归纳如下几点:

  1. 跨进程的接口有必要要承继IInterface接口
  2. 接口的Java完成类有必要承继至Binder
  3. asInterface必不可少,能够把Binder方针转成接口类型,这儿因为要区别是否是跨进程,如果是跨进程需求调用Proxy署理类来辅佐。
  4. onTransact是调用transact后回调,是服务端处理恳求,在线程池中履行
  5. Proxy署理类便是用来调用接口,以及办法相关参数的序列化与反序列化进程

总结

不知不觉本篇文章的子树现已十分多了,内容较多,下面做个总结:

  1. 在AIDL运用篇,主要便是AIDL文件的写法要特别留意,以及运用回调和重连机制的运用。
  2. 在AIDL生成文件剖析中,咱们要清晰看出其不同内部类的效果。
  3. 在手写Binder通讯时,咱们需求知道IInterface的意义,以及完成接口和承继至Binder的Java类。
  4. 其间要会依据是否跨进程来选择是否运用署理类,其间onTransact是在服务端线程池中调用,而Proxy中的办法是同步履行,会挂起。

笔者水平有限,如有问题,欢迎我们谈论指正。