在Android中, 每个应用程序都可以有自己的进程。 在写UI应用的时候, 经常要用到Service。 在不同的进程中,怎样传递对象呢? 在Android平台中不允许跨进程内存共享。 因此传递对象, 只能把对象拆分成操作系统能理解的简单形式,所以,他们需要把对象拆分成操作系统能理解的简单形式,以便伪装成对象跨越边界访问。
写"marshall"的代码是繁琐而枯燥的工作,好在AIDL提供了比较实在的工具以便让它更有趣。
AIDL (Android Interface Definition Language )是Android接口描述语言,属于Eclipse
plugin 管理)。通过Eclipse plugin ,AIDL可以自动产生java并编译,这些都工具位于tools/
目录。
AIDL语法很简单,可以用来声明一个带一个或多个方法的接口,也可以传递参数和返回值。 这些参数和返回值可以是任意类型,甚至在其他AIDL生成的接口上也可以这样。 然而, 需要注意的重点 你必须导入所有的non-built-in类型, 即使他们定义的package和你的interface一样也要导入。 AIDL支持的数据类型如下:
import
再次声明。 import
声明):
import
声明,所以import声明是必须的。import
也是必须的。下面是AIDL的基本语法:
// AIDL 文件, 文件名SomeClass.aidl // 文件可以有注释 // 在import和package声明以前的注释将被忽略 // 但是可以在接口/方法/属性之前添加注释 // package位置声明 package com.android.sample; // 需要声明的类 // import引入声明 import com.android.sample.IAtmService; // 接口定义 interface IBankAccountService { // 可以有0到多个参数,可以有0到1个返回值 int getAccountBalance(); void setOwnerNames(in List<String> names); // 方法中的参数可以在其他AIDL中被定义 BankAccount createAccount(in String name, int startingDeposit, in IAtmService atmService); // 所有非non-Java原始参数(e.g., int, bool, etc) 都必须 // 标明参数方向. 有效的参数方向 in, out, inout. (Java原始参数默认是in形式参数,并且没有其他方式)。 // 限制参数方向是根据实际需要来定,要注意的是不注明参数方向将会耗费大量资源来真理匹配该参数的方向。 int getCustomerList(in String branch, out String[] customerList); }
AIDL 生成接口的名称与.aidl文件名称是一样的。 如果使用Eclipse插件, AIDL被将会自动创建 (而用不着先运行AIDL工具然后再创建项目)。 如果你还准备通过原始方式来搞,那你只能运行AIDL工具了。
编译器会根据AIDL接口, 产生一个名为Stub的内部抽象类,它声明的所有方法都将出现在.aidl文件中。 Stub类包含一部分有用的的方法,比如asInterface(),它执行一个IBinder(在 applicationContext.bindService()执行成功后传给客户端onServiceConnected()方法), 并且返回一个接口实例以便调用IPC方法。详情参见Calling an IPC Method。
接口的大概步骤就是扩展YourInterface.Stub和实现方法。( 你可以创建一个aidl文件并实现stub方法而不用绑定-AndRoid创建过程在java文件之前会处理aidl文件。 )
下面是一个实现调用接口IRemoteService的例子,使用匿名实例公开一个简单的方法gerPid():
//在同一个项目中不需要import IRemoteService。 private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){ public int getPid(){ return Process.myPid(); } }
关于实现接口方法的几个规则:
既然你已经实现接口,现在需要对客户端开放接口的访问。 这就是所谓的 "发布服务" 发布一个服务,就是继承 Service并使用 Service.onBind(Intent)方法 返回到实例的接口实现。 下面这个代码片段是一个服务允许客户端访问的IRemoteService接口。
public class RemoteService extends Service { ... @Override public IBinder onBind(Intent intent) { // 选择返回接口,如果只有一个接口,那么直接放回即可 if (IRemoteService.class.getName().equals(intent.getAction())) { return mBinder; } if (ISecondary.class.getName().equals(intent.getAction())) { return mSecondaryBinder; } return null; } /** * 通过IDL定义IRemoteInterface */ private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { public void registerCallback(IRemoteServiceCallback cb) { if (cb != null) mCallbacks.register(cb); } public void unregisterCallback(IRemoteServiceCallback cb) { if (cb != null) mCallbacks.unregister(cb); } }; /** * secondary接口 */ private final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() { public int getPid() { return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { } }; }
如果你有类需要通过AIDL接口从一个进程发送到另一个,你可以使用Parcelables的参数值传递。 你必须确保类代码可以被IPC接收端所使用。通常这意味着一开始你就要和service进行通讯。
要支持Parcelable协议需要注意以下五个部分:
public void writeToParcel(Parcel out)
方法把当前对象打包。public void readFromParcel(Parcel in)
方法从包中读取信息到对象中。CREATOR
,该象可以实现Parcelable.Creator接口AIDL将使用代码中生成的这些方法和成员来伪装或解读对象。
下面这个例子说明了Rect 类如何实现了Parcelable协议。
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; // 定义Rect,AIDL才能找到并且实现parcelable接口协议。 parcelable Rect;
Rect类中的伪装是相当简单的。仔细查看Parcel类中的其他方法,,你会看到其他各种值你都可以写进Parcel。
警告: 不要忽视从其他进程接收数据时的安全性考虑。 在本例中,rect将从parcel中读四个数字,而你的工作则是确保这些都在可接受的值 得范围内而不管调用者想要干什么。Security and Permissions in Android 中有更多关于如何 确保应用程序安全的信息。
调用类调用远程接口的步骤:
YourInterfaceName.Stub.asInterface((IBinder)service)
将参数转换为YourInterface 类型。调用IPC服务需要注意几点:
下面的代码展示了在ApiDemos项目从远程Activity例子中调用AIDL创建Service的过程。
public class RemoteServiceBinding extends Activity { /** 初始化主要接口*/ IRemoteService mService = null; /** 其他接口 */ ISecondary mSecondaryService = null; Button mKillButton; TextView mCallbackText; private boolean mIsBound; /** * 标准的activity初始化,设置UI */ @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."); } /** * 类与main函数交互 */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // 连接建立时被调用的服务,客户端可以通过IDL接口与该Service通信。 mService = IRemoteService.Stub.asInterface(service); mKillButton.setEnabled(true); mCallbackText.setText("Attached."); // 监视服务 try { mService.registerCallback(mCallback); } catch (RemoteException e) { // 异常抛出,服务崩溃之前可以做的一些事情; } // 回显信息 Toast.makeText(RemoteServiceBinding.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // 服务异常断开连接 -- 比如进程崩溃 mService = null; mKillButton.setEnabled(false); mCallbackText.setText("Disconnected."); // 回显信息 Toast.makeText(RemoteServiceBinding.this, R.string.remote_service_disconnected, Toast.LENGTH_SHORT).show(); } }; /** * 类与secondary接口的交互. */ private ServiceConnection mSecondaryConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // 连接一个Secondary与连接其他接口的方法一样 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) { // 界面和代码动作绑定之后,允许其他应用通过远程服务的方式调用该组件,已完成相同的重复的工作 bindService(new Intent(IRemoteService.class.getName()), mConnection, Context.BIND_AUTO_CREATE); bindService(new Intent(ISecondary.class.getName()), 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) { // 异常信息处理,不过一般情况没什么需要做的 } } //断开连接 unbindService(mConnection); unbindService(mSecondaryConnection); mKillButton.setEnabled(false); mIsBound = false; mCallbackText.setText("Unbinding."); } } }; private OnClickListener mKillListener = new OnClickListener() { public void onClick(View v) { // 需要获知进程的PID才能结束本地服务, // 好在我们的服务可以方便的返回一些有用的信息 if (mSecondaryService != null) { try { int pid = mSecondaryService.getPid(); // 使用这个API可以结束任何已知PID的进程 // 比如只运行一个进程的应用,在运行过程中,会产生一些附加进程,这些进程会共享一个UID, // 结束这个UID,那么其他的附加进程也会被一起结束 Process.killProcess(pid); mCallbackText.setText("Killed service process."); } catch (RemoteException ex) { // 进程结束失败,显示一个失败通知 Toast.makeText(RemoteServiceBinding.this, R.string.remote_call_failed, Toast.LENGTH_SHORT).show(); } } } }; // ---------------------------------------------------------------------- // 回调示例代码 // ---------------------------------------------------------------------- /** * 实现远程服务的回调 */ private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { /** * 被调用的远程服务定时返回新的值。 */ 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); } } }; }
树上蹭灰 2008-10-04