进程间通信:bindService的替换方案
来源:互联网 发布:现代网络情诗 编辑:程序博客网 时间:2024/06/17 09:14
随着项目的业务和复杂度的增大,对内存的压力越来越明显,有时不得不使用多进程的方案,将一些功能放到另一个进程中去完成。其实很多时候,简单的业务也需要开一个单独的进程,如音乐播放器。
我们就以音乐播放器为例,播放音乐的实现功能部份我们往往放在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系统会帮我们重新创一个进程并继续之前的调用。
这里要注意:
- 在进程间传递的对象都是现实Parcelable接口,即CallArgs方法中的参数(methodArgs)和返回值(result)都应该实现Parcelable接口。
- 进程间通信是通过Binder机制进行的,每个调用线程都会被block,不管它是主线程还是子线程,直到Binder的服务端(本例中是ContentProvider)的binder线程执行完成返回给调用端时,线程才会继续运行。所以如果ContentProvider中的操作较耗时,最好是用子线程去调用。
- 每个Binder线程能传输的数据是有大小受Binder缓冲区的大小限制的,一般一个线程最大能占用128KB。超过这个限制会报TransactionTooLargeException异常。
原文链接:http://www.jianshu.com/p/d30e333d6e2e
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
- 进程间通信:bindService的替换方案
- 1 bindService的启动 2通过AIDL来进行进程间的通信
- 从AILD与bindService谈Binder进程间通信原理(上)
- 从AILD与bindService谈Binder进程间通信原理(下)
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- 进程间的通信
- Qt 串口类QSerialPort 使用笔记
- 为了回答一个小学生问题
- 探秘5G新空口技术
- windows上cmd用ftp简单上传文件
- android的aar使用
- 进程间通信:bindService的替换方案
- redis扩展安装
- 怎么利用pdf转换器将pdf转换成word
- android(日历+价格)思路简单,实现方便,一看就懂.我是1年安卓新人.
- OpenCV中 IplImage 与 Mat
- 判断回文字符串
- Lamp配置
- 内部排序算法汇总
- iOS异常处理