AIDL
选自https://www.cnblogs.com/tangZH/p/10775848.html
AIDL是Android中的IPC(跨进程通信)的一种,使用AIDL是用于跨进程通信进行数据的传递。通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。
基本用法
支持的数据类型
- 支持八种基本数据类型:byte、char、short、int、long、float、double、boolean;
- String、CharSequence;
- 实现了Parcelable接口的实体类;
- List类型。List中承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象;
- Map类型。Map中承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象;
AIDL文件类型
AIDL文件可以分为两类。一类是用来声明实现了Parcelable接口的数据类型,以供其它AIDL文件使用那些非默认支持的数据类型。另一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值。
定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为in、out、inout三种。其中in表示数据只能由客户端流向服务端;out表示数据只能由服务端流向客户端;inout表示数据可在服务端和客户端之间双向流通;此外,如果AIDL方法接口的参数值类型是:八种基本数据类型、String、CharSequence、其它AIDL文件定义的方法接口,那么这些参数值的定向Tag默认是且只能是in,所以除了这些类型外,其它参数值都需要明确标注使用哪种定向Tag。
在AIDL文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同一个包名下。
代码实现
下面是实现两个app之间的跨进程通信的代码;
作为服务端
1 . 创建aidl文件Book.aidl
创建了Book.aidl文件以后,并不会在java这个文件夹中看到,而是在与java文件夹同级中生成一个aidl文件夹,并且文件夹下级的文件夹名字与在哪里创建的Book.aidl文件夹名字一致。
生成的Book.aidl文件如下:
// Book.aidl
package com.ex.myapplication.com.aidl;
// Declare any non-default types here with import statements
interface Book {
/**
* 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);
}
2 . 创建一个实体类Book。注意Book类的包名必须与Book.aidl包名一样。但是生成的Book.java文件会在java文件夹下的创建Book类的文件夹下。
Book.java必须继承Parcelable接口,Book.java的代码如下:
package com.ex.myapplication.com.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private String bookName;
public Book(String name) {
this.bookName = name;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
protected Book(Parcel in) {
bookName = in.readString();
}
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(bookName);
}
public void readFromParcel(Parcel desc) {
bookName = desc.readString();
}
@Override
public String toString() {
return "bookName = " + bookName;
}
}
3 . 修改刚才生成的Book.aidl文件,将其声明为parcelable(小写的p)类型,并且需要注意的是,原先默认的interface接口需要去掉,否则会编译报错;
将刚才生成的Book.aidl文件修改为以下:
// Book.aidl
package com.ex.myapplication.com.aidl;
// Declare any non-default types here with import statements
parcelable Book;
4 . 创建将服务端暴露给客户端的接口BookManager.aidl文件。
在与刚才创建Book.aidl文件的相同目录下创建aidl文件BookManager.aidl。
BookManager.aidl代码如下:
// BookManager.aidl
package com.ex.myapplication.com.aidl;
import com.ex.myapplication.com.aidl.Book;
interface BookManager {
// void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
// double aDouble, String aString);
List<Book> getBookList();
void addBook(inout Book book);
}
5 . 把包手动导进来;
在Build菜单选项中使用make Project,便可以将刚才的BookManager.aidl文件编译成代码文件。
BookManager.aidl文件会生成一个BookManager.java文件,这个BookManager.java才是真正需要用到的。
public interface BookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.ex.myapplication.com.aidl.BookManager {
private static final java.lang.String DESCRIPTOR = "com.ex.myapplication.com.aidl.BookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.ex.myapplication.com.aidl.BookManager interface,
* generating a proxy if needed.
*/
public static com.ex.myapplication.com.aidl.BookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.ex.myapplication.com.aidl.BookManager))) {
return ((com.ex.myapplication.com.aidl.BookManager) iin);
}
return new com.ex.myapplication.com.aidl.BookManager.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 {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.ex.myapplication.com.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.ex.myapplication.com.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.ex.myapplication.com.aidl.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.ex.myapplication.com.aidl.BookManager {
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;
}
/**
* 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);
@Override
public java.util.List<com.ex.myapplication.com.aidl.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.ex.myapplication.com.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.ex.myapplication.com.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.ex.myapplication.com.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);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
book.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
/**
* 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);
public java.util.List<com.ex.myapplication.com.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.ex.myapplication.com.aidl.Book book) throws android.os.RemoteException;
}
可以看到,生成的BookManager.java这个类继承自IInterface这个接口。这个类首先声明了两个方法:getBookList()和addBook()。显示就是我们在BookManager.aidl中声明的方法。同时,这个类中还声明了两个整型的id(TRANSACTION_getBookList和TRANSACTION_addBook)分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。接着,声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程;而当这两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。这个类的核心实现时它的内部类Stub和Stub的内部代理类Proxy。
下面是对这两个内部类的每个方法的介绍:
(1) . DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类名表示,比如本例中的代码如下:
private static final java.lang.String DESCRIPTOR = "com.ex.myapplication.com.aidl.BookManager";
(2) . asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。
(3) . asBinder()
此方法用于返回当前Binder对象。
(4) . onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型为public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact()方法的执行过程就是这样。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。
(5) . Proxy#getBookList()
这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值List对象_resule;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact()方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact()方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;然后把_reply的返回值赋值给_result,最后返回_result中的数据。
(6) . Proxy#addBook(com.ex.myapplication.com.aidl.Book book)
这个方法运行在客户端,它的执行过程和getBookList()是一样的。但是因为addBook()方法没有返回值,所以这个方法不需要从_reply中取出返回值。
通过上面的分析,已经知道了Binder的工作机制,但是有两点需要说明一下:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的话,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管 是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
Binder的工作机制,如下图:
我们可以不提供AIDL文件即可实现Binder,之所以提供AIDL文件,是为了方便系统为我们生成代码。
Binder还有两个重要的方法linkToDeath()和unlinkToDeath()。Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。主要是如果我们不知道Binder连接已经断裂,那么客户端的功能就会受到影响。为了解决这个问题,Binder中提供了两个配对的方法linkToDeath()和unlinkToDeath(),通过linkToDeath()我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。
首先声明一个DeathRecipient对象。DeathRecipient是一个接口,其内部只有一个方法bindDied(),我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied()方法,然后我们就可以移除之前绑定的binder代理并重新绑定远程服务。
//这部分代码写在客户端LaunchActivity.java中;
IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if(bookManager == null){
Log.e(TAG,"bookManager == null...");
return;
}
bookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
bookManager = null;
//重新绑定远程Service;
Intent intent = new Intent(ACTION2);
// 注意在 Android 5.0以后,不能通过隐式 Intent 启动 service,必须制定包名;
intent.setPackage("com.ex.app");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
};
其次,在客户端绑定远程服务成功后,给binder设置死亡代理:
//这部分代码在客户端的ServiceConnection的onServiceConnected()方法中;
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected...");
bookManager = BookManager.Stub.asInterface(service);
//客户端绑定远程服务成功后,给binder设置;
try {
Log.e(TAG,"service.linkToDeath...");
service.linkToDeath(mDeathRecipient,0);
} catch (RemoteException e) {
Log.e(TAG,"service.linkToDeath RemoteException...");
e.printStackTrace();
}
}
其中,linkToDeath()的第二个参数是一个标记位,我们直接设为0即可。经过上面两个步骤,就给我们的Binder设置了死亡代理。当Binder死亡的时候,我们就可以收到通知了。另外通过Binder的isBinderAlive()方法也可以判断Binder是否死亡。
6 . 创建Service,供客户端绑定;
创建一个BookService的Service文件,这个文件不需要限定在某个文件夹中。代码如下:
package com.ex.myapplication.service;
import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.ex.myapplication.com.aidl.Book;
import com.ex.myapplication.com.aidl.BookManager;
import java.util.ArrayList;
import java.util.List;
public class BookService extends Service {
private final String TAG = BookService.class.getSimpleName();
private List<Book> list;
public BookService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate...");
//默认先有五本书;
list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Book book = new Book("第" + i + "本书");
list.add(book);
}
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind...");
return bookManager;
}
private BookManager.Stub bookManager = new BookManager.Stub() {
//获取书的列表;
@Override
public List<Book> getBookList() throws RemoteException {
Log.e(TAG, "getBookList...");
return list;
}
//添加书;
@Override
public void addBook(Book book) throws RemoteException {
Log.e(TAG, "addBook...");
if (book != null) {
list.add(book);
}
Log.e(TAG, book.toString());
}
};
@Override
public void unbindService(ServiceConnection conn) {
super.unbindService(conn);
Log.e(TAG, "unbindService...");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy...");
}
}
在BookService文件中,onBind()方法返回的是BookManager.Stub对象,实现里面的两个方法,客户端拿到这个对象后就可以与服务端进行数据通信了。
7 . 在AndroidManifest.xml文件中修改BookService的service标签;
<service
android:name=".service.BookService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="android.intent.action.BookService"/>
</intent-filter>
</service>
作为客户端
1 . 将aidl文件与Book实体类拷贝到客户端app的工程中。注意它们的目录需要和在服务端app中一样。我们就将服务端app中的aidl整个文件夹复制,然后粘贴到客户端app与java文件夹同级。Book这个实体类也需要放在与服务端app一样的文件夹路径中,这个在实际项目中,我们会让服务端app与客户端app都依赖同一个module,然后把数据实体类写在那个module中,否则,我们需要把服务端app与客户端app的文件夹路径写成一样的(实际中不会这样来做,我在这个demo中为了偷懒不去写一个module,就这样子来操作了)。
2 . 编写代码,与服务端进行连接;
package com.ex.myapplication;
import androidx.appcompat.app.AppCompatActivity;
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.TextView;
import com.ex.myapplication.com.aidl.Book;
import com.ex.myapplication.com.aidl.BookManager;
import java.util.List;
public class LaunchActivity extends AppCompatActivity {
private final String TAG = LaunchActivity.class.getSimpleName();
private TextView textView1;
private TextView textView2;
private TextView textView3;
//服务端app的那个BookService的intent-filter;
private final String ACTION2 = "android.intent.action.BookService";
private BookManager bookManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launch);
textView1 = findViewById(R.id.textView1_tv);
textView2 = findViewById(R.id.textView2_tv);
textView3 = findViewById(R.id.textView3_tv);
textView1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "textView1 onClick...");
Intent intent = new Intent(ACTION2);
intent.setPackage("com.ex.app");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
});
textView2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "textView2 onClick...");
if (bookManager != null) {
try {
List<Book> list = bookManager.getBookList();
Log.e(TAG, "getBookList:" + list);
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "bookManager is null...");
}
}
});
textView3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "textView3 onClick...");
Book book = new Book("添加的书");
try {
if (bookManager != null) {
Log.e(TAG, "addBook...");
bookManager.addBook(book);
} else {
Log.e(TAG, "bookManager is null ---");
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected...");
bookManager = BookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "onServiceDisconnected...");
bookManager = null;
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}
在这个Activity中有三个TextView,其中textView1用于绑定服务端,textView2用于获取服务端书籍列表,textView3用于向服务端书籍列表中添加书。
注意,我写的demo是在客户端app中使用textView1绑定服务端的Service,如果我们手动把服务端app从后台列表中清除了,那么客户端app也就没法与服务端app进行数据通信了。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-----------------------last line for now----------------------