AIDL和Service实现两进程通信:
AIDL (Android Interface Definition Language) 是一种接口定义语言,用于生成代码允许Android设备上的两个进程间进程通信(IPC).
如果你需要编写一个进程(比如Activity) 访问另一个进程(比如Services)的对象的方法代码,你可以使用AIDL自动生成代码而不用自己配置大量的参数.
AIDL IPC基于接口机制,类似COM,Corba,且更加轻量化.它使用一个代理来在客户和实现间传递值.
使用AIDL 实现进程通信可分为五个步骤:
一.创建 ISimpleRemoteService.aidl 文件
package com.teleca.ServiceSample;
interface ISimpleRemoteService
{
void addCommands(String cmd);
boolean isBusy();
}
利用aidl.exe生成接口文件.若你的IDE安装了ADT,将会在gen目录或src相应包中自动根据描述文件生成同名接口文件.否则请手动:
命令行:
adil path\SomeService.adil <CR>
注意:
1.自定义类在aidl描述文件中,即便在同一个包中,也要显式import.
2.在aidl文件中所有非Java原始类型参数必须加上标记:in, out, inout.
3.Java 原始类型默认为in,且不能为其它.
4.Java 原始类型包括为java.lang, java,util包中包括的类.
5.接口名同aidl文件名.
6.接口前不用加访问权限修饰符public ,private, protected等,也不能用final ,static.
接口文件分析:
接口中生成一个Stub的抽象类,里面包括aidl定义的方法.还包括一些其它辅助方法.值得关注的是asInterface(IBinder iBinder),它返回接口的实例.
二.实现接口
接口的实现需继承接口.Stub.并实现Stub类的方法.
下面给出一个使用匿名方式实现的例子.
private final ISimpleRemoteService.Stub binder=new ISimpleRemoteService.Stub()
{
public void addCommands(String cmd)
{
SimpleRemoteService.this.addCmd(cmd);
}
public boolean isBusy()
{
return SimpleRemoteService.this.isBusy();
}
};
注意:
1. 没有异常会正常返回
2.RPC通常比较耗时且是异步的,因此应该在线程中调用RPC服务.
3.只支持方法,不支持静态字段.
三.暴露接口给客户
客户要服务,当然要知道在哪有服务.通常一台服务器可能提供不止一个服务.我们这里只有一个服务.
暴露服务必须继承Service.并实现onBind()方法.
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return binder;
}
注意我们这里可以根据intent来返回不同的服务。
四.使用打包传送参数 如果在接口定义文件中使用了默认允许的类型(基本类型,String,CharSequence,List和Map),这些类型都将被自动处理。如果使用了其他的类型,那么该类型必须实现打包功能(Parcelable接口),而且需要创建一个aidl文件声明它是可打包类。可分为如下5个步骤:
4.1 实现 Parcelable接口
4.2 实现 public void writeToParcel(Parcel out) 方法
4.3 实现 public void readFromParcel(Parcel in) 方法
4.4 添加一个静态字段 CREATOR 到实现 Parcelable.Creator 接口的类中
4.5 创建一个aidl文件声明你的可打包的类
示例:
Rect.java文件
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.aidl文件
package android.graphics;
parcelable Rect;
五.调用IPC方法
调用IPC方法还有6个步骤:
5.1 声明aidl定义的接口类型引用
5.2 实现 ServiceConnection
5.3 调用 Context.bindService(),传入 ServiceConnection 的实现
5.4 在你的 ServiceConnection.onServiceConnected(),你将得到一个 IBinder 实例(service).
调用YourInterfaceName.Stub.asInterface((IBinder)service)强制转换 YourInterface 类型.
5.5 调用接口定义的方法.你应该始终小心 DeadObjectException 异常,当连接不成功或中断它就会抛出,这也是远程对象唯一的一个异常.
5.6 断开连接,调用 Context.unbindService().
注意:现在关于如何让AIDL定义生成的接口在客服端可见并没有发现完美的方法。
现在我的做法是把AIDL定义生成的接口,拷贝到客服端的工程中。并且让客服端打包时把AIDL定义生成的接口也打包进去。
我尝试过只用AIDL定义生成的接口来编译客户端程序,并不它打包进去。但是客服端根本无法找到服务器端AIDL文件定义生成的接口。
实例1:
服务器端:
文件ISimpleRemoteService.aidl
package com.teleca.ServiceSample;
interface ISimpleRemoteService
{
void addCommands(String cmd);
boolean isBusy();
}
文件SimpleRemoteService.java
package com.teleca.ServiceSample;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class SimpleRemoteService extends Service implements Runnable{
private final ISimpleRemoteService.Stub binder=new ISimpleRemoteService.Stub()
{
public void addCommands(String cmd)
{
SimpleRemoteService.this.addCmd(cmd);
}
public boolean isBusy()
{
return SimpleRemoteService.this.isBusy();
}
};
String tag="hubin";
@Override
public void onCreate() {
Log.i(tag,"oncreate");
StartThread();
}
@Override
public void onDestroy() {
blRun=false;
Log.i(tag,"OnDestory");
}
public void StartThread()
{
if(blRun==false)
{
Thread t=new Thread(this);
t.start();
}
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return binder;
}
boolean blRun=false;
final static int kSleepTime=5;
final String cmdPool[]=new String[10];
int cmdStartCursor=-1;
int cmdEndCursor=-1;
public void run()
{
blRun=true;
while(blRun)
{
if(cmdStartCursor!=cmdEndCursor)
{
cmdStartCursor=(cmdStartCursor+1)%cmdPool.length;
Log.i(tag, "run:"+cmdPool[cmdStartCursor]);
}
try{
Thread.sleep(kSleepTime);
}catch(InterruptedException e)
{
Log.e(tag, "InterruptedException", e);
}
}
}
void addCmd(String cmd)
{
if((cmdEndCursor+1)%cmdPool.length!=cmdStartCursor)
{
cmdEndCursor++;
cmdEndCursor=cmdEndCursor%cmdPool.length;
cmdPool[cmdEndCursor]=cmd;
}
}
boolean isBusy()
{
return cmdEndCursor!=cmdStartCursor;
}
}
文件AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.teleca.ServiceSample"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<service android:name="SimpleRemoteService" android:process=":remote">
<intent-filter>
<action android:name="com.teleca.action.STARTSERVICE"/>
</intent-filter>
</service>
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>
客服端Hello.java
package com.teleca;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
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 com.teleca.ServiceSample.*;
public class Hello extends Activity {
final static String tag="hubin";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button1 = (Button) findViewById(R.id.Button01);
OnClickListener listener1 = new OnClickListener() {
@Override
public void onClick(View v) {
doBindService();
}
};
button1.setOnClickListener(listener1);
Button button2 = (Button) findViewById(R.id.Button02);
OnClickListener listener2 = new OnClickListener() {
@Override
public void onClick(View v) {
doUnbindService();
}
};
button2.setOnClickListener(listener2);
Button button3 = (Button) findViewById(R.id.Button03);
OnClickListener listener3 = new OnClickListener() {
@Override
public void onClick(View v) {
if(mBoundService==null)
{
Log.i(tag,"the Service has not bind!Please bind the service first");
return;
}
String cmd="Hello:"+System.currentTimeMillis()%100;
try {
mBoundService.addCommands(cmd);
boolean res=mBoundService.isBusy();
Log.i(tag,"isBusy:"+res);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
button3.setOnClickListener(listener3);
}
private ISimpleRemoteService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder iservice) {
mBoundService = ISimpleRemoteService.Stub.asInterface(iservice);
}
public void onServiceDisconnected(ComponentName className) {
mBoundService = null;
}
};
boolean mIsBound=true;
void doBindService() {
Intent intent=new Intent("com.teleca.action.STARTSERVICE");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
}
评论