IPC之AIDL那些事儿

IPC之AIDL那些事儿 目录IPCAIDL如何使用服务端实现AIDL接口创建创建service客户端实现AIDL过程分析defaultStubDESCRIPTORasInterfaceonTransact(code,data,reply,flags)Stub.ProxymRemote定义的接口下集预告IPCIPC是Inter-Process Communication的缩写,含义就是进程通信或者跨进程通讯.在这里我们需要理解什么是进程?什么是线程?通常情况下一个app就是一个进程,一个进程可以有多个线程,因此进程和线程是包含与被包含的关系.从android的角度分析就是,一个app就是一个进程,一个进程有一个主线程,就是我们的UI线程,但是如果要执行很多耗时操作的时候,就需要开启子线程去执行任务,否则就会出现ANR.这就解释了为什么会有多线程.那么为什么会有多进程?比如一个应用因为某些原因自身需要多进程模式,至于原因可以有很多;或者是当前app需要获取其他app的数据,这就是两个进程之间的通讯,系统为我们提供了ContentProvider,所以可以证实ContentProvider是支持跨进程的;或者是一个应用为了获取更多的内存通过多进程来获取更多的内存空间,因为系统对每个app的运行内存都是做了限制的.那么跨进程方式有很多Bundle,文件共享,ContentProvider,Messenger,AIDL,socket等都可以来实现跨进程通讯,这里我们之去深究AIDL,因为大部分的跨进程最终都是通过Binder去实现.AIDL为什么要叫AIDL,因为在使用该模式的时候,需要创建aidl后缀的文件.我只能想到这理由,再结合bindService去实现.如何使用使用AIDL要分为两个流程:服务端给予AIDL声明的接口,去实现业务逻辑;客户端通过BindService的方式讲返回的binder对象转化为AIDL接口类型去调用.服务端实现服务端也分为两个步骤:1.编写aidl接口,若是使用到实体类需要特别注意,下边会有体现;2.创建service内部实现aidl接口的逻辑.AIDL接口创建咱们先来看看创建后的目录:首先来看看所需要的类,以及对应的创建目的.BookBean.aidl 表示在AIDL声明引用BookBean对象.IBookControl.aidl 表示业务逻辑的接口BookBean.java 表示自定义的实体类,必须实现Parcelable1.我们先来看看创建的实体类BookBean,必须是Parclable类型//BookBean.java public class BookBean implements Parcelable { String name; int price; protected BookBean(Parcel in) { name in.readString(); price in.readInt(); } public static final CreatorBookBean CREATOR new CreatorBookBean() { Override public BookBean createFromParcel(Parcel in) { return new BookBean(in); } Override public BookBean[] newArray(int size) { return new BookBean[size]; } }; Override public int describeContents() { return 0; } Override public void writeToParcel(Parcel parcel, int i) { parcel.writeString(name); parcel.writeInt(price); } }2.创建了BookBean.java,但是AIDL在使用自定义实体类时,需要去引用声明,也就是去创建一个同名同目录的aidl文件,这里就已BookBean.aidl为例// BookBean.aidl package com.example.csdnnote; parcelable BookBean;我们可以看到parcelable BookBean;就表示引用具体的实体类.3.创建业务逻辑的接口IBookControl.aidl// IBookControl.aidl package com.example.csdnnote; import com.example.csdnnote.BookBean; interface IBookControl { ListBookBean getBookList(); void addBook(in BookBean book); }里面有两个接口,一个是获取所有书的getBookList,一个是增加书本的addBook接口.有一行代码很关键:import com.example.csdnnote.BookBean;,我们需要显式的将我们想要使用的Parcelable对象引用进来.这里其实引用的是BookBean.aidl,但是BookBean.aidl内部又引用这BookBean.java.从而实现引用BookBean.java对象.咱们来看看addBook方法,形参里多了一个in,那么in表示什么意思?in其实是AIDL的一个对数据定向标记的语法,一共有三个in out inout.in:服务端会接收到客户端传来的完整的数据,并且服务端对数据进行修改也不影响到客户端的数据out:服务度接收到客户端传来的数据,但该数据不完整,该对象不为空,但是里面的字段为空,可以理解为传了一个刚刚new的对象,并且服务端对数据进行修改,会影响到客户端那头的数据.inout:服务端会接收到客户端传来的完整的数据,并且服务端对数据进行修改会影响到客户端的数据.总结aidl中需要使用Parcelable对象或者是aidl对象,都需要显式import去引入.若要使用Parcelable对象,需要创建一个同名同目录的.aidl文件,内部还要去引用.in out inout的使用.创建service既然接口都已经写好,我们可以去实现业务逻辑public class AidlService extends Service { ListBookBean list new ArrayList(); private IBinder iBookControl new com.example.csdnnote.IBookControl.Stub() { Override public ListBookBean getBookList() throws RemoteException { return list; } Override public void addBook(BookBean book) throws RemoteException { if (!list.contains(book)) list.add(book); } }; Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return iBookControl; } }我们可以看到,我们通过onBind方法去返回一个实现aidl接口的IBinder对象.为了证明是跨进程,我们需要在androidmanfest.xml文件里,去修改该service的进程.service android:name.AidlService android:process:aidldemo/service那么服务端的工作完成了,接下来就是看客服端.客户端实现public class MainActivity extends AppCompatActivity { TextView textView; IBookControl iBookControl; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView findViewById(R.id.tv_msg); findViewById(R.id.btn_get).setOnClickListener(new View.OnClickListener() { Override public void onClick(View view) { ListBookBean list null; try { list iBookControl.getBookList(); } catch (RemoteException e) { e.printStackTrace(); } textView.setText(list.toString()); } }); findViewById(R.id.btn_add).setOnClickListener(new View.OnClickListener() { Override public void onClick(View view) { try { iBookControl.addBook(new BookBean(name iBookControl.getBookList().size() 1, iBookControl.getBookList().size() 1)); } catch (RemoteException e) { e.printStackTrace(); } } }); Intent intent new Intent(this, AidlService.class); bindService(intent, connection, BIND_AUTO_CREATE); } ServiceConnection connection new ServiceConnection() { Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { iBookControl IBookControl.Stub.asInterface(iBinder); } Override public void onServiceDisconnected(ComponentName componentName) { } }; Override protected void onDestroy() { super.onDestroy(); unbindService(connection); } }客户端其实就是通过bindService的方式,去获取绑定成功的BInder对象,在转化成AIDL接口去实现业务逻辑.整一个AIDL过程其实很清晰.第一步:编写AIDL接口第二步:在Service里,返回AIDL接口内部的IBinder对象,并返回.第三步:通过bindService的方式,去获取到返回的BInder对象,转化成AIDL接口.这里出现了几个新词语 IBinder对象,Binder对象,看似乎AIDL的核心和他有关系,那么接下来我们来好好看看AIDL的核心过程.AIDL过程分析每一个aidl接口,编译后系统都是会给我们生成一个对应的java文件,如IBookControl.aidl会生成一个IBookControl.java文件,咱们先来看看代码的结构我们可以看到IBookControl接口内部定义了两个类,一个是Default和Stub,还有两个方法.两个方法就是我们外部定义的方法.default咱们先来看看内部的Default类/** Default implementation for IBookControl. */ public static class Default implements com.example.csdnnote.IBookControl { Override public java.util.Listcom.example.csdnnote.BookBean getBookList() throws android.os.RemoteException { return null; } Override public void addBook(com.example.csdnnote.BookBean book) throws android.os.RemoteException { } Override public android.os.IBinder asBinder() { return null; } }该类就表示一个方法的默认实现,若服务端没有处理,那么就由他去默认实现,Default不是重点,重点在于Stub的内部类.Stub现附上一个源代码,咱们抓重点一个个讲.public static abstract class Stub extends android.os.Binder implements com.example.csdnnote.IBookControl { private static final java.lang.String DESCRIPTOR com.example.csdnnote.IBookControl; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.csdnnote.IBookControl interface, * generating a proxy if needed. */ public static com.example.csdnnote.IBookControl asInterface(android.os.IBinder obj) { if ((objnull)) { return null; } android.os.IInterface iin obj.queryLocalInterface(DESCRIPTOR); if (((iin!null)(iin instanceof com.example.csdnnote.IBookControl))) { return ((com.example.csdnnote.IBookControl)iin); } return new com.example.csdnnote.IBookControl.Stub.Proxy(obj); } Override public android.os.IBinder asBinder() { return this; } 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.Listcom.example.csdnnote.BookBean _result this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(descriptor); com.example.csdnnote.BookBean _arg0; if ((0!data.readInt())) { _arg0 com.example.csdnnote.BookBean.CREATOR.createFromParcel(data); } else { _arg0 null; } this.addBook(_arg0); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } } 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.example.csdnnote.IBookControl impl) { if (Stub.Proxy.sDefaultImpl null impl ! null) { Stub.Proxy.sDefaultImpl impl; return true; } return false; } public static com.example.csdnnote.IBookControl getDefaultImpl() { return Stub.Proxy.sDefaultImpl; } private static class Proxy implements com.example.csdnnote.IBookControl {...} }DESCRIPTOR该常量表示唯一标识.asInterface该方法其实用于将服务端的IBinder对象转化成客户端所需的AIDL接口类型的对象.从逻辑上可以看出,先判断IBinder对象是否为空,再判断IBinder的对象是否处于一个进程中,若是则返回服务端的stub本身,若不是则返回Stub.proxy.所以可以断定Stub是运行在服务端中的,proxy是运行在客户端中的.onTransact(code,data,reply,flags)该方法是运行在服务端的,当客户端发起请求跨进程请求的时候,远程请求通过系统底层封装后交由此方法处理,通过code来区分所要调用的方法,data则是客户端传入的数据,通过reply可以写会返回的数据,flage则表示是否有返回值0表示双向,1表单向.注意:该方法的返回值表示是否调用成功,true表示成功,false表示失败,所以我们在这里可以做一些权限判断来决定调用是否成功.Stub.Proxyproxy该内部类其实是运行在客户端中的,就拿IBookControl为例,proxy内部实现了我们定义的addBook和getBookList接口,咱们来看看proxy内部做了什么mRemote该IBinder表示远程IBinder,也就是服务端中的IBinder定义的接口其实addBook和getBookList一样,我们就讲解一个Override public void addBook(com.example.csdnnote.BookBean 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(); } }我们可以看到一行客户端与服务端交互的代码boolean _status mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);通过服务端的IBinder对象,去调用transact方法,那么该方法就会调用到服务端也就是Stub的onTransact,咱们可以看看传入的参数,都是一一对应的,并且最后的返回值_status,若返回false,则抛出异常,true调用成功.经过分析,大致对AIDL的过程也就是Binder的过程有了初步的了解.不过还需要额外的说明:当客户端发起远程请求时,当前线程是会被挂起直至返回数据,所以远程操作是一个耗时操作;服务端的BInder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式.为了更好的理解流程,咱们通过一张图来总结一下.下集预告详细讲解Handler机制