Android开发艺术探索学习-IPC之Binder(一)
2015-11-18 19:19
549 查看
1. Binder简介
1.1 What is Binder?
Android Dev Doc:Base class for a remotable object, the core part of a lightweight remote procedure
call mechanism defined by
This class is an implementation of IBinder that provides standard local implementation of such an object.
Binder是一个远程对象的基类,是一个轻量级远程通信机制的核心部分,通过IBinder定义。Binder类实现IBinder接口,并通过提供标准的本地接口对象实现IBinder。
我们以前也接触过一些IPC方式,比如:管道(pipe)、有名管道(FIFO)、信号(signal)、消息队列、共享内存、套接字(socket)等,但是Android系统并没有采取这些方式,而是使用Binder完成IPC。
1.2 why is Binder?
由于移动设备的特殊性,在IPC要尽量满足两个方面的要求:安全性高、传输性能高。
首先从传输性能上看:
socket传输效率低,开销大,因此主要还是用于跨网络的进程间通信。消息队列和管道采用的是存储转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
共享内存的方式虽然不用数据拷贝,但是控制使用起来很麻烦,大家如果有兴趣的话可以看下Android的匿名共享内存方法,其实是很复杂的,罗升阳的Android系统匿名共享内存Ashmem(Anonymous
Shared Memory)简要介绍和学习计划中有详细介绍。
表1 各种IPC方式数据拷贝次数
其次从安全性上看:
传统的IPC其实是没有任何安全措施,完全依赖上层协议来确保,其接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份。但是Binder会为发送方添加UID/PID身份(由系统添加),安全性高。
1.3 Binder的注意事项
有一点我们需要牢记在心:当我们的进程被“Kill”,那么就需要在该进程重启时重新创建一个Binder对象并[b]re-attach到该进程。例如,当你如果在Activity里面使用Binder,如果你的Activity未启动,那么它就随意可能会系统回收。当Activity重建时,你需要重建一个新的Binder对象并放在正确的地方。当然你也需要注意你的进程因其他原因而启动(比如收到广播),那么这种情况activity不会重建也就不会跑到创建Binder对象的代码了。[/b]
1.4 AIDL
如果大家有兴趣阅读Android源码的话,就会发现Android FrameWork层存在着很多Binder机制。因此理解Binder的工作原理是很重要的。为了能让咱们程序员能很简单地使用Binder,Android为我们提供了一个非常简单地使用Binder的工具,AIDL。那么如何使用AIDL呢?请参考Android官方文档:http://developer.android.com/guide/components/aidl.html。
这里主要分析下,AIDL文件自动生成的JAVA文件。aidl文件名为:IAidlCall.aidl,代码如下。
自动生成的IAidlCall.java,代码如下。
IAidlCall是一个接口,其继承于android.os.IInterface。所以AIDL自动生成的java文件都要继承该接口,该接口代码如下。
IAidlCall接口中声明了一个静态内部抽象类Stub,该类继承Binder类并实现IAidlCall接口。因此Stub其实就是一个Binder类。
在我们实际的使用AIDL过程中,客户端和服务端都要与Stub打交道。在服务端中,我们需要实现Stub类,代码如下。
在客户端中,我们需要调用Stub类中的asInterface方法来得到IAidlCall对象,代码如下。
接着我们看下asInterface方法中的代码,看完该方法我们就可以了解IAidlCall.java工作流程。
这里需要注意的是_data、_reply两个Parcel对象和transact方法。
_data是入参序列,如果调用的远程方法需要入参就通过_data的writeInt、writeLong等方法将参数写入_data中。
_reply是方法的返回值序列,如果调用的远程方法有返回值,就通过_reply的readInt、readLong等方法将参数读到_reply中。
这里最主要的方法是transact(intcode,
Parceldata, Parcelreply, intflags)方法。
1)第一个参数code代表的是方法的代码,例如getName()的方法code为,
static final int TRANSACTION_getName
= (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
知道code就能知道调用的是哪个方法。
2)第二个参数是指远程方法的入参。
3)第三个参数是指远程方法的返回值。
4)第四个参数是指额外操作标志,0代表的是普通的RPC,其他值代表的是one-way RPC。
因此我们,可以通过模仿写出transact方法所需要的参数,通过RemoteObject(Binder)调用其transact方法就可以完成远程方法的调用,最后会给出相应地例子。
Binder中transact方法实现代码如下。
![](http://img.blog.csdn.net/20151118191831666?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
这里有两点需要注意:
首先,当客户端发起远程请求的时候,由于当前进程会被挂起直到服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
通过以上介绍,可以大致了解了Binder的工作方式,后续还将继续深入了解,当然也不会太深,都是结合《Android开发艺术探索》和工作上所碰到的问题进行概述。
1.1 What is Binder?
Android Dev Doc:Base class for a remotable object, the core part of a lightweight remote procedure
call mechanism defined by
IBinder.
This class is an implementation of IBinder that provides standard local implementation of such an object.
Binder是一个远程对象的基类,是一个轻量级远程通信机制的核心部分,通过IBinder定义。Binder类实现IBinder接口,并通过提供标准的本地接口对象实现IBinder。
我们以前也接触过一些IPC方式,比如:管道(pipe)、有名管道(FIFO)、信号(signal)、消息队列、共享内存、套接字(socket)等,但是Android系统并没有采取这些方式,而是使用Binder完成IPC。
1.2 why is Binder?
由于移动设备的特殊性,在IPC要尽量满足两个方面的要求:安全性高、传输性能高。
首先从传输性能上看:
socket传输效率低,开销大,因此主要还是用于跨网络的进程间通信。消息队列和管道采用的是存储转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
共享内存的方式虽然不用数据拷贝,但是控制使用起来很麻烦,大家如果有兴趣的话可以看下Android的匿名共享内存方法,其实是很复杂的,罗升阳的Android系统匿名共享内存Ashmem(Anonymous
Shared Memory)简要介绍和学习计划中有详细介绍。
表1 各种IPC方式数据拷贝次数
IPC | 数据拷贝次数 |
共享内存 | 0 |
Binder | 1 |
Socket/管道/消息队列 | 2 |
传统的IPC其实是没有任何安全措施,完全依赖上层协议来确保,其接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份。但是Binder会为发送方添加UID/PID身份(由系统添加),安全性高。
1.3 Binder的注意事项
有一点我们需要牢记在心:当我们的进程被“Kill”,那么就需要在该进程重启时重新创建一个Binder对象并[b]re-attach到该进程。例如,当你如果在Activity里面使用Binder,如果你的Activity未启动,那么它就随意可能会系统回收。当Activity重建时,你需要重建一个新的Binder对象并放在正确的地方。当然你也需要注意你的进程因其他原因而启动(比如收到广播),那么这种情况activity不会重建也就不会跑到创建Binder对象的代码了。[/b]
1.4 AIDL
如果大家有兴趣阅读Android源码的话,就会发现Android FrameWork层存在着很多Binder机制。因此理解Binder的工作原理是很重要的。为了能让咱们程序员能很简单地使用Binder,Android为我们提供了一个非常简单地使用Binder的工具,AIDL。那么如何使用AIDL呢?请参考Android官方文档:http://developer.android.com/guide/components/aidl.html。
这里主要分析下,AIDL文件自动生成的JAVA文件。aidl文件名为:IAidlCall.aidl,代码如下。
package com.example.aidldemo; interface IAidlCall{ String getName(); }
自动生成的IAidlCall.java,代码如下。
/* * This file is auto-generated. DO NOT MODIFY. * Original file: /Users/chenjiawei/Documents/AndroidSpace/ClientDemo/src/com/example/aidldemo/IAidlCall.aidl */ package com.example.aidldemo; public interface IAidlCall extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.aidldemo.IAidlCall { // Binder的唯一标示,一般用当前Binder的类名来标示 private static final java.lang.String DESCRIPTOR = "com.example.aidldemo.IAidlCall"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.aidldemo.IAidlCall * interface, generating a proxy if needed. */ public static com.example.aidldemo.IAidlCall asInterface( android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.example.aidldemo.IAidlCall))) { return ((com.example.aidldemo.IAidlCall) iin); } return new com.example.aidldemo.IAidlCall.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_getName: { data.enforceInterface(DESCRIPTOR); java.lang.String _result = this.getName(); reply.writeNoException(); reply.writeString(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.example.aidldemo.IAidlCall { 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; } @Override public java.lang.String getName() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0); _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public java.lang.String getName() throws android.os.RemoteException; }
IAidlCall是一个接口,其继承于android.os.IInterface。所以AIDL自动生成的java文件都要继承该接口,该接口代码如下。
/** * Base class for Binder interfaces. When defining a new interface, * you must derive it from IInterface. */ public interface IInterface { /** * Retrieve the Binder object associated with this interface. * You must use this instead of a plain cast, so that proxy objects * can return the correct result. */ public IBinder asBinder(); }
IAidlCall接口中声明了一个静态内部抽象类Stub,该类继承Binder类并实现IAidlCall接口。因此Stub其实就是一个Binder类。
在我们实际的使用AIDL过程中,客户端和服务端都要与Stub打交道。在服务端中,我们需要实现Stub类,代码如下。
public class MyServiceImpl extends IAidlCall.Stub { @Override public String getName() throws RemoteException { return "Jack"; } }
在客户端中,我们需要调用Stub类中的asInterface方法来得到IAidlCall对象,代码如下。
private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub } @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub call = IAidlCall.Stub.asInterface(service); try { Toast.makeText(getApplicationContext(), call.getName(), Toast.LENGTH_LONG).show(); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } };
接着我们看下asInterface方法中的代码,看完该方法我们就可以了解IAidlCall.java工作流程。
/** * Cast an IBinder object into an com.example.aidldemo.IAidlCall * interface, generating a proxy if needed. */ public static com.example.aidldemo.IAidlCall asInterface(android.os.IBinder obj) { //首先判断IBinder是否为空,因为Binder可以“死亡”,前面的有提过 if ((obj == null)) { return null; } //这里queryLocalInterface方法得到IInterface对象,这是通过attachInterface赋值过去的 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); //如果iin不为空并且是IAidlCall实例则将其强转成IAidlCall对象并返回 if (((iin != null) && (iin instanceof com.example.aidldemo.IAidlCall))){ return ((com.example.aidldemo.IAidlCall) iin); } //如果lin为空或者不是IAidlCall实例,则返回Proxy对象 return new com.example.aidldemo.IAidlCall.Stub.Proxy(obj); }这里我们可以看到一个Proxy类,该类是Stub里的静态类且继承于IAidlCall,在Proxy类中有真正完成getName()方法实现的过程。实现getName方法的代码如下。
@Override public java.lang.String getName() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0); _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; }
这里需要注意的是_data、_reply两个Parcel对象和transact方法。
_data是入参序列,如果调用的远程方法需要入参就通过_data的writeInt、writeLong等方法将参数写入_data中。
_reply是方法的返回值序列,如果调用的远程方法有返回值,就通过_reply的readInt、readLong等方法将参数读到_reply中。
这里最主要的方法是transact(intcode,
Parceldata, Parcelreply, intflags)方法。
1)第一个参数code代表的是方法的代码,例如getName()的方法code为,
static final int TRANSACTION_getName
= (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
知道code就能知道调用的是哪个方法。
2)第二个参数是指远程方法的入参。
3)第三个参数是指远程方法的返回值。
4)第四个参数是指额外操作标志,0代表的是普通的RPC,其他值代表的是one-way RPC。
因此我们,可以通过模仿写出transact方法所需要的参数,通过RemoteObject(Binder)调用其transact方法就可以完成远程方法的调用,最后会给出相应地例子。
Binder中transact方法实现代码如下。
/** * Default implementation rewinds the parcels and calls onTransact. On * the remote side, transact calls into the binder to do the IPC. */ public final boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (false) Log.v("Binder", "Transact: " + code + " to " + this); if (data != null) { data.setDataPosition(0); } boolean r = onTransact(code, data, reply, flags); if (reply != null) { reply.setDataPosition(0); } return r; }从该代码中我们可以看到调用了onTransact方法,在该方法中会把远程方法返回值写入到_reply中。在IAidlCall接口中的Stub类中,我们可以看到onTransact方法的实现,代码如下。
@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_getName: { data.enforceInterface(DESCRIPTOR); java.lang.String _result = this.getName(); reply.writeNoException(); // 这里将getName的返回值写入到reply中 reply.writeString(_result); return true; } } return super.onTransact(code, data, reply, flags); }至此,我们分析完了IAidlCall接口类及其工作过程。其实我们也可以完全自己动手写出IAidlCall.java,在《Android开发艺术探索》书中就有给出相应地例子。下面可以看看AIDL的工作图。
这里有两点需要注意:
首先,当客户端发起远程请求的时候,由于当前进程会被挂起直到服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
通过以上介绍,可以大致了解了Binder的工作方式,后续还将继续深入了解,当然也不会太深,都是结合《Android开发艺术探索》和工作上所碰到的问题进行概述。
相关文章推荐
- 高德地图AndroidSDK错误码返回值为32解决办法(暨如何获取SHA1值的正确方法)
- Android中的savedInstanceState
- 最新 Android 视频分享
- 首页增加沉浸式状态栏,仅支持android4.4及以上
- Android定时器Timer.schedule
- 穿越之旅之--android中如何执行java命令
- 不可不知的android开发冷知识3
- android布局的优化
- Android中调用c函数来打印log---(JNI)
- android控件的绘制过程
- Android 屏幕适配方案
- Android Studio中代码混淆
- android 设置textview中划线效果
- android textview 显示一行,且超出自动截断,显示"..."
- android布局layout中的一些属性
- Android实现自适应正方形GridView
- Android屏幕适配全攻略(最权威的官方适配指导)
- android项目在不装opencvmanager.apk情况下,直接使用
- 穿越之旅之--android中如何执行java命令
- Android 蓝牙(概述)