进程间通信:bindService的替换方案

作者: goeasyway | 来源:发表于2016-09-07 07:47 被阅读1807次

随着项目的业务和复杂度的增大,对内存的压力越来越明显,有时不得不使用多进程的方案,将一些功能放到另一个进程中去完成。其实很多时候,简单的业务也需要开一个单独的进程,如音乐播放器。

我们就以音乐播放器为例,播放音乐的实现功能部份我们往往放在Service中去做,并把这个Service运行在一个单独的进程中(通过设置android:process属性)。这样做的好处是,音乐播放器的UI部份在一个进程,可以退出释放内存。而Service部份所在的进程没有UI资源,占用的内存也较少。

麻烦一点的就是,每次UI进程打开Activity界面时需要使用bindService来绑定Service,才能和它进行通讯,如读取现在的播放进度,控制暂停快进等功能。

其实在音乐播放器这个场景中,Service这个方案是很合理的。但有些时候,我们需要调用一个远程进程中的功能,但是想在一个同步方法中调用,而且不能排除用户是在主线程调用。这种情况如果用Service的方案来实现,那么,我们只能通过bindService去绑定Service获取它的服务接口(Binder实例),但问题出在这个bindService是异步方法,调用后要在它的回调方法ServiceConnection中才能获取到它的服务接口。

如果我们想同步调用一个远程进程中提供的方法,应该怎么办呢?

我有想过像系统提供的服务一样,如ActivityManagerService,在系统服务注册,然后提供给Client端同步调用。

后来一想,这种方案大复杂了。很多时候我们单独进程中跑的功能并不复杂,有点杀鸡用牛刀的感觉。然后就是查找一些资料,没查到什么好的方案,我就去看一下ContentProvider的源码,想看看它是怎么实现,其实他和我的需求很像,可以进程间访问,每个方法都是同步的,需要时才启动,就是方法要封装成insert/delete/query这样的。后来一看,ContentProvider有一个call方法,完全可以实现我的需求。

    @Override
    public Bundle call(String method, String arg, Bundle extras) {
        if (extras == null) {
            return null;
        }
        extras.setClassLoader(GoeasywayContentProvider.class.getClassLoader());
        CallArgs callBundleArgs = extras.getParcelable(METHOD_CALL_ARGS);
        ......

注意代码中的extras.setClassLoader,如果我们需要用extras传递自定义的Pacelable对象,那么这里要指定一下它的ClassLoader,不然这里会无法解封CallArgs(自定义的Pacelable对象)。

可以用method区分每个方法,参数可以放到Bundle中。返回值也是一个Bundle对象,可以在返回值也传递一个自己定义的Pacelable对象。我把参数和返回值放在一个Pacelable中:

public class CallArgs implements Parcelable {

    public String method;
    public Object[] methodArgs;
    public Object result;

    public CallArgs() {

    }

    protected CallArgs(Parcel in) {
        readFromParcel(in);
    }

    public static final Creator<CallArgs> CREATOR = new Creator<CallArgs>() {
        @Override
        public CallArgs createFromParcel(Parcel in) {
            return new CallArgs(in);
        }

        @Override
        public CallArgs[] newArray(int size) {
            return new CallArgs[size];
        }
    };

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(method);
        dest.writeArray(methodArgs);
        dest.writeValue(result);
    }

    public void readFromParcel(Parcel in) {
        method = in.readString();
        methodArgs = in.readArray(CallArgs.class.getClassLoader());
        result = in.readValue(CallArgs.class.getClassLoader());
    }
}

访问就是通过getContentResolver().call这个方法,我们简单的封装了一下:

    private CallArgs callRemoteMethod(String method, Object... args) {
        CallArgs CallArgs = new CallArgs();
        CallArgs.method = method;
        CallArgs.methodArgs =  args;
        android.os.Bundle bundleArgs = new android.os.Bundle();
        bundleArgs.putParcelable(METHOD_CALL_ARGS, CallArgs);
        android.os.Bundle bundleResult = hostContext.getContentResolver().call(
            CALL_URI, // 一个Uri,和使用ContentProvider的query方法一样的
            method, null, bundleArgs);

        if (bundleResult == null) {
            return null;
        }
        bundleResult.setClassLoader(CallArgs.class.getClassLoader());
        CallArgs result = bundleResult.getParcelable(METHOD_CALL_RESULT);
        return result;
    }       

这个使用起来就像一个简单的API调用,我们用很少的代码就实现了一个进程间的同步调用,不用但心远程进程已经被系统KILL掉,Android系统会帮我们重新创一个进程并继续之前的调用。

这里要注意:

  1. 在进程间传递的对象都是现实Parcelable接口,即CallArgs方法中的参数(methodArgs)和返回值(result)都应该实现Parcelable接口。
  1. 进程间通信是通过Binder机制进行的,每个调用线程都会被block,不管它是主线程还是子线程,直到Binder的服务端(本例中是ContentProvider)的binder线程执行完成返回给调用端时,线程才会继续运行。所以如果ContentProvider中的操作较耗时,最好是用子线程去调用。
  2. 每个Binder线程能传输的数据是有大小受Binder缓冲区的大小限制的,一般一个线程最大能占用128KB。超过这个限制会报TransactionTooLargeException异常。

相关文章

  • 进程间通信:bindService的替换方案

    随着项目的业务和复杂度的增大,对内存的压力越来越明显,有时不得不使用多进程的方案,将一些功能放到另一个进程中去完成...

  • 进程通信AIDL之Binder分析

    AIDL 进程间通信 bindService(intent,serviceConnection,0); handl...

  • 9.12

    Android进程间通信步骤:应用程序填写Intent,调用bindService发送请求bindService通...

  • Android进程间通信(七)——客户端调用服务端onTrans

    在Android进程间通信(六)——普通进程的bindService流程[https://www.jianshu....

  • Android进程间通信之bindService

    在Android中binder是一种非常重要的进程间通信方式。基于binder实现的进程间通信形态非常多,其中An...

  • android 多进程下service通讯 Messenger

    解决当应用内有多个进程时通讯问题 服务基本上分为两种形式: 所以 进程间通信需要 bindService();bi...

  • 二、并发通信

    (一)进程间的通信 1、进程间的内存资源是隔离的互不干扰的。(进程之间是相互独立的) 2、进程之间通信的解决方案...

  • linux进程间通信(1)

    一、进程通信概述 1、什么是进程间通信?什么是线程间通信? 进程间通信: 进程间通信就指的是用户空间中进程A与进程...

  • Android 进程通信bindService详解

    Android系统涉及到许多进程间通信,也提供了实名及匿名Binder,例如:AMS是属于实名Binder,在系统...

  • 进程间的通信

    进程间的通信主要分为本机器进程间的通信和不同机器间进程的通信。本文主要描述本机进程间的通信。 一、传统Linux的...

网友评论

  • 折跃:但是在这个里面,content provider 会有 native crash会导致整个应用崩溃的情况,而在 remote service 的 jni crash 不会导致整个应用程序崩溃呀,有其它的替代方法没?
  • 真的放飞自我:如果是在子线程bindService,然后阻塞线程,等待onServiceConnected唤醒线程,这样也可以
  • dbfafe7b24d9:看上去是个很棒的想法,但是尝试了,未能实现,不知道大神能否提供个Demo做参考?
  • 本格拉的风:这个貌似不错,大神能不能讲的更清楚一些,最好来个demo之类
  • ab5ddc97ce84:楼主我有些问题请教下,音乐播放器那个场景的话,你的意思是使用ContentProvider call 进行远程调用操作音乐播放器的播放暂停,就不用使用service了? 还是说mediaplay还是在service中,activity 调用ContentProvider call ,在ContentProvider call 中调用service的方法操作 播放暂停 不知道我表达的是否清楚,
    dbfafe7b24d9:@ab5ddc97ce84 我使用aidl的场景也比较多,不知道这位仁兄有没有什么好的版本来解决同步调用的问题?
    ab5ddc97ce84:@goeasyway 感谢你能这么快的回复我,我现在的理解就是使用ContentProvider call 进行远程同步调用方法 。不过我不太能想到在什么场景下使用这种方式,是否能帮我举个例子呢。我现在一般用aidl 远程调用方法,我看这篇之后,我想说是否能把我之前的aidl 换成ContentProvider call 实现,但是我不知道如何去改。我看网上大部分都是在ContentProvider 中操纵数据库。数据库是在ContentProvider 的oncreate中初始化。 我是在service中操作一个串口,使用aidl 接口提供给其他应用操作串口 。那我是否直接在ContentProvider 的oncreate去初始化我的串口, 但是ContentProvider 的生命周期我不是很清楚,那是这个场景不太合适使用ContentProvider 来实现。问题有点多 :smile:
    goeasyway:@ab5ddc97ce84 音乐播放器这个场景较简单,也不需要同步方法访问Service的实例(Binder实例),直接用Service的方案就可以了。文中所提的ContentProvider call方式是给那些想用同步的方法获取调用远程进程的某个方法。即进程A 想要调用进程B中的某个方法(假设为b_fun),如果用Service的话,A不能直接调用b_fun,而是要等bindService成功回调ServiceConnection后获得Service的实例才能调service.b_fun,而有些时候我们就需要在A进程直接调用b_fun,显然用Service的方法做不到,因为bindService没有成功回调之前,service的实例无法获取。阻塞当前线程来等bindService的回调执行明显不行,因为我们可能要在主线程去调用b_fun,主线程被阻塞了,bindService的回调也会被阻塞。
  • WHOKNOWME:的确是一个很好的思路
  • 5fb654356853:能贴下源代码的下载地址吗
    933c3148b98e:@goeasyway 请问作者写过demo了吗?可以看看嘛?
    5fb654356853:@goeasyway 好的,好久没写内容提供者了,只看代码片段还是有点难以理解~
    goeasyway:@土豆斯基 这是我自己项目里面的片段,没有demo。我空的话写一个吧,到时通知你。

本文标题:进程间通信:bindService的替换方案

本文链接:https://www.haomeiwen.com/subject/qnhtjttx.html