Android IBinder的linkToDeath介绍及情景模拟
最近查看Framework源码的时候,读到了AudioService处理音量的流程,在这里碰到了IBinder的linkToDeath()这个知识点,比较感兴趣,所以记录下来,并自己写demo尝试了一下。
我们简单来看下AudioService处理静音这一块。
/frameworks/base/media/java/android/media/AudioManager.java
public class AudioManager { ...... /** * {@hide} */ private final IBinder mICallBack = new Binder(); public void setStreamMute(int streamType, boolean state) { IAudioService service = getService(); try { service.setStreamMute(streamType, state, mICallBack); } catch (RemoteException e) { Log.e(TAG, "Dead object in setStreamMute", e); } } ......}
service是一个IAudioService,它的实现类是AudioService,AudioManager.setStreamMute()中会调用AudioService.setStreamMute(streamType, state, mICallBack);其中mICallBack是一个Binder用来记录申请静音的客户端。
/frameworks/base/media/java/android/media/AudioService.java
private VolumeStreamState[] mStreamStates;//第1步/** @see AudioManager#setStreamMute(int, boolean) */ public void setStreamMute(int streamType, boolean state, IBinder cb) { if (isStreamAffectedByMute(streamType)) { mStreamStates[streamType].mute(cb, state); } }public class VolumeStreamState { private ArrayList mDeathHandlers; //handles mute/solo clients death private VolumeStreamState(String settingName, int streamType) { mVolumeIndexSettingName = settingName; mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE; mStreamType = streamType; mIndexMax = MAX_STREAM_VOLUME[streamType]; AudioSystem.initStreamVolume(streamType, 0, mIndexMax); mIndexMax *= 10; // mDeathHandlers must be created before calling readSettings() mDeathHandlers = new ArrayList(); readSettings(); }}//第2步public synchronized void mute(IBinder cb, boolean state) { VolumeDeathHandler handler = getDeathHandler(cb, state); if (handler == null) { Log.e(TAG, "Could not get client death handler for stream: "+mStreamType); return; } handler.mute(state); }private class VolumeDeathHandler implements IBinder.DeathRecipient { private IBinder mICallback; // To be notified of client's death private int mMuteCount; // Number of active mutes for this client VolumeDeathHandler(IBinder cb) { mICallback = cb; }//第3步 // must be called while synchronized on parent VolumeStreamState public void mute(boolean state) { if (state) { if (mMuteCount == 0) { // Register for client death notification try { // mICallback can be 0 if muted by AudioService if (mICallback != null) { mICallback.linkToDeath(this, 0); } mDeathHandlers.add(this); // If the stream is not yet muted by any client, set level to 0 if (muteCount() == 0) { Set set = mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); int device = ((Integer)entry.getKey()).intValue(); setIndex(0, device, false /* lastAudible */); } sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, VolumeStreamState.this, 0); } } catch (RemoteException e) { // Client has died! binderDied(); return; } } else { Log.w(TAG, "stream: "+mStreamType+" was already muted by this client"); } mMuteCount++; } else { if (mMuteCount == 0) { Log.e(TAG, "unexpected unmute for stream: "+mStreamType); } else { mMuteCount--; if (mMuteCount == 0) { // Unregister from client death notification mDeathHandlers.remove(this); // mICallback can be 0 if muted by AudioService if (mICallback != null) { mICallback.unlinkToDeath(this, 0); } if (muteCount() == 0) { // If the stream is not muted any more, restore its volume if // ringer mode allows it if (!isStreamAffectedByRingerMode(mStreamType) || mRingerMode == AudioManager.RINGER_MODE_NORMAL) { Set set = mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); int device = ((Integer)entry.getKey()).intValue(); setIndex(getIndex(device, true /* lastAudible */), device, false /* lastAudible */); } sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, VolumeStreamState.this, 0); } } } } } } public void binderDied() { Log.w(TAG, "Volume service client died for stream: "+mStreamType); if (mMuteCount != 0) { // Reset all active mute requests from this client. mMuteCount = 1; mute(false); } } } private synchronized int muteCount() { int count = 0; int size = mDeathHandlers.size(); for (int i = 0; i < size; i++) { count += mDeathHandlers.get(i).mMuteCount; } return count; }
它们的流程图如下(markdown画的图,不怎么美观)
Created with Raphaël 2.1.0 AudioManager AudioManager AudioService AudioService VolumeStreamState VolumeStreamState VolumeDeathHandler VolumeDeathHandler mute() mute() mute() mICallback.linkToDeath(this, 0)或者mICallback.unlinkToDeath(this, 0);通过上面的代码我们可以得知VolumeDeathHandler继承IBinder.DeathRecipient.它可以监听申请静音的客户端的存活状态变化。
好吧,下面进入主题。
IBinder.DeathRecipient
/*** Interface for receiving a callback when the process hosting an IBinder has gone away. * * @see #linkToDeath */ public interface DeathRecipient { public void binderDied(); }
它是IBinder的内部接口,接口方法void binderDied()。注释的意思大概是这是一个接受Binder所在的宿主进程消失时的回调,并且建议我们去查看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. * * You will only receive death notifications for remote binders, * as local binders by definition can't die without you dying as well. * * @throws Throws {@link RemoteException} if the target IBinder's * process has already died. * * @see #unlinkToDeath */
public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException; /** * Remove a previously registered death notification. * The recipient will no longer be called if this object * dies. * * @return Returns true if the recipient is successfully * unlinked, assuring you that its * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method * will not be called. Returns false if the target IBinder has already * died, meaning the method has been (or soon will be) called. * * @throws Throws {@link java.util.NoSuchElementException} if the given * recipient has not been registered with the IBinder, and * the IBinder is still alive. Note that if the recipient * was never registered, but the IBinder has already died, then this * exception will not be thrown, and you will receive a false * return value instead. */ public boolean unlinkToDeath(DeathRecipient recipient, int flags);
注释说的很清楚,通过一个IBinder.linkToDeath()可以监听这个Binder本身的消失,并调用回调DeathRecipient.binderDied().IBinder.unlinkToDeath()可以取消监听。
Android的c/s服务架构中,难免会发生服务端或者客户端异常终止的情况,而通过IBinder.DeathRecipient可以很好处理这种情况,当IBinder对象异常终止时可以做一些资源释放的处理。
实战 情景模拟
之前说过在Framework代码中AudioService出现过IBinder.DeathRecipient,但是我内心蠢蠢欲动,我就是想自己实践看看效果。有没有方法呢?自然有。
接下来的Demo中我会创建两个App应用,一个作为服务端,一个作为客户端。客户端通过IBinder.DeathRecipient来监听服务端的异常终止情况。
服务端
ITest.aidl
package com.example.deathrecipientdemo;interface ITest{ void test();}
TestService.java
package com.example.deathrecipientdemo;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;public class TestService extends Service { private static final String TAG = "frank"; private Binder mBinder = new ITest.Stub() { @Override public void test() throws RemoteException { // TODO Auto-generated method stub Log.i(TAG, "server"); } }; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub Log.i(TAG, "onBind"); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { Thread.sleep(10000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); Log.i(TAG,e.toString()); } //结束自己 android.os.Process.killProcess(android.os.Process.myPid()); //TestService.this.stopSelf(); //Log.i("test", "stopSelf"); } }).start(); return mBinder; }}
可以看到,它会在被启动时过10秒后自杀。
<service android:name="com.example.deathrecipientdemo.TestService" android:enabled="true" android:exported="true"> <intent-filter > <action android:name="com.frank.test"/> intent-filter> service>
好了,以上是服务器App的部分代码。
接下我新建一个客户端的App
package com.example.testdemo;public class MainActivity extends Activity { private static final String TAG = "frank"; private ServiceConnection mCon; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(); intent.setComponent(new ComponentName("com.example.deathrecipientdemo","com.example.deathrecipientdemo.TestService")); intent.setAction("com.frank.test"); final DeathRecipient deathHandle = new DeathRecipient() { @Override public void binderDied() { // TODO Auto-generated method stub Log.i(TAG, "binder is died"); } }; mCon = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub Log.i(TAG, "onServiceDisconnected "+name.toShortString()); } @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub try { Log.i(TAG, "onServiceConnected "+name.toShortString()+" "+service.getInterfaceDescriptor()); service.linkToDeath(deathHandle, 0); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; bindService(intent,mCon,Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); unbindService(mCon); }}
接下来就是验证时间,把两个App都装进了手机,先启动服务App,再启动客户端App,然后查看Log。我习惯于打开CMD,然后adb logcat | findstr frank。因为我在demo中将Log的TAG都设置为了frank.
按照理想中的预期,服务端被启动后10秒就会自杀,而自杀时客户端能够监听得到,真实情况是不是这样的呢?看log
I/frank (17732): onBindI/frank (17475): onServiceConnected {com.example.deathrecipientdemo/com.example.deathrecipientdemo.TestService} com.example.deathrecipientdemo.ITestD/ActivityThread(17732): SVC-BIND_SERVICE handled : 0 / BindServiceData{token=android.os.BinderProxy@5ebc3b5 intent=Intent { act=com.frank.test cmp=com.example.deathrecipientdemo/.TestService }}I/frank (17475): binder is diedI/frank (17475): onServiceDisconnected {com.example.deathrecipientdemo/com.example.deathrecipientdemo.TestService}
I/frank (17475): binder is died
这个被成功打印,所以代表我们实现成功了,我们能够准确运用IBinder.DeathRecipient接口去监听服务端的消失变动。当然实际开发中,我们要根据业务是否存在这样的需求而去编码,这里只作为学习探讨之用,反正我觉得涉及到服务之间的交互这个功能是很有用武之地的。
更多相关文章
- 用CSS3生成的一个漂亮的android客户端页面
- Android之从网络中获取数据并返回客户端的两种方式:XML格式返回
- java android客户端SSL通信 遇到的几个报错及处理
- Android仿人人客户端(v5.7.1)——个人主页(三)
- Android之基于xmpp openfire smack开发之Android客户端开发[3]
- [email protected] 的 Android 和 iOS 客户端全面开源
- PasswordMaker的Android客户端
- Android服务端开发1-使用Eclipse搭建Java Web服务端
- Android客户端与PC服务器实现Socket通信