跨进程的EventBus,开源框架HermesEventBus使用小结

为什么使用HermesEventBus

原有项目场景:Socket通信模块在子进程RemoteService中,主进程想要发送一个Socket协议,先从业务模块通过EventBus post一个Event到本地LocalService,本地Service通过ServiceConnection与RemoteService绑定,本地LocalService在ServiceConnection的连接上将AIDL接口封装为一个Messenger,通过Messenger向RemoteService发送Message,而Event就是被包裹在Message中的Bundle里。

说到这里感觉不借助图形很难描述:

原进程通信.png

问题在这里:

  • 每个序列化的Object要放入Bundle中,Bundle对象再放入Message中,过程繁琐
  • 每个Message都有what属性需要Handler在处理时判断
  • 业务模块想要执行子进程的RemoteService的方法先通过EventBus post一个Object,在LocalService必须有相应的处理。
  • 子进程中的RemoteService接收到了服务器发来的消息给业务模块,先要通过本地LocalService处理Message,再由EventBus把Message中的Object post出去。

综上所述,多了一个LocalService,显得很碍眼。

使用HermesEventBus

HermesEventBus是一种支持跨进程,跨APP通信的以EventBus为基础的框架,使用起来就像EventBus。
项目地址:https://github.com/elemers/HermesEventBus

** 如果你的应用是单个app,在Application中可以初始化HermesEventBus**:

    private void initHermesEventBus() {
        HermesEventBus.getDefault().init(getBaseApplication());
    }

由于是多进程,以我的项目为例,主进程会跑一遍这个方法,RemoteService启动时子进程也会跑一遍这个方法。

** 如果项目是多个app,选择一个作为主APP注册Service**:

<service android:name="xiaofei.library.hermes.HermesService$HermesService0"/>

然后在其他app的Application的onCreate中执行:

HermesEventBus.getDefault().connectApp(this, packageName);

** 当进程不再需要使用HermesEventBus,通过调用以下方法销毁HermesEventBus**:

HermesEventBus.getDefault().destroy();

发送事件就像EventBus一样简单,在任意进程内执行:

 HermesEventBus.getDefault().postSticky(event);

发送完事件,在任意进程内的注册的观察者就会处理事件:

    /**
     * 通信层处理发送SOCKET请求事件
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.POSTING , sticky = true)
    public void handleRequestEvent(RequestEvent event){
        onHandleLocalSocketBizRequest(event);
    }

HermesEventBus是怎么做到跨进程通信的

先看初始化方法:

    public void init(Context context) {
        mContext = context.getApplicationContext();
        mMainProcess = isMainProcess(context.getApplicationContext());
        if (mMainProcess) {
            Hermes.init(context);
            Hermes.register(MainService.class);
            mMainApis = MainService.getInstance();
        } else {
            mState = STATE_CONNECTING;
            Hermes.setHermesListener(new HermesListener());
            Hermes.connect(context, Service.class);
            Hermes.register(SubService.class);
        }
    }

在初始化时,区分了主进程和子进程,如果是子进程会通过Hermes.connect(context, Service.class)绑定主进程的Service。

打开编译好的APK,查看AndroidManifest.xml会发现:

 <service
            android:name="xiaofei.library.hermeseventbus.HermesEventBus$Service" />

没错,子进程就是绑定了主进程中的Service与主进程通信的。而通信的载体是一个Mail类:

private final IHermesService.Stub mBinder = new IHermesService.Stub() {
        @Override
        public Reply send(Mail mail) {
            try {
                Receiver receiver = ReceiverDesignator.getReceiver(mail.getObject());
                int pid = mail.getPid();
                IHermesServiceCallback callback = mCallbacks.get(pid);
                if (callback != null) {
                    receiver.setHermesServiceCallback(callback);
                }
                return receiver.action(mail.getTimeStamp(), mail.getMethod(), mail.getParameters());
            } catch (HermesException e) {
                e.printStackTrace();
                return new Reply(e.getErrorCode(), e.getErrorMessage());
            }
        }

        @Override
        public void register(IHermesServiceCallback callback, int pid) throws RemoteException {
            mCallbacks.put(pid, callback);
        }

        @Override
        public void gc(List<Long> timeStamps) throws RemoteException {
            OBJECT_CENTER.deleteObjects(timeStamps);
        }
    };

public class Mail implements Parcelable {

    private long mTimeStamp;

    private int mPid;

    private ObjectWrapper mObject;

    private MethodWrapper mMethod;

    private ParameterWrapper[] mParameters;

    public static final Parcelable.Creator<Mail> CREATOR
            = new Parcelable.Creator<Mail>() {
        public Mail createFromParcel(Parcel in) {
            Mail mail = new Mail();
            mail.readFromParcel(in);
            return mail;
        }
        public Mail[] newArray(int size) {
            return new Mail[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeLong(mTimeStamp);
        parcel.writeInt(mPid);
        parcel.writeParcelable(mObject, flags);
        parcel.writeParcelable(mMethod, flags);
        parcel.writeParcelableArray(mParameters, flags);
    }

    public void readFromParcel(Parcel in) {
        mTimeStamp = in.readLong();
        mPid = in.readInt();
        ClassLoader classLoader = Mail.class.getClassLoader();
        mObject = in.readParcelable(classLoader);
        mMethod = in.readParcelable(classLoader);
        Parcelable[] parcelables = in.readParcelableArray(classLoader);
        if (parcelables == null) {
            mParameters = null;
        } else {
            int length = parcelables.length;
            mParameters = new ParameterWrapper[length];
            for (int i = 0; i < length; ++i) {
                mParameters[i] = (ParameterWrapper) parcelables[i];
            }
        }

    }

    private Mail() {

    }

    public Mail(long timeStamp, ObjectWrapper object, MethodWrapper method, ParameterWrapper[] parameters) {
        mTimeStamp = timeStamp;
        mPid = Process.myPid();
        mObject = object;
        mMethod = method;
        mParameters = parameters;
    }

    public int getPid() {
        return mPid;
    }

    public ParameterWrapper[] getParameters() {
        return mParameters;
    }

    public ObjectWrapper getObject() {
        return mObject;
    }

    public MethodWrapper getMethod() {
        return mMethod;
    }

    public long getTimeStamp() {
        return mTimeStamp;
    }

}

回顾一下AIDL通信,AIDL接口支持的参数类型除了基本类型、List、Map、AIDL接口就是Parcelable,而Mail这个实现Parcelable的类将Object封装在内部。

所以HermesEventBus本质上也是通过AIDL实现跨进程通信,依赖于主进程的Service载体。使用HermesEventBus省去了自己实现跨进程通信的烦恼。

小结几种跨进程方法

  • BroadCastReceiver 由于面向整个系统注册的广播,跨进程消耗较大,性能不能保证。
  • ContentProvider 支持跨进程数据共享
  • AIDL 客户端调用AIDL接口是同步并且带返回结果的,如果执行时间较长,客户端的调用线程会一直等待。服务端执行AIDL接口是异步的,支持所有基本类型、AIDL接口、Parcelable、List、Map等类型的参数,实现起来繁琐。
  • Messenger 本质是AIDL通信,客户端发送Message后不带返回结果,服务端接收到Message是通过一个线程的Handler轮询MessageQueue处理的,因此处理Message是在同一线程。
  • HermesEventBus 本质也是AIDL通信,不需要自己实现绑定Service,发送事件也是不带返回结果的,使用简单。
  • Binder机制 Android跨进程通信实现的核心,AIDL就是基于Binder机制实现的,其中transact方法是客户端向服务端发送消息,onTransact方法是客户端接收服务端的消息。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容