一、AIDL介绍
AIDL(Android 接口定义语言)。用于定义客户端与服务IPC通信时都认可的编程接口。Android中一个进程无法访问另一个进程中的内存,但远程可以将其对象分解为操作系统能够识别的原语,并将对象编组成跨越边界的对象。编写执行这一编组操作的代码是繁琐的,因此Android会使用AIDL来处理。
注意:只有不同应用用IPC方式访问服务,且在服务中处理多线程时,才有必要使用AIDL。如果不需要跨进程,则可以通过实现Binder创建接口,如果不需要处理多线程,则使用Messenger类来实现接口。请参考绑定Service专题。
AIDL接口的调用是直接函数调用,不应该假设调用的线程。调用来自本地进程还是远程进程中的线程,实际情况也有所差异,具体:
1.来自本地进程的调用在发起调用的同一线程内执行。因此,只有本地线程访问服务时,才能完全控制哪些线程在服务中执行(但这种情况,不应使用AIDL,而应扩展Binder类创建接口)。
2.来自远程进程的调用将由系统在自有进程内部维护的线程池进行分派。由于存在多线程的并发调用,AIDL接口的实现必须是完全线程安全实现。
3.oneway 关键字用于修改远程调用的行为。使用该关键字时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自Binder线程池的常规调用进行接收的。如果oneway用于本地调用,则不会有任何影响,调用仍是同步调用。
二、定义AIDL接口
使用java语言在.aidl文件中定义ADIL接口,然后将它保存在托管服务的应用以及任何其他绑定到服务的应用的源代码内(src/目录)。
每个包含.aidl文件的应用,AndroidSDk工具都会生成一个基于该.aidl文件的IBinder接口,并将其保存在项目的/gen目录中。服务必须实现IBinder接口。然后客户端应用便可绑定到该服务,并调用IBinder中的方法执行IPC。
步骤:
1.创建.aidl文件:
此文件定义带有方法签名的编程接口。
默认情况下,AIDL支持下列数据类型:
Jva语言中的所有原语类型(int、long、char、boolean等)、
String、CharSequence、
List(其中元素必须是前面支持的数据类型、其他AIDL生成的接口或自己声明的可打包类型;可将List作为通用类,如List<String>,另一端实际接收的具体类始终是ArrayList,但生成的方法使用的是List接口)、
Map(其中元素必须是前面支持的数据类型、其他AIDL生成的接口或自己声明的可打包类型;不支持通用Map,如果Map<String, String>的形式。另一端实际接收的具体类始终是HashMap,但生成的方法使用的是Map接口)
定义接口时,请注意:
1)方法可带0个或多个参数,有或无返回值。
2)所有非原语参数都需要指示数据走向的方向标记。可以使in、out或inout。原语默认为in,不能是其他方向。
注意:应将方向限定为真正需要的方向,因为编组参数的开销极大。
3).aidl文件中包括的所有代码注释都包含在生成的IBinder接口中(import和package语句之前的注释除外)
4)只支持方法;不能公开AIDL中的静态字段。
示例:
// IRemoteService.aidl
package com.example.android;
// 使用import语句来声明任何非默认类型
/** 服务接口示例 */
interface IRemoteService {
/** 请求服务进程id. */
int getPid();
/** 演示可以用于AIDL中参数和返回值的基本类型
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
将.aidl文件保存到项目src/目录中,SDK工具会在项目gen/目录中生成IBinder接口文件,文件名与.aidl文件一致,只是使用了.java扩展名。(本例:IRemoteService.java)
如果使用Android Studio,增量编译会立即生成Binder类。若不是AS,gradle工具会在您下一次开发应用时生成Binder类。Android Studio生成的接口文件在app\build\generated\source\aidl\debug中
2.实现接口:
SDK工具基于.aidl文件,使用Java编程语言生成一个接口。此接口具有一个名为Stub的内部抽象类,是其父接口(Inteface.Stub)的抽象实现,用于扩展Binder类并实现AIDL接口中的方法。必须扩展Stub类并实现方法。
注:Stub还定义了几个辅助程序,最需要关注的是asInterface(),该方法带IBinder(通常是传递给客户端onServiceConnected()回调方法的参数)并返回存根接口实例。
如需实现.aidl生成的接口,请扩展生成的Binder接口并实现从.aidl文件集成的方法。
示例(IRemoteService):
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
mBinder是Stub类的一个实例,用于定义服务的RPC接口。接下来,将向客户端公开该实例。
实现AIDL接口时应注意遵守以下规则:
1)处理好多线程,并将服务正确的编译为线程安全服务;
2)默认情况下,RPC调用是同步调用。如果明知服务完成请求的时间不止几毫秒,就不该从Acticity的主线程调用服务,要在子线程中进行,防止ANR。
3)引发的任何异常都不会传给调用者。
3.向客户端公开接口:
实现Service并重写onBind()以返回Stub类的实现。
注意:在AIDL接口首次发布后对其进行的任何更改都必须保持向后兼容性,以避免其他应用对服务的使用。也就是说因为必须将.aidl文件复制到其他应用,才能让这些应用访问服务的接口,因此必须保留对原接口的支持。
实现接口后,要向客户端公开接口,以便客户端进行绑定。要为服务公开接口,请扩展Service并实现onBind(),返回一个类实例,这个类实现了生成的Stub。
RemoteService示例:
public class RemoteService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
// 返回接口
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
}
当客户端调用bindService()以连接此服务时,客户端的onServiceConnected()将会接收服务的onBind()方法返回的mBinder实例。
客户端还必须具有对interface类的访问权限,因此如果客户端和服务在不同的应用内,客户端的应用/src目录内必须包含.aidl文件(它生成android.os.Binder接口——为客户端提供对AIDL方法的访问权限)的副本。
当客户端在onServiceConnected()回调收到Ibinder时,它必须调用YourServiceInterface.Stub.asInterface(service)以将返回的参数转换成YourServiceInterface类型。如:
IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// 与服务建立连接时调用
public void onServiceConnected(ComponentName className, IBinder service) {
// 转换成需要的服务类型
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
// 连接意外断开时调用
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
在实践时,发现几个问题:
1)5.0的机器,在使用隐式Intent时,不仅要指定Action还要指定包Intent.setPackage()。
2)aidl在客户端一定要与服务端一致,包括包名以及类名,否则将无法转换。
3)有的机器远程服务无法使用,比如魅族,具体咋办,请百度。
三、通过IPC传递对象
通过IPC接口把某个类从一个进程发送到另一个进程,必须保证该类的代码对IPC通道的另一端可用,并且该类必须实现Parcelable接口,因为Android系统可通过它将对象分解成可编组到各进程的原语。
创建支持Parcelable协议的类,操作:
1.让类实现Parcelable接口。
2.实现writeToParcel,它会获取对象的当前状态并将其写入Parcel。
3.为类添加一个名为CREATOR的静态字段,该字段是一个实现Parcelable.Creator接口的对象。
4.最后,创建一个声明可打包类的.aidl文件(按照下面的例子Rect.aidl所示)
如果使用的是自定义编译进程,切勿在编译中添加.aidl文件。此文件与C语言中的头文件类似,并未编译。
Rect.aidl文件可创建一个可打包的Rect类:
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
Rect类:
import android.os.Parcel;
import android.os.Parcelable;
public final class Rect implements Parcelable {
public int left;
public int top;
public int right;
public int bottom;
public static final Parcelable.Creator<Rect> CREATOR =
new Parcelable.Creator<Rect>() {
public Rect createFromParcel(Parcel in) {
return new Rect(in);
}
public Rect[] newArray(int size) {
return new Rect[size];
}
};
public Rect() {
}
private Rect(Parcel in) {
readFromParcel(in);
}
public void writeToParcel(Parcel out) {
out.writeInt(left);
out.writeInt(top);
out.writeInt(right);
out.writeInt(bottom);
}
public void readFromParcel(Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
bottom = in.readInt();
}
}
警告:别忘记从其他进程接收数据的安全影响。本例中,Rect从Parcel读取四个数字,但要由程序员自己来确保无论调用方目的为何,这些数字都在可接受值范围内。
四、调用IPC方法
调用步骤如下:
1.在项目src/目录中加入.aidl文件;
2.声明一个IBinder接口实例(基于AIDL);
3.实现ServiceConnection;
4.调用Context.bindService,并传入ServiceConnection实例;
5.在ServiceConnection的onServiceConnected()实现中,参数service即为IBinder实例。调用InterfaceName.Stub.asInterface(service)将service转换成需要的接口名称;
6.调用接口中定义的方法,但是要记得捕获DeadObjectException异常(RemoteException子类),是连接中断时引发的异常;这是远程方法引起的唯一异常;
7.断开连接使用Context.unbindService().
说明:
对象是跨进程技术的引用。(不懂)
可以将匿名对象作为方法参数传送。
使用示例:
public static class Binding extends Activity {
/** 调用服务的主要接口 */
IRemoteService mService = null;
/** 另一个接口. */
ISecondary mSecondaryService = null;
Button mKillButton;
TextView mCallbackText;
private boolean mIsBound;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.remote_service_binding);
Button button = (Button)findViewById(R.id.bind);
button.setOnClickListener(mBindListener);
button = (Button)findViewById(R.id.unbind);
button.setOnClickListener(mUnbindListener);
mKillButton = (Button)findViewById(R.id.kill);
mKillButton.setOnClickListener(mKillListener);
mKillButton.setEnabled(false);
mCallbackText = (TextView)findViewById(R.id.callback);
mCallbackText.setText("Not attached.");
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// 服务连接建立时调用,参数service为服务示例。
// 即将通过IDL接口与服务交互,因此要将service转换为客户端的表现形式
mService = IRemoteService.Stub.asInterface(service);
mKillButton.setEnabled(true); mCallbackText.setText("Attached.");
// 连接之后监视服务
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
// 服务以外断开时调用
mService = null;
mKillButton.setEnabled(false);
mCallbackText.setText("Disconnected.");
// 通知用户断开连接
Toast.makeText(Binding.this, R.string.remote_service_disconnected, Toast.LENGTH_SHORT).show();
}
};
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mSecondaryService = ISecondary.Stub.asInterface(service);
mKillButton.setEnabled(true);
}
public void onServiceDisconnected(ComponentName className) {
mSecondaryService = null;
mKillButton.setEnabled(false);
}
};
private OnClickListener
mBindListener = new OnClickListener() {
public void onClick(View v) {
// 建立两个连接,通过接口名称绑定。允许通过实现相同的接口来替换远程服务对应的接口。
Intent intent = new Intent(Binding.this, RemoteService.class);
intent.setAction(IRemoteService.class.getName());
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
intent.setAction(ISecondary.class.getName());
bindService(intent, mSecondaryConnection, Context.BIND_AUTO_CREATE);
mIsBound = true; mCallbackText.setText("Binding.");
}
};
private OnClickListener mUnbindListener = new OnClickListener() {
public void onClick(View v) {
if (mIsBound) {
// 如果已经连接服务,并且注册回调,在解绑时,要解注册
if (mService != null) {
try {
mService.unregisterCallback(mCallback);
} catch (RemoteException e) {
}
}
// unbind服务
unbindService(mConnection);
unbindService(mSecondaryConnection);
mKillButton.setEnabled(false);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
};
private OnClickListener mKillListener = new OnClickListener() {
public void onClick(View v) {
// 杀死持有服务的进程,要知道PID。服务有个调用可以获取PID
if (mSecondaryService != null) {
try {
int pid = mSecondaryService.getPid();
// 尽管此API允许根据PID来杀死任何进程,但内核仍会对实际能杀死的PID施加限制。
// 通常只有应用所在进程以及该进程创建的其他进程,共享一个UID的包也能相互杀死
Process.killProcess(pid);
mCallbackText.setText("Killed service process.");
} catch (RemoteException ex) {
Toast.makeText(Binding.this, R.string.remote_call_failed, Toast.LENGTH_SHORT).show();
}
}
}
};
/**
* 这个实现用于从远程服务接收回调
*/
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
/**
* 由远程服务定期调用通知新值。由于在每个进程的线程池调度IPC,
* 因此回调不一定会在主线程调用,要使用Handler来更新UI。
*/
public void valueChanged(int value) {
mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
}
};
private static final int BUMP_MSG = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BUMP_MSG:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
};
}