Android AIDL(跨进程)

AIDL是Android Interface Definition Language的缩写,用于实现进程间通信。它提供了类似Java的语法,但主要处理数据流向。AIDL支持基本数据类型、String、CharSequence、List和Map,并允许定义Parcelable对象。使用AIDL时,需要为非默认支持的数据类型创建一个.aidl文件,然后在服务端实现接口方法,客户端通过Binder调用这些方法。跨进程通信涉及序列化,通常通过实现Parcelable接口完成。服务端创建Binder对象,客户端通过bindService连接服务端并调用方法。AIDL的工作原理是通过Parcel对象在客户端和服务端之间传递数据,实现通信。

1.AIDL
AIDL是Android Interface Definition Language的缩写,即Android接口定义语言。所以AIDL是一种语言。
AIDL是为了实现进程间通信,尤其是涉及多进程并发情况下的进程间通信。通过AIDL可以在一个进程中访问另一个进程的数据,甚至调用它的一些方法,当然只能是特定的方法。

2.AIDL的语法
AIDL的语法和Java基本是一样的,只是在一些细微处有些许差别——毕竟它只是被创造出来简化Android程序员工作的。
列举一下AIDL和Java不一样的地方:
①文件类型:用AIDL书写的文件的后缀是.aidl,而不是.java。
②数据类型:AIDL默认支持一些数据类型,在使用这些数据类型时不需要导包,但是这些类型之外的数据类型在使用之前必须导包,即使目标文件与当前正在编写的.aidl文件在同一个包下(在Java中,这种情况是不需要导包的)。
AIDL默认支持的数据类型包括:
(1)八种基本数据类型,包括byte,short,int,long,float,double,boolean,char。
(2)String类型。
(3)CharSequence类型。
(4)List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的Parcelable。List可以使用泛型。
(5)Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map不支持泛型。
③定向tag:AIDL中的定向tag表示在跨进程通信中数据的流向,其中in表示数据只能由客户端流向服务端,out表示数据只能由服务端流向客户端,而inout则表示数据可在服务端与客户端之间双向流通。
数据流向是针对在客户端中的那个传入方法的对象而言的。in为定向tag的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout则表示服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
注意:基本类型和String 、CharSequence的定向tag默认且只能是in。
不要滥用定向tag,而要根据需要选取合适的。要是不管三七二十一全都一上来就用inout,等工程大了系统的开销就会大很多,因为排列整理参数的开销是很昂贵的。
④两种AIDL文件:所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用非默认支持的数据类型。一类是用来定义方法接口,以供系统使用来完成跨进程通信。
可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。
注:所有非默认支持的数据类型必须通过第一类AIDL文件定义才能被使用。

下面是两个例子,对于常见的AIDL文件都有所涉及:
①AIDL用来定义Parcelable对象
//Book.aidl 第一类AIDL文件,作用是引入一个序列化对象Book供其他的AIDL文件使用。注意:Book.aidl与Book.java的包名应当是一样的。

package com.lypeer.ipcclient;

parcelable Book; //注意parcelable是小写

②AIDL用来定义方法接口
//BookManager.aidl 第二类AIDL文件

package com.lypeer.ipcclient;
//导入需要使用的非默认支持数据类型的包
import com.lypeer.ipcclient.Book;

interface IBookManager {
//所有的返回值前都不需要加任何东西,不管是什么数据类型
List< Book> getBooks();
Book getBook();
int getBookCount();
//传参时除了Java基本类型以及String、CharSequence之外的类型,都需要在前面加上定向tag
void setBookPrice(in Book book , int price)
void setBookName(in Book book , String name)
void addBookIn(in Book book);
void addBookOut(out Book book);
void addBookInout(inout Book book);
}

3.使用AIDL文件完成跨进程通信
跨进程通信时,根据AIDL中定义的方法里是否包含非默认支持的数据类型,要进行的操作是不一样的。如果不包含,只需要编写一个AIDL文件;如果包含,通常需要写n+1个AIDL文件(n为非默认支持的数据类型的种类数)。

以AIDL文件中包含非默认支持的数据类型的情况为例,说明使用AIDL进程间通信的步骤:
①使数据类实现Parcelable接口
由于不同的进程有着不同的内存区域,并且它们只能访问自己那一块内存区域,所以不能像平时那样传一个句柄过去就完事了(句柄指向的是一个内存区域),现在目标进程根本不能访问源进程的内存。所以必须将要传输的数据转化为能够在内存之间流通的形式,这个转化的过程就叫序列化与反序列化。
比如现在要将一个对象的数据从客户端传到服务端去,就可以在客户端对这个对象进行序列化操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化操作,从而还原其中包含的数据。通过这种方式就达到了在一个进程中访问另一个进程数据的目的。

通过AIDL进行跨进程通信时,选择的序列化方式通常是实现Parcelable接口。
首先,创建一个类实现Parcelable接口。这个类就是接下来跨进程通信需要传递的类,所以它必须实现Android序列化。实现Parcelable接口时studio会报红,这时候只需要alt+enter解决即可。
最后生成的Book类如下:
public class Book implements Parcelable{
private String name;
private int price;
public Book(){ }
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;
}

public Book(Parcel in) {
name = in.readString();
price = in.readInt();
}

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.writeString(name);
dest.writeInt(price);
}
}
注意:默认生成的模板类的对象只支持为in的定向tag。这是因为默认生成的类里面只有writeToParcel()。如果要支持为out或inout的定向tag,还需要实现readFromParcel()。这个方法并没有在Parcelable接口里面,需要从头写。
readFromParcel()可以仿照writeToParcel():
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
//参数是一个Parcel,用来存储与传输数据
public void readFromParcel(Parcel dest) {
//注意,读值顺序应当是和writeToParcel()方法中一致的
name = dest.readString();
price = dest.readInt();
}
这样添加了readFromParcel()之后,Book类的对象在AIDL文件里就可以用out或inout来作为它的定向tag了。
注:若AIDL文件中涉及到的所有数据类型均为默认支持的数据类型,则无此步骤。因为默认支持的那些数据类型都是可序列化的。

至此,关于AIDL中非默认支持数据类型的序列化操作就完成了。

②书写AIDL文件
首先需要一个Book.aidl文件来将Book类引入,使其他的AIDL文件可以使用Book对象。
在项目的app上点击右键,然后new->AIDL-> AIDL File,生成AIDL文件。
注意:如果先创建Book.java文件,然后在创建Book.aidl文件时,studio会报Interface name must be unique的错误,此时可以改成先创建Book.aidl,再创建Book.java就可以了。
生成AIDL文件后,项目目录中就多了一个叫做aidl的包,aidl包的层级和java包相同,并且aidl包里默认有着和java包里相同的包结构。
如果你用的是Eclipse或者较低版本的studio,编译器没有这个选项,就可以自己写。打开项目文件夹,依次进入app->src->main,在main包下新建一个和java文件夹平级的aidl文件夹,然后手动在这个文件夹里面新建和java文件夹里面的默认结构一样的文件夹结构,再在最里层新建.aidl文件就可以了。

在这里需要两个AIDL文件:
(1)Book.aidl
//第一类AIDL文件,作用是引入一个序列化对象Book供其他的AIDL文件使用。注意:Book.aidl与Book.java的包名应当是一样的。

package com.demo.test;

parcelable Book; //注意parcelable是小写

(2)IBookManager.aidl
//第二类AIDL文件,作用是定义方法接口
package com.demo.test;

//导入需要使用的非默认支持数据类型的包
import com.demo.test.Book;

interface IBookManager {
//所有的返回值前都不需要加任何东西,不管是什么数据类型
List< Book> getBooks();
//传参时,除了Java基本类型以及String、CharSequence之外的类型,都需要在前面加上定向tag
void addBook(in Book book);
}
注意:一直强调Book.aidl与Book.java的包名是一样的。似乎理所当然的意味着这两个文件应当在同一个包里面,事实上很多比较老的文章里就是这样说的,它们说最好都在aidl包里同一个包下,方便移植。然而在Android Studio里并不是这样,如果这样做的话,系统根本就找不到Book.java文件,从而在其他的AIDL文件里面使用Book对象的时候会报Symbol not found的错误。这是因为Android Studio默认使用Gradle构建Android项目,而Gradle在构建项目时会通过sourceSets配置不同文件的访问路径,从而加快查找速度。问题就出在这里,Gradle默认将java代码的访问路径设置在java包下,这样一来,如果java文件放在aidl包下的话,系统当然找不到这个java文件。
解决办法有两个:要么让系统来aidl包里面来找java文件,要么把java文件放到系统能找到的地方去,也就是放到java包里。
这两种方式具体做法如下:
(1)修改build.gradle文件:在android{}中间加上下面的内容:
sourceSets {
main {
java.srcDirs = [‘src/main/java’, ‘src/main/aidl’]
}
}
这样就把java代码的访问路径设置成了java包和aidl包,系统就会到aidl包里面去查找java文件。只是这样设置后Android Studio中的项目目录会有一些改变,感觉改得挺难看的。
(2)把java文件放到java包下:把Book.java放到java包里任意一个包下,保持其包名不变,与Book.aidl一致。只要它的包名不变Book.aidl就能找到Book.java,而只要Book.java在java包下,系统就能找到它。但是这样做也有一个问题,就是在移植相关.aidl文件和.java文件的时候没那么方便,不能直接把整个aidl文件夹拿过去完事儿了,还要单独将.java文件放到java文件夹里。
这两个方法都能解决找不到.java文件的坑,用哪一种都可以。

到这里,AIDL文件新建并且书写完毕了,clean一下项目,如果没有报错,这一块就OK了。

③移植相关文件
在客户端和服务端中都要有需要用到的.aidl文件和其中涉及到的.java文件,因此不管在哪一端写的这些类文件,写完之后都要把它们复制到另一端去。
如果用上面两个方法中的第一个解决找不到.java文件的问题,就直接将aidl包复制到另一端的main目录下就可以了;如果使用第二个方法,除了把整个aidl文件夹拿过去,还要单独将.java文件放到java文件夹里去。
复制时要注意,aidl文件夹和java文件夹平级,都在app-src-main下。复制.java文件时要保证.java文件的包名和.aidl的包名一致。
复制完成后,同步并rebuild一下工程,此时在build下会生成aidl文件对应的java文件。

④编写服务端代码
通过上面几步,已经完成了AIDL及相关文件的全部内容,下面就利用这些来进行跨进程通信。

在写完AIDL文件并clean或rebuild项目之后,编译器会根据AIDL文件自动生成一个与AIDL文件同名的.java文件,这个.java文件才是与跨进程通信密切相关的东西。

通信的基本操作流程是:在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。
注意:因为AIDL是多线程并发,所以在方法接口的具体逻辑里都加了synchronized。

服务端代码:
public class AIDLService extends Service {
private List< Book> mBooks = new ArrayList<>();

//由AIDL文件生成的IBookManager
private final IBookManager.Stub mBookManager = new IBookManager.Stub() {
@Override
public 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<>();
}
}
@Override
public 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());
}
}
};

@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setName(“Android开发艺术探索”);
book.setPrice(28);
mBooks.add(book);
}

@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format(“on bind,intent = %s”, intent.toString()));
return mBookManager;
}
}
整体的代码结构大致可以分为三块:
第一块是初始化,在onCreate()方法里面进行一些数据的初始化操作。
第二块是重写IBookManager.Stub中的方法,在这里提供AIDL里定义的方法接口的具体实现逻辑。
第三块是重写onBind()方法,返回写好的BookManager.Stub。

最后别忘了在Manefest文件里面注册Service :
< service
android:name=".service.AIDLService"
android:exported=“true”>
< intent-filter>
< action android:name=“com.demo.aidl”/>
< category android:name=“android.intent.category.DEFAULT”/>
< /intent-filter>
< /service>
到这里,服务端代码就编写完毕了。

⑤编写客户端代码
客户端主要是调用服务端的方法。在调用之前要先连接上服务端。
客户端代码:
public class AIDLActivity extends Activity {
//由AIDL文件生成的Java类
private IBookManager mBookManager = null;
//是否已连接服务,false未连接,true已连接
private boolean mBound = false;
private List< Book> mBooks;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected( ComponentName name, IBinder service) {
Log.e(TAG, “service connected”);
mBookManager = IBookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();
Log.e(TAG, mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected( ComponentName name) {
Log.e(TAG, “service disconnected”);
mBound = false;
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
addBtn.setOnClickListener(new OnClickListener {
addBook();
}
}

//点击按钮调用服务端的addBook方法
public void addBook() {
//若服务未连接,则尝试连接
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(TAG, book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}

//尝试与服务端建立连接
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction(“com.demo.aidl”);
intent.setPackage( “com.demo.ipcserver”);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}

@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}

@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
}
首先建立连接,然后在ServiceConnection里面获取BookManager对象,接着通过它来调用服务端的方法。

⑥开始通信
通过上面的步骤,已经完成了所有的前期工作,接下来就可以通过AIDL来进行跨进程通信了。
将两个app同时运行在同一台手机上,然后调用客户端的addBook()方法,会看到服务端的logcat信息:
on bind,intent = Intent { act=com.demo.aidl pkg=com.demo.ipcserver }
invoking getBooks() method , now the list is : [name : Android开发艺术探索 , price : 28]
invoking addBooks() method , now the list is : [name : Android开发艺术探索 , price : 28, name : APP研发录In , price : 2333]

客户端的log信息:
service connected
[name : Android开发艺术探索 , price : 28]
name : APP研发录In , price : 30

所有的log信息都很正常并且符合预期,说明到这里为止的步骤都是正确的,能够正确的使用AIDL来进行跨进程通信的。

4.AIDL源码分析
写完AIDL文件后,编译器会自动生成一个同名的.java接口文件,在实际编写客户端和服务端代码的过程中,真正工作的就是这个文件,而.aidl文件从头到尾都没有出现过。其实写AIDL文件的目的就是为了生成这个文件。事实上就算不写AIDL文件,直接按照它生成的.java文件那样写一个.java文件出来,在服务端和客户端中也可以照常使用这个.java类来进行跨进程通信。所以AIDL语言只是在简化写这个.java文件的工作而已,而要研究AIDL是如何实现跨进程通信的,其实就是研究这个生成的.java文件是如何工作的。
自动生成的这个.java文件的完整路径是:app->build->generated->aidl_source_output_dir->debug->out->com->demo->ipcclient->IBookManager.java(其中com.demo.ipcclient是包名,相对应的AIDL文件为IBookManager.aidl )。

先从整体对这个流程整理一下:
首先从服务端开始,刨去其它与此无关的东西,从宏观上看看它干了些啥:
private final IBookManager.Stub mBookManager = new IBookManager.Stub() {
@Override
public List< Book> getBooks() throws RemoteException {
// getBooks()方法的具体实现
}

@Override
public void addBook(Book book) throws RemoteException {
// addBook()方法的具体实现
}
};

public IBinder onBind(Intent intent) {
return mBookManager;
}
首先对BookManager.Stub里面的抽象方法进行了重写。这些抽象方法正是在AIDL文件里面定义的那些。也就是说在这里为之前定义的方法提供了具体实现。然后在onBind()方法里将这个IBookManager.Stub作为返回值传了过去。

先来看看Stub这个类:
public static abstract class Stub extends android.os.Binder implements IBookManager
可以看到这个类是Binder的子类。

接着看客户端:
private IBookManager mBookManager = null;

private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected (ComponentName name, IBinder service)
mBookManager = IBookManager.Stub.asInterface(service);
//省略
}
@Override
public void onServiceDisconnected (ComponentName name) {
//省略
}
};

public void addBook(View view) {
//省略
mBookManager.addBook(book);
}
简单说客户端就做了这些事:获取IBookManager对象,然后调用它里面的方法。

现在结合服务端与客户端做的事情,好好思考一下,会发现这样一个怪事情:它们配合的如此紧密,以至于它们之间的交互竟像是同一个进程中的两个类那么自然!大家可以回想下平时项目里的接口回调,基本流程与此一般无二。明明是在两个线程里面,数据不能直接互通,为何它们能交流的如此愉快呢?答案就在IBookManager.java里。接下来具体分析:
①从客户端开始
点开IBookManager.java,会发现IBookManager是一个接口类!我们都知道,接口类里方法是没有具体实现的。但是明明在客户端里面调用了 mBookManager.addBook() !那么就说明在客户端里用到的IBookManager肯定是它的一个实现类!那么就可以从这个实现类入手,看看客户端调用addBook()方法的时候,究竟BookManager在背后完成了哪些操作。首先看下客户端的IBookManager对象是怎么来的:
public void onServiceConnected( ComponentName name, IBinder service)
mBookManager = IBookManager.Stub.asInterface(service);
}
看下这个IBookManager.Stub.asInterface():
public static IBookManager asInterface( android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
//DESCRIPTOR = “com.demo.ipcclient.Boo kManager”,搜索本地是否已经有可用的对象了,如果有就将其返回
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBookManager))) {
return ((IBookManager) iin);
}
//如果本地没有的话就新建一个返回
return new IBookManager.Stub.Proxy(obj);
}
调用queryLocalInterface()方法,它是IBinder接口里面的一个方法,而这里传进来IBinder对象就是onServiceConnected()方法里的第二个参数service。
然后创建一个对象返回,这个就是我们的目标——那个实现了IBookManager接口的实现类。果断去看这个IBookManager.Stub.Proxy类:
private static class Proxy implements IBookManager {
private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {
//此处的remote正是onServiceConnected()方法里的第二个参数IBinder service
mRemote = remote;
}

@Override
public java.util.List< Book> getBooks(){
//省略
}

@Override
public void addBook(Book book) {
//省略
}
//省略部分方法
}
看到这里可以确定:Proxy类确实是我们的目标,客户端最终就是通过这个类与服务端进行通信的。
那接下来看getBooks()方法里面具体做了什么:
@Override
public List< Book> getBooks() throws RemoteException {
//_data用来存储流向服务端的数据流,_reply用来存储服务端流回客户端的数据流
Parcel _data = android.os.Parcel.obtain();
Parcel _reply = android.os.Parcel.obtain();
java.util.List< Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//调用transact()方法将方法id和两个Parcel容器传过去
mRemote.transact(Stub.TRANSACTIO N_g etBooks, _data, _reply, 0);
_reply.readException();
//从_reply中取出服务端执行方法的结果
_result = _reply.createTypedArrayList( Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result; //将结果返回
}
这段代码里有几个需要说明的地方:
_data与_reply对象:一般来说,会将方法的传参的数据存入_data中,而将方法的返回值的数据存入_reply中(在没涉及定向 tag 的情况下)。
Parcel :Parcel是一个用来存放和读取数据的容器。可以用它来进行客户端和服务端之间的数据传输,它能传输的只能是可序列化的数据。
transact()方法:这是客户端和服务端通信的核心方法。调用这个方法后,客户端将会挂起当前线程,等候服务端执行完相关任务后,通知并接收返回的_reply数据流。关于这个方法的传参,有两点需要说明的地方:
(1)方法ID:transact()方法的第一个参数是方法ID,这个是客户端与服务端约定好的给方法的编码,彼此一一对应。在AIDL文件转化为 .java 文件的时候,系统将会自动给AIDL文件里面的每一个方法自动分配一个方法ID。
(2)第四个参数:transact()方法的第四个参数是一个int值,它的作用是设置进行IPC的模式,为0表示数据可以双向流通,即_reply流可以正常的携带数据回来;为1则表示数据只能单向流通,从服务端回来的_reply流将不携带任何数据。
注:AIDL生成的.java文件的这个参数均为0。

总结一下在Proxy类的方法里面一般的工作流程:
(1)生成_data和_reply数据流,并向_data中存入客户端的数据。
(2)通过transact()方法将它们传递给服务端,并请求服务端调用指定方法。
(3)接收_reply数据流,并从中取出服务端传回来的数据。

其实onServiceConnected()方法里的第二个参数(IBinder service)是客户端与服务端通信的灵魂人物,正是通过它调用的transact()方法才将客户端的数据和请求发送到服务端去。从这个角度来看,这个service就像是服务端在客户端的代理一样——你想要找服务端?要传数据过去?那你来找我,我给你把数据送过去——而BookManager.java中的那个Proxy类,就只能沦为二级代理了,在外部通过它来调动service对象。
至此,客户端在IPC中进行的工作已经分析完了。

②接着看服务端:
客户端通过transact()方法将数据和请求发送到服务端,那么服务端应当有一个方法来接收这些传过来的东西:在IBookManager.java里面有一个叫做onTransact()的方法,看这名字就知道多半和它脱不了关系,再一看它的传参 (int code, Parcel data, Parcel reply, int flags) ,和transact()方法的传参是一样的!下面来看它是怎么做的:
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBooks: {
//省略
return true;
}
case TRANSACTION_addBook: {
//省略
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
它在接收了客户端transact()方法传过来的参数后就直接进入了一个switch选择:根据传进来的方法ID执行不同的操作。
接下来看一下每个方法里面具体做了些什么,以getBooks()方法为例:
case TRANSACTION_getBooks: {
data.enforceInterface(DESCRIPTOR);
//调用this.getBooks()方法,在这里开始执行具体的事务逻辑,result列表为调用getBooks()方法的返回值
List< Book> _result= this.getBooks();
reply.writeNoException();
//将方法执行的结果写入 reply ,
reply.writeTypedList(_result);
return true;
}
直接调用服务端的具体方法实现,然后获取返回值并将其写入reply流。当然这是由于这个方法没有传入参数并且不涉及定向tag的关系,不然还会涉及到将传入参数从data中读取出来,以及针对定向tag的操作。

另外,还有一个问题,有些读者可能会疑惑,为什么这里没有看到关于将reply回传到客户端的相关代码?事实上在客户端也没有看到它将相关参数传向服务端的相关代码,它只是把这些参数都传入了一个方法,其中过程同样是隐藏的,服务端也一样,在执行完return true之后系统将会把reply流传回客户端,具体是怎么做的就不足为外人道了。不知道大家发现了没有,通过隐藏了这些细节,在transact()与onTransact()之间的调用以及数据传送看起来就像是发生在同一个进程甚至同一个类里面一样。

最后,总结一下服务端的一般工作流程:
(1)获取客户端传过来的数据,根据方法ID执行相应操作。
(2)将传过来的数据取出来,调用本地写好的对应方法。
(3)将需要回传的数据写入reply流,传回客户端。

其实,客户端和服务端并不是直接通信的,而是经过了内核空间。这期间数据复制了1次。
在这里插入图片描述
Android系统为每个App分配了一定的虚拟内存,如下图的客户端和服务端。由于App有各自的虚拟内存,所以不能直接通信,数据需要经过内核空间。
App的虚拟内存映射到真实的物理内存上,例如客户端App虚拟内存映射到了物理内存A,服务端App虚拟内存映射到物理内存B。客户端把数据A发送到内核空间,内核空间把它复制到共享物理内存上(红色框),正是因为共享,所以服务端直接使用即可,不需要再复制了。
在这里插入图片描述

我的理解:
AIDL默认支持的数据类型:8中基本数据类型(int short long float double boolean char byte)、String、CharSequence、List(支持泛型)、Map(不支持泛型)。除了默认支持的这些数据类型以外,用到其他类型需要新建一个aidl文件先定义。AIDL有两种文件格式,一种用来定义非默认支持的数据类型,一种用来定义接口方法。

AIDL的使用很简单,先在客户端新建一个实体类的java文件(Book.java),然后新建实体类的AIDL文件(Book.aidl),引入实体java类(parcelable Book)。注意:一定要保证实体类的aidl文件包名和实体类的java文件包名一致。再新建一个aidl的接口类(BookManager.aidl),接口里定义好需要的方法。rebuild项目,自动生成aidl对应的java文件(BookManager.java)。然后把2个aidl文件和一个java实体文件拷贝到服务端(注意路径对应),rebuild项目。
准备工作做好了,接下来开始进程间通信。
在服务端先利用Stub(继承IBinder并实现AIDL接口)生成BookManager.Stub对象,并实现aidl中定义的接口方法。在onBind方法中返回该Binder变量到客户端。最后别忘记在配置文件里注册服务。
在客户端,通过bindService连接服务器,在ServiceConnection的onServiceConnected方法中,通过传过来的binder生成BookManager对象(BookManager bookManager=new BookManager.Stub.asInterface(binder)),然后就可以调用BookManager在服务端实现的具体方法了。
至于原理,主要是用到了onServiceConnected方法中第二个参数binder来实现了数据的传输和接收。在客户端的BookManager.Stub.asInterface( binder)中返回了BookManager接口的实现类对象,在该实现类里对每个接口方法都做了如下操作:生成_data和_replay数据流,并向_data里存入客户端请求方法里的数据,通过binder的tansact方法将它们传到服务端,请求服务端调用指定方法,然后等待服务端处理。这时在服务端的BookManager.java里有与transact对应的onTansact方法,它接收到客户端传过来的数据,然后调用服务端的具体方法实现逻辑,将结果值写入_reply返回给了客户端。这样就实现了客户端和服务端的通信。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值