专栏首页Frank909Android IBinder的linkToDeath介绍及情景模拟

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<VolumeDeathHandler> 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<VolumeDeathHandler>();

            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.0AudioManagerAudioManagerAudioServiceAudioServiceVolumeStreamStateVolumeStreamStateVolumeDeathHandlerVolumeDeathHandlermute()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.
     *
     * <p>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 <var>recipient</var> 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
     * <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);

注释说的很清楚,通过一个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): onBind
I/frank   (17475): onServiceConnected {com.example.deathrecipientdemo/com.example.deathrecipientdemo
.TestService}  com.example.deathrecipientdemo.ITest
D/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 died
I/frank   (17475): onServiceDisconnected {com.example.deathrecipientdemo/com.example.deathrecipientd
emo.TestService}

I/frank (17475): binder is died 这个被成功打印,所以代表我们实现成功了,我们能够准确运用IBinder.DeathRecipient接口去监听服务端的消失变动。当然实际开发中,我们要根据业务是否存在这样的需求而去编码,这里只作为学习探讨之用,反正我觉得涉及到服务之间的交互这个功能是很有用武之地的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 死磕YOLO系列,YOLOv2的自我修养

    YOLO 在当时是非常不错的算法,速度极快,但明显的缺陷就是精度问题特别是小尺寸目标检测问题上。

    Frank909
  • Kotlin 第一弹:自定义 ViewGroup 实现流式标签控件

    上周 Google I/O 大会的召开,宣布了 Kotlin 语言正式成为了官方开发语言。一时间 Android 开发者的圈子炸开了锅,各种关于 Kotlin ...

    Frank909
  • 轻松学,听说你还没有搞懂 Dagger2

    Dagger2 确实比较难学,我想每个开发者学习的时候总是经历了一番痛苦的挣扎过程,于是就有了所谓的从入门到放弃之类的玩笑,当然不排除基础好的同学能够一眼看穿。...

    Frank909
  • Guava-1.20类Splitter

    拆分字符串。 该类的实例是不可变的。构造方法对于引用的实例没有效果, 必须使用构造方法返回的splitter实例。

    悠扬前奏
  • ViewPager 实现 Galler 效果, 中间大图显示,两边小图展示

    正常情况下, ViewPager 一页只能显示一项数据, 但是我们常常看到网上,特别是电视机顶盒的首页经常出现中间大图显示两端也都露出一点来,这种效果怎么实现呢...

    xiangzhihong
  • Unity UI案例(绘制圆环)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明...

    bering
  • Android使用后端云Bmob实现登录、注册及失物招领

    最近在使用后端云Bmob对数据进行存储,目的是在不搭建服务器的前提下,能对Android应用的数据进行操作处理。

    SoullessCoder
  • 漫画:设计模式之 “工厂模式”

    假设我们的业务代码当中,有一个被广泛引用的“口罩类”,这个类实例需要在许多地方被创建和初始化,而初始化的代码也比较复杂。

    小灰
  • Disruptor 实践:整合到现有的爬虫框架一. Disruptor二. 实践总结:

    NetDiscovery 是基于 Vert.x、RxJava 2 等框架实现的爬虫框架。

    fengzhizi715
  • HandlerThread源码分析

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

    用户2965908

扫码关注云+社区

领取腾讯云代金券