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

原创 2015年11月19日 17:24:30
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() {

		@Override
		public void binderDied() {
			// TODO Auto-generated method stub
			if (mIAidlCall == null)
				return;
			mIAidlCall.asBinder().unlinkToDeath(mDeathRecipient, 0);
			mIAidlCall = null;
			// TODO:重新绑定远程服务
		}
	};
	
	private ServiceConnection conn = new ServiceConnection() {

		@Override
		public void onServiceDisconnected(ComponentName name) {

		}

		@Override
		public 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 block
				e.printStackTrace();
			}
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		// "demo.action.aidl.IAidlCall" 是远程服务的action
		bindService(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;
	}
	
	@Override
	public int describeContents() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public 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 {

		@Override
		public void start2download() throws RemoteException {
			// TODO Auto-generated method stub
			downloadFlag = true;
			new Thread(new WorkThread()).start();
		}

		@Override
		public void stop2download() throws RemoteException {
			// TODO Auto-generated method stub
			downloadFlag = false;
		}
		
		@Override
		public void register(IDoneListener listener) throws RemoteException {
			// TODO Auto-generated method stub
			mRemoteCallbackList.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();
		}

		@Override
		public 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();
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return new MyServiceImpl();
	}
	
	private int counter = 0;
	
	private class WorkThread implements Runnable {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(downloadFlag){
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.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() {

		@Override
		public void binderDied() {
			// TODO Auto-generated method stub
			if (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() {

		@Override
		public void onServiceDisconnected(ComponentName name) {
			// TODO Auto-generated method stub
		}

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// TODO Auto-generated method stub
			mIAidlCall = IAidlCall.Stub.asInterface(service);
			try {
				service.linkToDeath(mDeathRecipient, 0);
				mIAidlCall.register(doneListener);
				mIAidlCall.start2download();
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	};

	private IDoneListener doneListener = new IDoneListener.Stub() {
		@Override
		public void onDone(FileInfo fileInfo) throws RemoteException {
			// TODO Auto-generated method stub
			Message 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();
		};
	};

	@Override
	protected 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);
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		if (mIAidlCall != null && mIAidlCall.asBinder().isBinderAlive()) {
			try {
				mIAidlCall.unRegister(doneListener);
				mIAidlCall.stop2download();
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		unbindService(conn);
	}
}

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






版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

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

1. Binder简介 1.1 What is Binder?     Android Dev Doc:Base class for a remotable object, the core pa...

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

其实正常情况下,项目中能用到AIDL的机会不多,但是有一种情况是比较常用的,比如一些大厂,开发了好几个App,如果这些App都是热门应用,那么他们之间就会出现相互“勾结”的情况,AIDL就有了用武之地...

Android aidl Binder框架浅析

1、概述Binder能干什么?Binder可以提供系统中任何程序都可以访问的全局服务。这个功能当然是任何系统都应该提供的,下面我们简单看一下Android的Binder的框架Android Binde...

读安卓开发艺术探索和鸿洋博客有关于binder知识的笔记

首先贴上玉刚大神博客中安卓中binder的知识总结,如下就是玉刚大神的博客里面对进程间知识通信的总结: 摘要 Binder是android中一个很重要且很复杂的概念,它在系统的整体运作...

Android开发艺术探索--第二章IPC机制(2)之Binder

直观来说,Binder是Android中的一个实现了IBinder接口的类;从IPC角度来看,Binder是Android中一个跨进程通信的方式;从Android FrameWork角度来说,Bind...

IBinder中linkToDeath的介绍

远程对象的基础接口,是一个为了在执行进程中和进程间调用时的高性能,而设计的轻量级远程调用机制的核心部分。这个接口描述了和远程对 象交互的抽象协议。不要直接实现这个接口,而是通过继承 Binder来 ...

android 之 linkToDeath和unlinkToDeath。(死亡代理)

Binder死亡代理  我们都知道,在和service进行交互时,service返回一个Binder对象。Binder是工作在service端,如果,由于某种原因,服务端出现故障而死亡,那么该返回的...
  • lea_fy
  • lea_fy
  • 2016-10-31 21:29
  • 1179

Android IBinder的linkToDeath介绍及情景模拟

最近查看Framework源码的时候,读到了AudioService处理音量的流程,在这里碰到了IBinder的linkToDeath()这个知识点,比较感兴趣,所以记录下来,并自己写demo尝试了一...

linkToDeath机制了解和使用

往往是由于服务端进程意外停止了,这时我们需要重新连接服务。 那么我们可以使用linkToDeath机制,如果使用bindService那么还可以通过ServiceConnection.onServic...

《Android开发艺术探索》之学习笔记(二)IPC机制

IPC Inter-Process Communication的缩写。含义为进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程。 进程和线程的区别 线程是CPU调度的最小单元,...
  • tgzzl
  • tgzzl
  • 2016-08-08 21:33
  • 209
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)