关闭

Android开发艺术探索学习-IPC之Binder(一)

标签: androidBinder
397人阅读 评论(0) 收藏 举报
分类:
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 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对象并re-attach到该进程。例如,当你如果在Activity里面使用Binder,如果你的Activity未启动,那么它就随意可能会系统回收。当Activity重建时,你需要重建一个新的Binder对象并放在正确的地方。当然你也需要注意你的进程因其他原因而启动(比如收到广播),那么这种情况activity不会重建也就不会跑到创建Binder对象的代码了。
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是入参序列,如果调用的远程方法需要入参就通过_datawriteInt、writeLong等方法将参数写入_data中。
_reply是方法的返回值序列,如果调用的远程方法有返回值,就通过_replyreadInt、readLong等方法将参数读到_reply中。
这里最主要的方法是transact(intcode, Parceldata, Parcelreplyintflags)方法。
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开发艺术探索》和工作上所碰到的问题进行概述。






0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:15879次
    • 积分:411
    • 等级:
    • 排名:千里之外
    • 原创:21篇
    • 转载:0篇
    • 译文:2篇
    • 评论:20条
    最新评论