AIDL使用学习(二):跨进程回调以及RemoteCallbackList

前言#

五一假期终于结束了,回来一直也是在面试,今天先把之前的AIDL的内容讲完,再整理一下面试总结。

正文#

上一篇我们已经了解了AIDL的基本使用方法,一般服务内都是要做耗时操作的,等处理结束之后在回调给调用方,首先我们需要定义一个callback

// IOnCallbackListener.aidl
package com.lzp.aidlstudy.callback;

interface IOnCallbackListener{

    void callback(int result);
}

有些朋友可能有疑问了:这不是跟之前定义的Service一样吗?

恭喜你答对了,他俩就是一样的,这个时候在好好的体会一下AIDL的定义:Android 接口定义语言。也就是说他本身就是定义接口的,只不过他自动生成了内部的Binder机制,我们就以这个IOnCallbackListener为例,看看到底生成了什么东西:


/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/li504799868/Desktop/AIDLStudy/app/src/main/aidl/com/lzp/aidlstudy/callback/IOnCallbackListener.aidl
 */
package com.lzp.aidlstudy.callback;

public interface IOnCallbackListener extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.lzp.aidlstudy.callback.IOnCallbackListener {
        private static final java.lang.String DESCRIPTOR = "com.lzp.aidlstudy.callback.IOnCallbackListener";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.lzp.aidlstudy.callback.IOnCallbackListener interface,
         * generating a proxy if needed.
         */
        public static com.lzp.aidlstudy.callback.IOnCallbackListener asInterface(android.os.IBinder 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 {
            ...
        }

        private static class Proxy implements com.lzp.aidlstudy.callback.IOnCallbackListener {
            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 void callback(int result) throws android.os.RemoteException {
                ...
            }
        }

        static final int TRANSACTION_callback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public void callback(int result) throws android.os.RemoteException;
}


由于这个文件有点太长了,所以一些方法的代码就省略了,更为具体的大家可以自己运行去看一下源文件,简单的分析一下:

1、IOnCallbackListener里面除了定义了Stub内部类以外,只有callback()方法。
2、在Stub里面有很多熟悉的方法,例如获取代理,返回binder之类的,内部还有代理类Proxy。
3、Proxy中跨进程通信的Binder机制实现,这个是最核心的代码。

ok,总结也就是说,系统为我们自定义的接口实现了Binder机制,这样就可以实现跨进行,只不过这个Binder没有我们之前绑定服务使用的那么明显。

然后在之前的ITestInterface.aidl文件中新定义一个方法:

// aidl  定义实现的Service方法
package com.lzp.aidlstudy;

import com.lzp.aidlstudy.bean.TestBean;
import com.lzp.aidlstudy.callback.IOnCallbackListener;

interface ITestInterface {

    // 定义一个计算方法
    int getCalculateResult(in TestBean bean);

    // 通过线程回调的方式返回计算结果
    void getCalculateResultByThread(in TestBean bean, IOnCallbackListener callback);

}

接下来去实现getCalculateResultByThread方法,打开TestService文件:

@Override
public void getCalculateResultByThread(final TestBean bean, final IOnCallbackListener callback) throws RemoteException {
            new Thread() {
                @Override
                public void run() {
                    callback.callback(bean.getX() + bean.getY());
                }
            }.start();
        }

最后在MainActivity中添加一个按钮:

findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    TestBean testBean = new TestBean();
                    testBean.setX(100);
                    testBean.setY(250);
                    // 这里要使用IOnCallbackListener.Stub
                    binder.getCalculateResultByThread(testBean, new IOnCallbackListener.Stub() {
                        @Override
                        public void callback(final int result) {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(MainActivity.this, result + "", Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    });

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

首先强调一下,跨进程callback回调是在子线程中,千万不要忘记。

带着无比激动的心情,赶紧运行一下,完美!

这里写图片描述

扩展#

很多朋友在网上查看资料的时候发现,还有一个RemoteCallbackList,他的主要作用是可以把多个callback保存到列表里,在合适的时机同时回调,也可以防止重复的调用相同的任务,只保证你需要的一个结果回调,这个就厉害了,他的源码也非常的简单:

package android.os;

import android.util.ArrayMap;

/**
 * 擅长 简单的持续性的一系列的远程接口的使用,尤其是Service对他的客户端的回调。
 * 需要注意的是:
 * 使用的时候,请确保每一个注册的callback唯一性,这样可以在进程停止的时候,清空这些callback。
 * 多线程请注意锁的问题。
 * 
 * 使用这个类只要在Service使用单例模式就可以了,使用register和unregister方法来添加客户端的回调,使用时,先beginBroadcast,在getBroadcastItem,最后finishBroadcast。
 * 
 * 如果一个注册的会滴啊进程结束了,这个类将自动从列中中移除,如果你想做一些额外的工作,就通过继承来实现onCallbackDied方法。
 * 
/
public class RemoteCallbackList<E extends IInterface> {
    /*package*/ ArrayMap<IBinder, Callback> mCallbacks
            = new ArrayMap<IBinder, Callback>();
    private Object[] mActiveBroadcast;
    private int mBroadcastCount = -1;
    private boolean mKilled = false;

    private final class Callback implements IBinder.DeathRecipient {
        final E mCallback;
        final Object mCookie;
        
        Callback(E callback, Object cookie) {
            mCallback = callback;
            mCookie = cookie;
        }

        public void binderDied() {
            synchronized (mCallbacks) {
                mCallbacks.remove(mCallback.asBinder());
            }
            onCallbackDied(mCallback, mCookie);
        }
    }

    /**
     * 注册方法
     */
    public boolean register(E callback) {
        return register(callback, null);
    }
    /**
     *注册的具体实现方法,注册的callback,只有在调用unregister或者进程结束才会被解绑,返回添加到集合中的结果(true、false)
     */
    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }

    /**
     * 解绑callback
     */
    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }

    /**
     * 清空之前所有注册的callback
     */
    public void kill() {
        synchronized (mCallbacks) {
            for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
                Callback cb = mCallbacks.valueAt(cbi);
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
            }
            mCallbacks.clear();
            mKilled = true;
        }
    }

    /**
     * 老版本的onCallbackDied
     */
    public void onCallbackDied(E callback) {
    }
    
    /**
     * 默认调用的是onCallbackDied(E callback)r
     */
    public void onCallbackDied(E callback, Object cookie) {
        onCallbackDied(callback);
    }

    /**
     * 开始对保存的集合进行回调,返回目前回调集合的大小
     */
    public int beginBroadcast() {
        synchronized (mCallbacks) {
            if (mBroadcastCount > 0) {
                throw new IllegalStateException(
                        "beginBroadcast() called while already in a broadcast");
            }
            
            final int N = mBroadcastCount = mCallbacks.size();
            if (N <= 0) {
                return 0;
            }
            Object[] active = mActiveBroadcast;
            if (active == null || active.length < N) {
                mActiveBroadcast = active = new Object[N];
            }
            for (int i=0; i<N; i++) {
                active[i] = mCallbacks.valueAt(i);
            }
            return N;
        }
    }

    /**
     * 获取指定索引的callback
     */
    public E getBroadcastItem(int index) {
        return ((Callback)mActiveBroadcast[index]).mCallback;
    }
    
    /**
     * 回去指定索引的callback的Cookie
     */
    public Object getBroadcastCookie(int index) {
        return ((Callback)mActiveBroadcast[index]).mCookie;
    }

    /**
     * 清楚之前的beginBroadcast的初始状态,当处理结束请调用这个方法。
     *
     * @see #beginBroadcast
     */
    public void finishBroadcast() {
        synchronized (mCallbacks) {
            if (mBroadcastCount < 0) {
                throw new IllegalStateException(
                        "finishBroadcast() called outside of a broadcast");
            }

            Object[] active = mActiveBroadcast;
            if (active != null) {
                final int N = mBroadcastCount;
                for (int i=0; i<N; i++) {
                    active[i] = null;
                }
            }

            mBroadcastCount = -1;
        }
    }

    /**
     * 返回当前的的要处理的callback数量
     * beginBroadcast 返回的是当前注册的数量
     * getRegisteredCallbackCount返回的是处理的数量
     * 两者的返回结果可能不同
     */
    public int getRegisteredCallbackCount() {
        synchronized (mCallbacks) {
            if (mKilled) {
                return 0;
            }
            return mCallbacks.size();
        }
    }
}

他的代码不多,但是英文注释是真心的多,我就直接在注释里面给大家简单的翻译一下了,具体大家可以自己去看源码注释。

下面就简单的修改一下我们的代码:

// 添加RemoteCallbackList
private final RemoteCallbackList<IOnCallbackListener> mCallbacks
            = new RemoteCallbackList<>();

// 修改后的getCalculateResultByThread
 @Override
public void getCalculateResultByThread(final TestBean bean, final IOnCallbackListener callback) throws RemoteException {
            new Thread() {
                @Override
                public void run() {
                //注册callback
                    mCallbacks.register(callback);
                    callback(bean.getX() + bean.getY(), callback);
                }
            }.start();
        }
        
/**
* 处理callback
*/
void callback(int result, IOnCallbackListener callback) {
        final int N = mCallbacks.beginBroadcast();
        try {
            for (int i = 0; i < N; i++) {
                IOnCallbackListener ibc = mCallbacks.getBroadcastItem(i);
                ibc.callback(result);
                // 处理结束,解绑callback
                mCallbacks.unregister(ibc);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mCallbacks.finishBroadcast();
    }

运行结果也是一样的,就不贴出来了。

总结#

这样AIDL的跨进程回调我们也了解了,那么他具体的调用过程是怎么样的,系统为我们生成的代码都起到了什么作用呢?这些我们在下一篇在深入的讨论。

Demo下载地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容

  • Jianwei's blog 首页 分类 关于 归档 标签 巧用Android多进程,微信,微博等主流App都在用...
    justCode_阅读 5,888评论 1 23
  • Android跨进程通信IPC整体内容如下 1、Android跨进程通信IPC之1——Linux基础2、Andro...
    隔壁老李头阅读 10,703评论 13 43
  • 毫不夸张地说,Binder是Android系统中最重要的特性之一;正如其名“粘合剂”所喻,它是系统间各个组件的桥梁...
    weishu阅读 17,782评论 29 246
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,282评论 25 707
  • 作为人类文明肇始的源头之一,古希腊像一颗洁白莹润,光彩熠熠的珍珠,始终散发着迷离动人的光泽。 无论是它神秘莫测,却...
    江昭和阅读 340评论 2 10