Android AIDL IPC机制详解

Introduction

由于每个应用程序都运行在自己的进程空间,并且可以从应用程序UI运行另一个服务进程,而且经常会在不同的进程间传递对象。在Android平台,一个进程通常不能访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的通过进程边界。通过代码来实现这个数据传输过程是冗长乏味的,Android提供了AIDL工具来处理这项工作。

AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。AIDL IPC机制是面向接口的,像COMCorba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。

AIDL适用场景

官方文档特别提醒我们何时使用AIDL是必要的:只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。具体的不同场景下的AIDL设计规则如下:

如果不需要进行不同应用程序间的并发通信(IPC)通过实现一个Binder对象来创建接口;或者你想进行IPC,但不需要处理多线程的,则使用Messenger对象来创建接口。无论如何,在使用AIDL前,必须要理解如何绑定service——bindService

AIDL开发步骤

AIDL接口文件,和普通的接口内容没有什么特别,只是它的扩展名为.aidl,保存在src目录下。如果其他应用程序需要IPC,则那些应用程序的src也要带有这个文件。Android SDK tools就会在gen目录自动生成一个IBinder接口文件。service必须适当地实现这个IBinder接口。那么客户端程序就能绑定这个service并在IPC时从IBinder调用方法。每个aidl文件只能定义一个接口,而且只能是接口的声明和方法的声明。

换言之,AIDL文件的作用即是在

1 创建.aidl文件

任何需要跨进程调用的方法都需要声明在涉及的每一个服务或者进程中。
     AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。其中对于Java编程语言的基本数据类型 (int, long, char, boolean),StringCharSequence,集合接口类型ListMap,不需要import 语句。而如果需要在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。AIDL允许传递实现Parcelable接口的类,需要import。 需要特别注意的是,对于非基本数据类型,也不是StringCharSequence类型的,需要有方向指示,包括inoutinoutin表示由客户端设置,out表示由服务端设置,inout是两者均可设置。AIDL只支持接口方法,不能公开static变量。

例如

l AIDLService .aidl: 这个AIDL用于声明需要在客户端中调用的服务中定义的方法。

注意,在某个aidl文件中可以调用其他aidl文件。

package com.kevin.android.demos.binder.aidl;  
import com.kevin.android.demos.binder.aidl.AIDLActivity;
interface AIDLService {   
    void registerTestCall(AIDLActivity cb);   
    void invokCallBack();
}  


l AIDLActivity.aidl:这个aidl主要声明在服务中所需要调用的客户端的方法。

package com.kevin.android.demos.binder.aidl;  

import com.kevin.android.demos.binder.aidl.Rect1;

interface AIDLActivity {   

    void performAction(in Rect1 rect);   

}  

l Rect1.aidl:这个aidl主要声明服务与客户端之间公用的数据结构。

package com.kevin.android.demos.binder.aidl; 

parcelable Rect1;

2 实现服务端接口

创建一个类实现刚才那个aidl的接口:

/*****************Copyright (C), 2010-2015, FORYOU Tech. Co., Ltd.********************/
package com.kevin.android.demos.binder;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.kevin.android.demos.binder.aidl.AIDLActivity;
import com.kevin.android.demos.binder.aidl.AIDLService;
import com.kevin.android.demos.binder.aidl.Rect1;
public class AIDLTestService extends Service {
private AIDLActivity callback;
private void Log(String str) {
Log.d(Constant.TAG, "------ " + str + "------");
}
@Override
public void onCreate() {
Log("service create");
}
@Override
public void onStart(Intent intent, int startId) {
Log("service start id=" + startId);
}
@Override
public IBinder onBind(Intent t) {
Log("service on bind");
return mBinder;
}
@Override
public void onDestroy() {
Log("service on destroy");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log("service on unbind");
return super.onUnbind(intent);
}
public void onRebind(Intent intent) {
Log("service on rebind");
super.onRebind(intent);
}
private final AIDLService.Stub mBinder = new AIDLService.Stub() {
@Override
public void invokCallBack() throws RemoteException {
Log("AIDLService.invokCallBack");
Rect1 rect = new Rect1();
rect.bottom=-1;
rect.left=-1;
rect.right=1;
rect.top=1;
callback.performAction(rect);
}
@Override
public void registerTestCall(AIDLActivity cb) throws RemoteException {
Log("AIDLService.registerTestCall");
callback = cb;
}
};
}


这里会看到有一个名为AIDLService.Stub类,查看aidl文件生成的Java文件源代码就能发现有这么一段代码:

/** Local-side IPC implementation stub class. */ 

public static abstract class Stub extends android.os.Binder implements com.kevin.android.demos.binder.aidl.AIDLService

原来Stub类就是继承于Binder类,也就是说RemoteService类和普通的Service类没什么不同,只是所返回的IBinder对象比较特别,是一个实现了AIDL接口的Binder

数据Bean实现

接下来就是关于所传递的数据Bean——Rect1类,是一个序列化的类,这里使用Parcelable 接口来序列化,是Android提供的一个比Serializable 效率更高的序列化类。

Parcelable需要实现三个函数:
    1) void writeToParcel(Parcel dest, int flags) 将需要序列化存储的数据写入外部提供的Parcel对象dest。读取Parcel数据的次序要和这里的write次序一致,否则可能会读错数据。
    2) describeContents() :简单地返回0即可。
    3) static final Parcelable.Creator对象CREATOR  这个CREATOR命名是固定的,而它对应的接口有两个方法:

l createFromParcel(Parcel source) 实现从source创建出JavaBean实例的功能

l newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话(return new T[size])即可。估计本方法是供外部类反序列化本类数组使用。

/*****************Copyright (C), 2010-2015, FORYOU Tech. Co., Ltd.********************/
package com.kevin.android.demos.binder.aidl;
import android.os.Parcel;
import android.os.Parcelable;
 
public class Rect1 implements Parcelable {
public int left;
public int top;
public int right;
public int bottom;
public static final Parcelable.Creator<Rect1> CREATOR = new Parcelable.Creator<Rect1>() {
public Rect1 createFromParcel(Parcel in) {
return new Rect1(in);
}
public Rect1[] newArray(int size) {
return new Rect1[size];
}
};
public Rect1() {
}
private Rect1(Parcel in) {
readFromParcel(in);
}
public void readFromParcel(Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
bottom = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(left);
out.writeInt(top);
out.writeInt(right);
out.writeInt(bottom);
}
}


 对于实现AIDL接口,官方还提醒我们:

    1. 调用者是不能保证在主线程执行的,所以从一调用的开始就需要考虑多线程处理,以及确保线程安全;

2. IPC调用是同步的。如果你知道一个IPC服务需要超过几毫秒的时间才能完成地话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应,这种情况应该考虑单独开启一个线程来处理。
    3. 抛出的异常是不能返回给调用者(跨进程抛异常处理是不可取的)。

3 客户端获取接口

客户端如何获取AIDL接口呢?通过AIDLService .Stub.asInterface(service)来得到IMyService对象:

/*****************Copyright (C), 2010-2015, FORYOU Tech. Co., Ltd.********************/
package com.kevin.android.demos.binder;
import java.text.MessageFormat;
import android.app.Activity;
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.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import com.kevin.android.demos.binder.aidl.AIDLActivity;
import com.kevin.android.demos.binder.aidl.AIDLService;
import com.kevin.android.demos.binder.aidl.Rect1;
public class AIDLTestActivity extends Activity {
private Button btnOk;
private Button btnCancel;
private Button btnCallBack;
private void Log(String str) {
Log.d(Constant.TAG, "------ " + str + "------");
}
 
/*Activity中也要实现一个AIDL接口,本质上也为一个bind类*/
private AIDLActivity.Stub mCallback = new AIDLActivity.Stub() {
@Override
public void performAction(Rect1 rect) throws RemoteException {
Log("AIDLActivity.performAction");
String str = MessageFormat.format(
"rect[bottom={0},top={1},left={2},right={3}]", rect.bottom,
rect.top, rect.left, rect.right);
Toast.makeText(AIDLTestActivity.this,
"this toast is called from service\n" + str, 1).show();
}
};
/*以下是获取到远端的Service连接的方法,声明一个匿名类*/
AIDLService mService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log("connect service");
mService = AIDLService.Stub.asInterface(service);
try {
mService.registerTestCall(mCallback);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
Log("disconnect service");
mService = null;
}
};
@Override
public void onCreate(Bundle icicle) {
Log("AIDLTestActivity.onCreate");
super.onCreate(icicle);
setContentView(R.layout.aidl_activity);
btnOk = (Button) findViewById(R.id.btn_ok);
btnCancel = (Button) findViewById(R.id.btn_cancel);
btnCallBack = (Button) findViewById(R.id.btn_call_back);
btnOk.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log("AIDLTestActivity.btnOk");
Bundle args = new Bundle();
Intent intent = new Intent();
intent.setAction("com.kevin.android.demos.binder.AIDLTestService");
intent.putExtras(args);
/*Activity中绑定服务的方式还是类似传统中的*/
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
startService(intent);
}
});
btnCancel.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log("AIDLTestActivity.btnCancel");
unbindService(mConnection);
// stopService(intent);
}
});
btnCallBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log("AIDLTestActivity.btnCallBack");
try {
mService.invokCallBack();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}


效果

 

Permission权限

如果ServiceAndroidManifest.xml中声明了全局的强制的访问权限,其他引用必须声明权限才能来startstopbind这个service.
     另外,service可以通过权限来保护她的IPC方法调用,通过调用checkCallingPermission(String)方法来确保可以执行这个操作。

 AndroidManifest.xmlService元素

<service android:name=".RemoteService" android:process=":remote"> 
        <intent-filter> 
                <action android:name="com.demo.IMyService" /> 
        </intent-filter> 
</service>

android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。

常见问题:

无法由aidl文件生成java类文件

java se的等级由1.5改为1.6

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值