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

来源:互联网 发布:java开源审批流源代码 编辑:程序博客网 时间:2024/05/22 12:41
1.Binder死亡代理
    这一节首先将介绍Binder类中比较重要的两个方法linkToDeath和unlinkToDeath。我们知道Binder是运行在服务进程,若服务端进程因为某种原因“死亡”,那么Binder对象也将随之而去,因为Binder对象是寄宿在服务端进程中的,这个时候我们的远程调用将会失败,客户端进程的功能也将受到影响。Binder类提供linkToDeath方法在客户端可以设置死亡代理,当服务端的Binder对象“死亡”,客户端可以受到死亡通知,这个时候我们可以重新恢复链接。
1.1 linkToDeath方法源码释义。
    /**     * Register the recipient for a notification if this binder     * goes away.  If this binder object unexpectedly goes away     * (typically because its hosting process has been killed),     * then the given {@link DeathRecipient}'s     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method     * will be called.     *      * <p>You will only receive death notifications for remote binders,     * as local binders by definition can't die without you dying as well.     *      * @throws RemoteException if the target IBinder's     * process has already died.     *      * @see #unlinkToDeath     */    public void linkToDeath(DeathRecipient recipient, int flags)            throws RemoteException;
    如果Binder死亡,那么该方法会注册一个通知的接受者。若binder对象意外死亡(一个典型的例子就是宿主进程被系统回收),那么死亡代理DeathRecipientbinderDied()方法将被调用。另外注意,你只会收到远程binder对象的死亡通知,本地的binder对象是不会收到的。
2. unlinkToDeath方法源码释义。
    /**     * Remove a previously registered death notification.     * The recipient will no longer be called if this object     * dies.     *      * @return {@code true} if the <var>recipient</var> is successfully     * unlinked, assuring you that its     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method     * will not be called;  {@code false} if the target IBinder has already     * died, meaning the method has been (or soon will be) called.     *      * @throws java.util.NoSuchElementException if the given     * <var>recipient</var> has not been registered with the IBinder, and     * the IBinder is still alive.  Note that if the <var>recipient</var>     * was never registered, but the IBinder has already died, then this     * exception will <em>not</em> be thrown, and you will receive a false     * return value instead.     */    public boolean unlinkToDeath(DeathRecipient recipient, int flags);
    该方法的作用是移除先前注册的死亡通知,如果binder对象死亡,那么死亡接受者将不再被调用。
    1)方法返回true:如果死亡接受者已经成功断开,要确保其binderDied()方法不会再被调用。
    2)方法返回false:如果目标IBinder对象已经死亡,意味着binderDied()方法已经(或者不久之后)被调用。
1.2.linkToDeath和unlinkToDeath使用
    在客户端代码如下。
public class MainActivity extends Activity {private IAidlCall mIAidlCall;private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {@Overridepublic void binderDied() {// TODO Auto-generated method stubif (mIAidlCall == null)return;mIAidlCall.asBinder().unlinkToDeath(mDeathRecipient, 0);mIAidlCall = null;// TODO:重新绑定远程服务}};private ServiceConnection conn = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName name) {}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mIAidlCall = IAidlCall.Stub.asInterface(service);try {service.linkToDeath(mDeathRecipient, 0);Toast.makeText(getApplicationContext(), mIAidlCall.getName(),Toast.LENGTH_LONG).show();} catch (RemoteException e) {// TODO Auto-generated catch blocke.printStackTrace();}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// "demo.action.aidl.IAidlCall" 是远程服务的actionbindService(new Intent("demo.action.aidl.IAidlCall"), conn, BIND_AUTO_CREATE);}}
    通过上述两个方法,我们可以完成Binder死亡重连远程服务的操作,其实在onServiceDisconnected方法中我们也可以完成重连远程服务的操作,那么这两种方法有什么区别?区别就是binderDied()方法跑在Binder Thread中,onServiceDisconnected方法跑在客户端的UI线程中。

2.Binder服务端回调通知客户端
    假如我们请求服务端为我们下载一批文件,每当下载完成一个文件时候需要通知客户端该文件已经下载完成,这时候我们需要在客户端注册监听下载完成的动作。我们都知道对象本身是不能跨进程传输的,在Binder跨进程通信的时候都是要将待传输的对象序列化,因此客户端进程和服务端进程持有的该对象是不同的对象,虽然他们的内容相同。因此当我们在客户端注册监听回调接口到服务端的后,服务端收到的回调接口和客户端注册的回调接口就不是同一个了,在注销(删除)监听的时候就会出现异常。伟大的Android为我们提供了RemoteCallbackList类来解决该问题。
    RemoteCallbackList是系统专门提供用于删除进程listener的接口,RemoteCallbackList是一个泛型类,支持管理任意的AIDL接口。以下是关于RemoteCallbackList的官方文档说明
    Takes care of the grunt work of maintaining a list of remote interfaces, typically for the use of performing callbacks from a Service to its clients. In particular, this:
    1. Keeps track of a set of registered IInterface callbacks, taking care to identify them through their underlying unique IBinder (by calling IInterface.asBinder().
    2.Attaches a IBinder.DeathRecipient to each registered interface, so that it can be cleaned out of the list if its process goes away.
    3.Performs locking of the underlying list of interfaces to deal with multithreaded incoming calls, and a thread-safe way to iterate over a snapshot of the list without holding its lock.
    To use this class, simply create a single instance along with your service, and call its register(E) and unregister(E) methods as client register and unregister with your service. To call back on to the registered clients, use beginBroadcast()getBroadcastItem(int), and finishBroadcast().
    If a registered callback's process goes away, this class will take care of automatically removing it from the list. If you want to do additional work in this situation, you can create a subclass that implements the onCallbackDied(E) method.
接下来给出相应地实例代码,客户端aidl相关代码如下。
    
    FileInfo类实现了Parcelable接口,用于跨进程对象的传递,代码如下。
public class FileInfo implements Parcelable {public long fileSize;public String fileName;public FileInfo() {}public FileInfo(long fileSize, String fileName) {this.fileName = fileName;this.fileSize = fileSize;}@Overridepublic int describeContents() {// TODO Auto-generated method stubreturn 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {// TODO Auto-generated method stub       dest.writeLong(fileSize);       dest.writeString(fileName);}    public static final Parcelable.Creator<FileInfo> CREATOR = new Parcelable.Creator<FileInfo>() {        public FileInfo createFromParcel(Parcel in) {            return new FileInfo(in);        }        public FileInfo[] newArray(int size) {            return new FileInfo[size];        }    };        private FileInfo(Parcel in) {        fileSize = in.readLong();        fileName = in.readString();    }    @Override    public String toString() {        return String.format("[fileSize:%s, fileName:%s]", fileSize, fileName);    }}
IAidlCall.aidl接口声明了四个方法,代码如下。
package com.example.aidldemo;import com.example.aidldemo.IDoneListener; interface IAidlCall{ void start2download(); void stop2download(); void register(IDoneListener listener); void unRegister(IDoneListener listener);}
    这里我们可以看到register的方法参数是IDoneListener接口,并不是AIDL支持的类型,因此要想AIDL支持该参数类型,必须声明IDoneListener接口的aidl文件,其代码如下。
package com.example.aidldemo;import com.example.aidldemo.FileInfo;interface IDoneListener{ void onDone(in FileInfo fileInfo);}
    然后将整个包(com.example.aidldemo)包括以上四个文件拷贝到服务端工程下。

    以下是服务端Service的代码,代码里面已有注释。
package com.example.service;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteCallbackList;import android.os.RemoteException;import android.util.Log;import com.example.aidldemo.FileInfo;import com.example.aidldemo.IAidlCall;import com.example.aidldemo.IDoneListener;public class MyService extends Service {private RemoteCallbackList<IDoneListener> mRemoteCallbackList = new RemoteCallbackList<IDoneListener>();private String TAG =this.getClass().getSimpleName();private boolean downloadFlag = false;public class MyServiceImpl extends IAidlCall.Stub {@Overridepublic void start2download() throws RemoteException {// TODO Auto-generated method stubdownloadFlag = true;new Thread(new WorkThread()).start();}@Overridepublic void stop2download() throws RemoteException {// TODO Auto-generated method stubdownloadFlag = false;}@Overridepublic void register(IDoneListener listener) throws RemoteException {// TODO Auto-generated method stubmRemoteCallbackList.register(listener);mRemoteCallbackList.beginBroadcast();//为什么调用完beginBroadcast马上要调用finishBroadcast,请看beginBroadcast官方说明    /**     * Prepare to start making calls to the currently registered callbacks.     * This creates a copy of the callback list, which you can retrieve items     * from using getBroadcastItem.  Note that only one broadcast can     * be active at a time, so you must be sure to always call this from the     * same thread (usually by scheduling with Handler) or do your own synchronization.       * You must call finishBroadcast when done.     * *///以上的说明有另外两点需要注意://第一:同一时间段只能有一个broadcast被激活,因此你必须确保请求一直都是在同一线程。//第二:完成beginBroadcast调用后,必须接着调用finishBroadcast。mRemoteCallbackList.finishBroadcast();}@Overridepublic void unRegister(IDoneListener listener) throws RemoteException {// TODO Auto-generated method stub            boolean success = mRemoteCallbackList.unregister(listener);            Log.d(TAG, "success = " + success);mRemoteCallbackList.beginBroadcast();mRemoteCallbackList.finishBroadcast();}}@Overridepublic IBinder onBind(Intent intent) {// TODO Auto-generated method stubreturn new MyServiceImpl();}private int counter = 0;private class WorkThread implements Runnable {@Overridepublic void run() {// TODO Auto-generated method stubwhile(downloadFlag){try {Thread.sleep(3000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}int size = counter +10;counter = size;FileInfo fileInfo = new FileInfo(size, "Kissonchan" + size);final int N = mRemoteCallbackList.beginBroadcast();for (int i = 0; i < N; i++) {//通过getBroadcastItem方法找到对应的IDoneListener,关于getBroadcastItem官方说明如下    /**     * Retrieve an item in the active broadcast that was previously started     * with beginBroadcast. This can only be called after the broadcast is started,     * and its data is no longer valid after calling finishBroadcast.     *///说明1:调用getBroadcastItem方法前一定要调用beginBroadcast方法,因为只有broadcast启动了才能调用getBroadcastItem。//说明2:当调用finishBroadcast后,通过getBroadcastItem得到的数据将无效了。IDoneListener l = mRemoteCallbackList.getBroadcastItem(i);if (l != null) {try {Log.d(TAG, "fileInfo = " +fileInfo.fileName);l.onDone(fileInfo);} catch (RemoteException e) {e.printStackTrace();}}}mRemoteCallbackList.finishBroadcast();}}}}
    
    以下是客户端实现调用过程的代码。
package com.example.clientdemo;import android.app.Activity;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.os.RemoteException;import android.util.Log;import android.widget.Toast;import com.example.aidldemo.FileInfo;import com.example.aidldemo.IAidlCall;import com.example.aidldemo.IDoneListener;public class MainActivity extends Activity {private IAidlCall mIAidlCall;private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {@Overridepublic void binderDied() {// TODO Auto-generated method stubif (mIAidlCall == null)return;mIAidlCall.asBinder().unlinkToDeath(mDeathRecipient, 0);mIAidlCall = null;// 重新绑定远程服务bindService(new Intent("demo.action.aidl.IAidlCall").setPackage("com.example.severdemo"), conn,BIND_AUTO_CREATE);}};private ServiceConnection conn = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName name) {// TODO Auto-generated method stub}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// TODO Auto-generated method stubmIAidlCall = IAidlCall.Stub.asInterface(service);try {service.linkToDeath(mDeathRecipient, 0);mIAidlCall.register(doneListener);mIAidlCall.start2download();} catch (RemoteException e) {// TODO Auto-generated catch blocke.printStackTrace();}}};private IDoneListener doneListener = new IDoneListener.Stub() {@Overridepublic void onDone(FileInfo fileInfo) throws RemoteException {// TODO Auto-generated method stubMessage msg = mHandler.obtainMessage();msg.obj = fileInfo.fileName;mHandler.sendMessage(msg);//这里使用Handler进行消息同步,是因为当前线程是Binder线程,通过打印出的log信息可以看到线程名字为Binder_1,Binder_2....等Log.d("MyService", Thread.currentThread().getName());}};private Handler mHandler = new Handler() {public void handleMessage(android.os.Message msg) {Toast.makeText(getApplicationContext(), msg.obj.toString(),Toast.LENGTH_LONG).show();};};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// "demo.action.aidl.IAidlCall" 是远程服务的action,在5.0之后必须要调用setPackage方法设置服务端的包名不然会报错bindService(new Intent("demo.action.aidl.IAidlCall").setPackage("com.example.severdemo"),conn, BIND_AUTO_CREATE);}@Overrideprotected void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();if (mIAidlCall != null && mIAidlCall.asBinder().isBinderAlive()) {try {mIAidlCall.unRegister(doneListener);mIAidlCall.stop2download();} catch (RemoteException e) {// TODO Auto-generated catch blocke.printStackTrace();}}unbindService(conn);}}

    以上Binder服务端回调通知客户端的过程阐述完毕,通过看《Android开发艺术探索》收货真的很多,以前项目中也做过AIDL相关的功能模块,但是也只是很简单地使用,并没有深入研究。当然Binder作为Android系统比较核心的存在,还有很多值得我们去探索!






0 0