目录
前言
什么是AIDL?
为什么要设计出这样一种语言?它能帮助我们干什么?
还有其他方法能实现跨进程通信吗?相较于别的方法AIDL有什么优势呢?
AIDL的相关语法
Java与AIDL的不同之处
AIDL默认支持的数据类型:
定向tag
AIDL的分类
AIDL我们该如何使用呢?
1.如何编写AIDL文件?
创建AIDL文件
AIDL文件的内容
AIDL的编译产物
2.如何使用AIDL
移植相关文件
编写服务端代码
编写客户端代码
AIDL文件是怎么工作的?
结合客户端和服务端的代码分析AIDL文件
客户端
服务端
前言
为什么要写这一篇笔记?
距离上一次写笔记,好像过去了一年多的时间,这一年多的时间是繁忙的,忙着加班加班加班,牛马大概就是这样,但也接触了很多新的东西,也成长了很多,这是学生时代体验不到的另一种生活,忙碌但也充实。回归正题,为什么要写这一篇笔记呢?因为我太多纸质的笔记了,但每一个笔记本最后都沦为和草稿纸共用的下场,导致当时对一些知识点理解的笔记早已不翼而飞,所以想了想还是整理成博客吧,CSDN才应该是我笔记的最终归属地555...
好了,接下来言归正传。
什么是AIDL?
AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。
所以简而言之,AIDL就是一种语言。
为什么要设计出这样一种语言?它能帮助我们干什么?
设计这门语言的初衷是为了实现跨进程通信(IPC)。
(进程是一个独立的资源分配单元,不同进程之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源,但不同的进程需要进行信息的交互和状态的传递等,所以需要有一个方法来实现进程之间的通信)
设计的目的google官方有明确说明: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service,这就说明是为多线程,多客户端并发访问设计的,
AIDL可以帮助我们在一个进程上访问另一个进程中的数据,甚至调用一些特定的方法。
还有其他方法能实现跨进程通信吗?相较于别的方法AIDL有什么优势呢?
Messenger: Messenger的核心就是Message和Handler来进行线程间通信,因为Messenger进行跨进程通信时请求队列是同步进行的(Messenger,service收到的请求是放在Handler的MessageQueue里面,Handler大家都用过,它需要绑定一个Thread,然后不断poll message执行相关操作,这个过程是同步执行的),无法并发执行,但是使用AIDL的时候,service端每收到一个client端的请求时,就会启动一个线程(非主线程)去执行相应的操作。所以在要求多进程的情况下Messenger是不适用的。
BroadcastReceiver:BroadcastReceiver它占用的系统资源比较多,如果进程间频繁调动,使用AIDL显然更可取。
ContentProvider : ContentProvider只是将自己的数据库暴露出去,别的应用app可以获取到数据,但是获取的这个数据并不是实时的,所以用来跨进程通信并不可取。
Socket : 它分为两种TCP协议和UDP协议,实际编码过程中一般不会用到
TCP(Transmission Control Protocol)是传输控制协议,它的连接需要三次握手,断开连接需要四次挥手。它具有建立连接,数据无限制,速度慢,但是可靠的特点。他提供超时重传机制,所以很稳定。
UDP(User Datagram Protocol)是用户数据报协议,它具有不建立连接通道,数据有限制,不可靠,速度快等特点。虽然UDP的效率更高,但是数据的传输是不可靠的,不能保证数据能够正确的传输。
文件共享: 文件共享是将对象序列化后保存到文件中,再通过反序列化读取文件中的对象,此方法对文件的格式没有具体要求,可以是xml、json、txt等。常见的有使用ObjectInputStream和ObjectOutputStream存取对象。但是因为文件共享存在并发读写的问题,所以有很大的局限性,比如读取的数据不完整或者读取的数据并不是最新的,文件共享适合在对数据同步要求不高的进程间通信,并且要妥善处理并发读/写的问题。
现在让我们正式开始了解AIDL吧~~~
AIDL的相关语法
从上面我们知道,AIDL是一门语法,并且和Java的语法相似,其下主要是与Java语法的不同之处
Java与AIDL的不同之处
1. 文件类型:AIDL的文件后缀是.aidl , java的文件后缀是.java
2. 导包:AIDL除了默认支持的数据类型不需要导包以外,其他数据类型都是需要导包的,哪怕在同一个包下,但是Java在同一个包下时就不需要导包
eg: 在com.carrie.demo包下有三个文件Cat.java, AnimalManager.aidl, AnimalManager.java ,如果在需要在AnimalManager.aidl文件中使用Cat对象,那么,必须通过import com.carrie.demo.Cat导入Cat对象的包,但是因为AnimalManager.java和Cat.java在同一目录下,所以不需要导包
AIDL默认支持的数据类型:
1. Java的八种数据类型:boolean、byte、short、int、long、float、double、char
2. String类型
3. CharSequence类型
4. List类型:List类型可以使用泛型,但是List中的所有元素的数据类型必须是aidl默认支持的数据类型,或者是其他AIDL生成的接口,或者是定义的parcelable
5. Map类型:Map类型不可以使用泛型,但是Map中的所有元素的数据类型必须是aidl默认支持的数据类型,或者是其他aidl生成的接口,或者是定义的parcelable
定向tag
Android官方文档对定向tag是这么介绍的
All non-primitive parameters require a directional tag indicating which way the data goes . Either in , out , or inout . Primitives are in by default , and connot be otherwise .
所有的非基本参数都需要一个定向tag来定义数据的流通方式,例如in、out、intout.并且基本参数的定向tag默认并且只能是in
in表示数据从客户端流向服务端,
out表示数据从服务端流向客户端
inout表示数据在客户端和服务端之间相互流动
定向tag表示了在跨进程通信中的数据流向。其中数据流向是针对客户端传入方法中的对象而言的,tag为in表示服务端会收到那个对象的完整数据,但是客户端的那个对象不会因为服务端对传入参数的修改而发生改变;tag为out表示服务端会收到那个对象的空对象,服务端对空对象的任何修改客户端都会进行同步修改;tag为inout表示服务端会收到那个对象的完整数据,并且客户端会同步服务端对该对象的修改。
另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。但注意不要全部都使用inout,而是需要根据需要来进行选择。如果全部使用inout系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。
AIDL的分类
AIDL主要分为两大类:一种是为了定义parcelable对象,以供其他AIDL文件使用AIDL文件中的非默认支持的数据类型;一种是定义接口来方便系统实现跨进程通信。
//Animal.aidlpackage com.carrie.demo//这个文件的作用是声明一个序列化对象,以供其他aidl文件使用
parcelable animal;
//AnimalManager.aidlpackage com.carrie.demo
//因为声明的Animal对象不是aidl默认支持的数据类型,所以需要导入
import com.carrie.demo.Animal;interface AnimalManager{//方法中的参数除了Java的八种基本类型,string类型,CharSequence类型,其他类型都需要在前面加上定向tag,具体加什么tag依情况而定void setAnimalName(string name,in Animal animal);void setAnimalCount(in Animal animal,int count);//在返回值前不需要加任何tag,无论什么数据类型Animal getAnimal();String getAnimalName();List<Animal> getAnimalList();
如果在AIDL定义的方法中不包含不默认支持的数据类型,那么只需要编写一个AIDL文件,如果AIDL定义的方法中包含N个不默认支持的数据类型,那么需要编写N+1个AIDL文件。
AIDL我们该如何使用呢?
1.如何编写AIDL文件?
创建AIDL文件
Android Studio本身支持创建AIDL文件,先创建名为IMyAidlInterface的AIDL文件。
在Module上单击右键新建AIDL file:
由于是第一次创建AIDL文件,AS还帮我们创建了aidl包作为目录结构
AIDL文件的内容
我们打开刚创建的IMyAidlInterface.aidl文件,内容如下:
// IMyAidlInterface.aidl
package com.example.aidl;// Declare any non-default types here with import statementsinterface IMyAidlInterface {/*** 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);
}
其中basicTypes(xx)方法是自动生成的,用来指导我们如何编写方法,可以去掉。
IMyAidlInterface 接口里声明的方法为Server端暴露给外部调用的方法,先为Server添加方法:
// IMyAidlInterface.aidl
package com.example.aidl;// Declare any non-default types here with import statementsinterface IMyAidlInterface {//所有的返回值前都不需要加任何东西,不管是什么数据类型List<Book> getBooks();//传参时除了Java基本类型以及String,CharSequence之外的类型//都需要在前面加上定向tag,具体加什么量需而定void addBook(in Book book);
}
切换到Project模式,点击编译:发现Book类没有定义
所以再定义一个Book类
// Book.aidl
package com.example.aidl;//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用parcelable Book;
package com.example.aidl;import android.os.Parcel;
import android.os.Parcelable;public class Book implements Parcelable{public String getName() {return name;}public void setName(String name) {this.name = name;}public int getPrice() {return price;}public void setPrice(int price) {this.price = price;}private String name;private int price;public Book(){}public Book(Parcel in) {name = in.readString();price = in.readInt();}public static final Creator<Book> CREATOR = new Creator<Book>() {@Overridepublic Book createFromParcel(Parcel in) {return new Book(in);}@Overridepublic Book[] newArray(int size) {return new Book[size];}};@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(name);dest.writeInt(price);}/*** 参数是一个Parcel,用它来存储与传输数据* @param dest*/public void readFromParcel(Parcel dest) {//注意,此处的读值顺序应当是和writeToParcel()方法中一致的name = dest.readString();price = dest.readInt();}//方便打印数据@Overridepublic String toString() {return "name : " + name + " , price : " + price;}
}
AIDL的编译产物
在我们实际编写客户端和服务端代码的过程中,真正协助我们工作的其实是这个生成的.java文件,而 .aidl 文件从头到尾都没有出现过。
这里我们暂时不分析这个文件,具体分析内容见后面段落-----“AIDL文件是怎样工作的?”
2.如何使用AIDL
基本的操作流程就是:在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。
移植相关文件
服务端和客户端是两个不同的进程,在客户端和服务端中都有我们需要用到的 .aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。
编写服务端代码
package com.example.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;import androidx.annotation.Nullable;import java.util.ArrayList;
import java.util.List;/*** 服务端的AIDLService.java*/
public class AIDLService extends Service {public final String TAG = this.getClass().getSimpleName();//包含Book对象的listprivate List<Book> mBooks = new ArrayList<>();//由AIDL文件生成的IMyAidlInterfaceprivate final IMyAidlInterface.Stub mBookManager = new IMyAidlInterface.Stub() {@Overridepublic List<Book> getBooks() throws RemoteException {synchronized (this) {Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());if (mBooks != null) {return mBooks;}return new ArrayList<>();}}@Overridepublic void addBook(Book book) throws RemoteException {synchronized (this) {if (mBooks == null) {mBooks = new ArrayList<>();}if (book == null) {Log.e(TAG, "Book is null in In");book = new Book();}//尝试修改book的参数,主要是为了观察其到客户端的反馈book.setPrice(2333);if (!mBooks.contains(book)) {mBooks.add(book);}//打印mBooks列表,观察客户端传过来的值Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());}}};@Overridepublic void onCreate() {super.onCreate();Book book = new Book();book.setName("Android开发艺术探索");book.setPrice(28);mBooks.add(book); }@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));return mBookManager;}
}
分析一下以上代码,主要分为三块:
第一块是初始化。在 onCreate() 方法里面我进行了一些数据的初始化操作。
第二块是重写 IMyAidlInterface.Stub()中的方法。在这里面提供AIDL里面定义的方法接口的具体实现逻辑。
第三块是重写 onBind() 方法。在里面返回写好的 IMyAidlInterface.Stub() 。
编写客户端代码
package com.example.aidl;import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import java.util.List;/*** 客户端的AIDLActivity.java*/
public class AIDLActivity extends AppCompatActivity {//由AIDL文件生成的Java类private IMyAidlInterface mBookManager = null;//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中private boolean mBound = false;//包含Book对象的listprivate List<Book> mBooks;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}/*** 按钮的点击事件,点击之后调用服务端的addBookIn方法** @param view*/public void addBook(View view) {//如果与服务端的连接处于未连接状态,则尝试连接if (!mBound) {attemptToBindService();Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();return;}if (mBookManager == null) return;Book book = new Book();book.setName("APP研发录In");book.setPrice(30);try {mBookManager.addBook(book);Log.e(getLocalClassName(), book.toString());} catch (RemoteException e) {e.printStackTrace();}}/*** 尝试与服务端建立连接*/private void attemptToBindService() {Intent intent = new Intent();intent.setAction("com.carrie.aidl");intent.setPackage("com.carrie.ipcserver");bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);}@Overrideprotected void onStart() {super.onStart();if (!mBound) {attemptToBindService();}}@Overrideprotected void onStop() {super.onStop();if (mBound) {unbindService(mServiceConnection);mBound = false;}}private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.e(getLocalClassName(), "service connected");mBookManager = IMyAidlInterface.Stub.asInterface(service);mBound = true;if (mBookManager != null) {try {mBooks = mBookManager.getBooks();Log.e(getLocalClassName(), mBooks.toString());} catch (RemoteException e) {e.printStackTrace();}}}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.e(getLocalClassName(), "service disconnected");mBound = false;}};
}
AIDL文件是怎么工作的?
根据以上内容,我们可以发现,在整个服务器和客户端通信的过程中,其实并没有使用到我们编写的IMyAidlInterface.aidl文件,而是使用的它自动生成的IMyAidlInterface.java文件。
那我们编写的.aidl文件到底有何用处呢?它的作用不会就是只用来生成对应的.java文件吧?
我们来做一个实验:
我们将/aidl/com.example.aidl中的IMyAidlInterface.aidl文件删除,然后将build目录下自动生成的IMyAidlInterface.java文件拷贝到/java/com.example.aidl目录下运行,发现通信仍然正常
所以我们编写的.aidl文件它的作用就是用来生成对应的.java文件,AIDL语言只是在简化我们写这个 .java 文件的工作而已,而要研究AIDL是如何帮助我们进行跨进程通信的,其实就是研究这个生成的 .java 文件是如何工作的。
结合客户端和服务端的代码分析AIDL文件
让我们再看下服务端、客户端、AIDL的代码:
AIDL:
interface IMyAidlInterface {List<Book> getBooks();void addBook(in Book book);
}
服务端:
public class AIDLService extends Service {//获取IMyAidlInterface.Stub对象private final IMyAidlInterface.Stub mBookManager = new IMyAidlInterface.Stub() {@Overridepublic List<Book> getBooks() throws RemoteException {...方法的具体实现}@Overridepublic void addBook(Book book) throws RemoteException {...方法的具体实现}};@Overridepublic void onCreate() {super.onCreate();...}@Nullable@Overridepublic IBinder onBind(Intent intent) {//返回IMyAidlInterface.Stub对象return mBookManager;}
}
客户端:
private IMyAidlInterface mBookManager = null;private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) mBookManager = IMyAidlInterface.Stub.asInterface(service);...}@Overridepublic void onServiceDisconnected(ComponentName name) {...}
};public void addBook(View view) {...mBookManager.addBook(book);
}
结合看这三部分的代码,会有以下疑问:
IMyAidlInterface是一个接口,接口意味着什么?方法都没有具体实现。在客户端代码中我们调用的addBook()方法必然来自于 IMyAidlInterface的实现类。看以上代码,方法的实现在服务端,方法的调用在客户端,方法的实现与调用在两个进程中,但好像契合的像在一个类中完成实现与调用一样。这是怎么办到的呢?奥秘应该就藏在生成的这个IMyAidlInterface.java文件中吧,带着此问题我们通过连携客户端和服务端的代码一起分析一下
生成的这个.java文件的具体内容如下:
/** This file is auto-generated. DO NOT MODIFY.*/
package com.example.aidl;
public interface IMyAidlInterface extends android.os.IInterface
{/** Default implementation for IMyAidlInterface. */public static class Default implements com.example.aidl.IMyAidlInterface{//所有的返回值前都不需要加任何东西,不管是什么数据类型@Override public java.util.List<com.example.aidl.Book> getBooks() throws android.os.RemoteException{return null;}//传参时除了Java基本类型以及String,CharSequence之外的类型//都需要在前面加上定向tag,具体加什么量需而定@Override public void addBook(com.example.aidl.Book book) throws android.os.RemoteException{}@Overridepublic android.os.IBinder asBinder() {return null;}}/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.example.aidl.IMyAidlInterface{/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.example.aidl.IMyAidlInterface interface,* generating a proxy if needed.*/public static com.example.aidl.IMyAidlInterface asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.example.aidl.IMyAidlInterface))) {return ((com.example.aidl.IMyAidlInterface)iin);}return new com.example.aidl.IMyAidlInterface.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;}}switch (code){case TRANSACTION_getBooks:{data.enforceInterface(descriptor);java.util.List<com.example.aidl.Book> _result = this.getBooks();reply.writeNoException();reply.writeTypedList(_result);return true;}case TRANSACTION_addBook:{data.enforceInterface(descriptor);com.example.aidl.Book _arg0;if ((0!=data.readInt())) {_arg0 = com.example.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.example.aidl.IMyAidlInterface{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.example.aidl.Book> getBooks() throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.util.List<com.example.aidl.Book> _result;try {_data.writeInterfaceToken(DESCRIPTOR);boolean _status = mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);if (!_status) {if (getDefaultImpl() != null) {return getDefaultImpl().getBooks();}}_reply.readException();_result = _reply.createTypedArrayList(com.example.aidl.Book.CREATOR);}finally {_reply.recycle();_data.recycle();}return _result;}//传参时除了Java基本类型以及String,CharSequence之外的类型//都需要在前面加上定向tag,具体加什么量需而定@Override public void addBook(com.example.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);}boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);if (!_status) {if (getDefaultImpl() != null) {getDefaultImpl().addBook(book);return;}}_reply.readException();}finally {_reply.recycle();_data.recycle();}}public static com.example.aidl.IMyAidlInterface sDefaultImpl;}static final int TRANSACTION_getBooks = (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.aidl.IMyAidlInterface impl) {// Only one user of this interface can use this function// at a time. This is a heuristic to detect if two different// users in the same process use this function.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.example.aidl.IMyAidlInterface getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}}public static final java.lang.String DESCRIPTOR = "com.example.aidl.IMyAidlInterface";//所有的返回值前都不需要加任何东西,不管是什么数据类型public java.util.List<com.example.aidl.Book> getBooks() throws android.os.RemoteException;//传参时除了Java基本类型以及String,CharSequence之外的类型//都需要在前面加上定向tag,具体加什么量需而定public void addBook(com.example.aidl.Book book) throws android.os.RemoteException;
}
客户端
然后先看客户端是如何获取到这个IMyAidlInterface对象的
private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {...mBookManager = IMyAidlInterface.Stub.asInterface(service);...}
我们先看一下IMyAidlInterface.Stub.asInterface(service)这个方法做了什么
/*** Cast an IBinder object into an com.example.aidl.IMyAidlInterface interface,* generating a proxy if needed.*/public static com.example.aidl.IMyAidlInterface asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}//DESCRIPTOR = "com.example.aidl.IMyAidlInterface",搜索本地是否已經有可用的对象了,如果有就将其返回android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.example.aidl.IMyAidlInterface))) {return ((com.example.aidl.IMyAidlInterface)iin);}//如果本地没有的话就新建一个返回return new com.example.aidl.IMyAidlInterface.Stub.Proxy(obj);}
接下来我们去看com.example.aidl.IMyAidlInterface.Stub.Proxy(obj)这个里面做了什么
private static class Proxy implements com.example.aidl.IMyAidlInterface{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.example.aidl.Book> getBooks() throws android.os.RemoteException{...}@Override public void addBook(com.example.aidl.Book book) throws android.os.RemoteException{...}public static com.example.aidl.IMyAidlInterface sDefaultImpl;}
基本上可以看出客户端最终通过这个Proxy类与服务端进行通信。
我们来看一下getBooks()这个方法里干了什么
@Override public java.util.List<com.example.aidl.Book> getBooks()
throws android.os.RemoteException {//_data用来存储流向服务端的数据流,//_reply用来存储服务端流回客户端的数据流android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.util.List<com.example.aidl.Book> _result;try {_data.writeInterfaceToken(DESCRIPTOR);//调用 transact() 方法将方法id和两个 Parcel 容器传过去boolean _status = mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);if (!_status) {if (getDefaultImpl() != null) {return getDefaultImpl().getBooks();}}_reply.readException();//从_reply中取出服务端执行方法的结果_result = _reply.createTypedArrayList(com.example.aidl.Book.CREATOR);}finally {_reply.recycle();_data.recycle();}//返回结果return _result;}
- 关于 _data 与 _reply 对象:一般来说,我们会将方法的传参的数据存入_data 中,而将方法的返回值的数据存入 _reply 中——在没涉及定向 tag 的情况下。如果涉及了定向 tag ,情况将会变得稍微复杂些,具体是怎么回事请参见这篇博文:你真的理解AIDL中的in,out,inout么?
- 关于 Parcel :简单的来说,Parcel 是一个用来存放和读取数据的容器。我们可以用它来进行客户端和服务端之间的数据传输,当然,它能传输的只能是可序列化的数据。具体 Parcel 的使用方法和相关原理可以参见这篇文章:Android中Parcel的分析以及使用
- 关于 transact() 方法:这是客户端和服务端通信的核心方法。调用这个方法之后,客户端将会挂起当前线程,等候服务端执行完相关任务后通知并接收返回的 _reply 数据流。关于这个方法的传参,这里有两点需要说明的地方:
- 方法 ID :transact() 方法的第一个参数是一个方法 ID ,这个是客户端与服务端约定好的给方法的编码,彼此一一对应。在AIDL文件转化为 .java 文件的时候,系统将会自动给AIDL文件里面的每一个方法自动分配一个方法 ID。
- 第四个参数:transact() 方法的第四个参数是一个 int 值,它的作用是设置进行 IPC 的模式,为 0 表示数据可以双向流通,即 _reply 流可以正常的携带数据回来,如果为 1 的话那么数据将只能单向流通,从服务端回来的 _reply 流将不携带任何数据。
注:AIDL生成的 .java 文件的这个参数均为 0。
具体的就不在这里继续阐述了,因为它涉及到Binder里比较底层的东西,先埋个坑,如果以后有时间的话再补充进来~ 坑坑坑 下面直接上总结吧
Proxy 类的方法里面一般的工作流程:
- 1,生成 _data 和 _reply 数据流,并向 _data 中存入客户端的数据。
- 2,通过 IBinder service调用 transact() 方法将它们传递给服务端,并请求服务端调用指定方法。
- 3,接收 _reply 数据流,并从中取出服务端传回来的数据。
接下来让我们再从服务端来进行分析
服务端
首先从上面我们知道客户端是通过transact() 方法将它们传递给服务端,那服务端是通过什么来接收的呢?让我们接着往下看
首先我们在IMyAidlInterface.java这个文件中,可以看到有这么一个方法,如下:
@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:{...}}switch (code){case TRANSACTION_getBooks:{...}case TRANSACTION_addBook:{...}default:{return super.onTransact(code, data, reply, flags);}}}
通过它的方法名和传入的参数,路过的大爷应该都会猜一下,onTransact()这个方法是不是和客户端将数据传递给服务端调用的 transact() 方法有什么关系。
所以它会是服务端接收数据的那个方法吗?
当我们仔细去看这个方法里面的内容,它其实在传入参数后,进行了一个Switch判断,让我们看一下case TRANSACTION_getBooks里面都具体做了什么
case TRANSACTION_getBooks: {data.enforceInterface(DESCRIPTOR);//调用 this.getBooks() 方法,在这里开始执行具体的事务逻辑//result 列表为调用 getBooks() 方法的返回值java.util.List<com.lypeer.ipcclient.Book> _result = this.getBooks();reply.writeNoException();//将方法执行的结果写入 reply ,reply.writeTypedList(_result);return true;
}
直接调用服务端这边的具体方法实现,然后获取返回值并将其写入 reply 流,在执行完 return true 之后系统将会把 reply 流传回客户端
然后还是直接上总结~
服务端的一般工作流程:
- 1,获取客户端传过来的数据,根据方法 ID 执行相应操作。
- 2,将传过来的数据取出来,调用本地写好的对应方法。
- 3,将需要回传的数据写入 reply 流,传回客户端。
总而言之,言而总之,学习AIDL不要把它看成一个新的东西,你要把它当成简化你代码工作量的工具,学习起来会有意思的多。
差不多到这里就结束了,实在是困了,如果以后还有要补充更详细的东西,会再进行更新~