标签:填充 含义 多个 lse 怎样 远程服务器 ade 个人 bug
目录整个app都在一个进程有什么弊端?
微信,微博等主流app是如何解决这些问题的?
下面我们使用adb查看一下微信和微博的进程信息(Android 5.0以下版本可直接在“设置 -> 应用程序”相关条目中查看):
进入adb shell后,使用 “ps | grep 条目名称” 可以过滤出想要查看的进程。
可以看到,微信的确有一个tools进程,而新浪微博也有image相关的进程,而且它们当中还有好些其它的进程,比如微信的push进程,微博的remote进程等,这里可以看出,他们不单单只是把上述的WebView、图库等放到单独的进程,还有推送服务等也是运行在独立的进程中的。
可见,合理使用多进程不仅仅是有多大好处的问题,我个人认为而且是很有必要的。
所以说,我们最好还是根据自身情况,考虑一下是否需要拆分进程。这也是本文的初衷:给大家提供一个多进程的参考思路,在遇到上述问题和场景的时候,可以考虑用多进程的方法来解决问题,又或者,在面试的时候,跟面试官聊到这方面的知识时候也不至于尴尬。
为什么需要“跨进程通讯”
Android的进程与进程之间通讯,有些不需要我们额外编写通讯代码,例如:把选择图片模块放到独立的进程,我们仍可以使用startActivityForResult方法,将选中的图片放到Bundle中,使用Intent传递即可。(看到这里,你还不打算把你项目的图片选择弄到独立进程么?)
但是对于把“消息推送Service”放到独立的进程,这个业务就稍微复杂点了,这个时候可能会发生Activity跟Service传递对象,调用Service方法等一系列复杂操作。
由于各个进程运行在相对独立的内存空间,所以它们是不能直接通讯的,因为程序里的变量、对象等初始化后都是具有内存地址的,举个简单的例子,读取一个变量的值,本质是找到变量的内存地址,取出存放的值。
跨进程的通讯方式有哪些
下面开始对AIDL的讲解,各位道友准备好渡劫了吗?
使用AIDL使用一个跨进程消息推送
像图片选择这样的多进程需求,可能并不需要我们额外编写进程通讯的代码,使用四大组件传输Bundle就行了,但是像推送服务这种需求,进程与进程之间需要高度的交互,此时就绕不过进程通讯这一步了。下面我们就用即时聊天软件为例,手动去实现一个多进程的推送例子,具体需求如下:
实现思路
先来整理一下实现思路:
例子具体实现
为了阅读方便,下文中代码将省略非重点部分,可以把本文完整代码Clone到本地再看文章:
Step0. AIDL调用流程概览
开始之前,我们先来概括一下使用AIDL进行多进程调用的整个流程:
整个AIDL调用过程概括起来就以上3个步骤,下文中我们使用上面描述的例子,来逐步分解这些步骤,并讲述其中的细节。
Step1.客户端使用bindService方法绑定服务端
1.1 创建客户端和服务端,把服务端配置到另外的进程
上面描述的客户端、服务端、以及把服务端配置到另外进程,体现在AndroidManifest.xml中,如下所示:
开启多进程的方法很简单,只需要给四大组件指定android:process标签。
1.2 绑定MessageService到MainActivity
创建MessageService:
此时的MessageService就是刚创建的模样,onBind中返回了null,下一步中我们将返回一个可操作的对象给客户端。
客户端MainActivity调用bindService方法绑定MessageService
这一步其实是属于Service组件相关的知识,在这里就比较简单地说一下,启动服务可以通过以下两种方式:
bindService & startService区别:使用bindService方式,多个Client可以同时bind一个Service,但是当所有Client unbind后,Service会退出,通常情况下,如果希望和Service交互,一般使用bindService方法,使用onServiceConnected中的IBinder对象可以和Service进行交互,不需要和Service交互的情况下,使用startService方法即可。
正如上面所说,我们是要和Service交互的,所以我们需要使用bindService方法,但是我们希望unbind后Service仍保持运行,这样的情况下,可以同时调用bindService和startService(比如像本例子中的消息服务,退出UI进程,Service仍需要接收到消息),代码如下:
Stpe2.服务端在onBind方法返回Binder对象
2.1 首先,什么是Binder?
要说Binder,首先要说一下IBinder这个接口,IBinder是远程对象的基础接口,轻量级的远程过程调用机制的核心部分,该接口描述了与远程对象交互的抽象协议,而Binder实现了IBinder接口,简单说,Binder就是Android SDK中内置的一个多进程通讯实现类,在使用的时候,我们不用也不要去实现IBinder,而是继承Binder这个类即可实现多进程通讯。
2.2 其次,这个需要在onBind方法返回的Binder对象从何而来?
在这里就要引出本文中的主题了——AIDL多进程中使用的Binder对象,一般通过我们定义好的 .adil 接口文件自动生成,当然你可以走野路子,直接手动编写这个跨进程通讯所需的Binder类,其本质无非就是一个继承了Binder的类,鉴于野路子走起来麻烦,而且都是重复步骤的工作,Google提供了 AIDL 接口来帮我们自动生成Binder这条正路,下文中我们围绕 AIDL 这条正路继续展开讨论(可不能把人给带偏了是吧)
2.3 定义AIDL接口
很明显,接下来我们需要搞一波上面说的Binder,让客户端可以调用到服务端的方法,而这个Binder又是通过AIDL接口自动生成,那我们就先从AIDL搞起,搞之前先看看注意事项,以免出事故:
AIDL支持的数据类型:
其他注意事项:
下面继续我们的例子,开始对AIDL的讲解~
2.4 创建一个AIDL接口,接口中提供发送消息的方法(Android Studio创建AIDL:项目右键 -> New -> AIDL -> AIDL File),代码如下:
一个比较尴尬的事情,看了很多文章,从来没有一篇能说清楚in、out、inout这三个参数方向的意义,后来在stackoverflow上找到能理解答案(https://stackoverflow.com/questions/4700225/in-out-inout-in-a-aidl-interface-parameter-value),**我翻译一下大概意思:**
上述的MessageModel为消息的实体类,该类在AIDL中传递,实现了Parcelable序列化接口,代码如下:
手动实现Parcelable接口比较麻烦,安利一款AS自动生成插件android-parcelable-intellij-plugin创建完MessageModel这个实体类,别忘了还有一件事要做:”在AIDL中传递的对象,需要在类文件相同路径下,创建同名、但是后缀为.aidl的文件,并在文件中使用parcelable关键字声明这个类“。代码如下:
对于没有接触过aidl的同学,光说就能让人懵逼,来看看此时的项目结构压压惊:
我们刚刚新增的3个文件:
OK,相信此时懵逼已解除~
2.5 在服务端创建MessageSender.aidl这个AIDL接口自动生成的Binder对象,并返回给客户端调用,服务端MessageService代码如下:
MessageSender.Stub是Android Studio根据我们MessageSender.aidl文件自动生成的Binder对象(至于是怎样生成的,下文会有答案),我们需要把这个Binder对象返回给客户端。
2.6 客户端拿到Binder对象后调用远程方法
调用步骤如下:
此时客户端代码如下:
在客户端中我们调用了MessageSender的sendMessage方法,向服务端发送了一条消息,并把生成的MessageModel对象作为参数传递到了服务端,最终服务端打印的结果如下:
这里有两点要说:
到这里,我们已经完成了最基本的使用AIDL进行跨进程方法调用,也是Step.0的整个细化过程,可以再回顾一下Step.0,既然已经学会使用了,接下来…全剧终。。。
如果写到这里全剧终,那跟咸鱼有什么区别…
知其然,知其所以然
我们通过上述的调用流程,看看从客户端到服务端,都经历了些什么事,看看Binder的上层是如何工作的,至于Binder的底层,这是一个非常复杂的话题,本文不深究。(如果看到这里你又想问什么是Binder的话,请手动倒带往上看…)
我们先来回顾一下从客户端发起的调用流程:
抛开其它无关代码,客户端调跨进程方法就这两个步骤,而这两个步骤都封装在 MessageSender.aidl 最终生成的 MessageSender.java 源码(具体路径为:build目录下某个子目录,自己找,不爽你来打我啊 )
请看下方代码和注释,前方高能预警…
只看代码的话,可能会有点懵逼,相信结合代码再看下方的流程图会更好理解:
从客户端的sendMessage开始,整个AIDL的调用过程如上图所示,asInterface方法,将会判断onBind方法返回的Binder是否存处于同一进程,在同一进程中,则进行常规的方法调用,若处于不同进程,整个数据传递的过程则需要通过Binder底层去进行编组(序列化,传输,接收和反序列化),得到最终的数据后再进行常规的方法调用。
敲黑板:对象跨进程传输的本质就是 序列化,传输,接收和反序列化 这样一个过程,这也是为什么跨进程传输的对象必须实现Parcelable接口
跨进程的回调接口
新建消息收取的AIDL接口MessageReceiver.aidl:
接下来我们把回调接口注册到服务端去,修改我们的MessageSender.aidl:
以上就是我们最终修改好的aidl接口,接下来我们需要做出对应的变更:
客户端变更,修改MainActivity:
客户端主要有3个变更:
下面对服务端MessageServie进行变更:
服务端主要变更:
这里还有一个需要讲一下的,就是RemoteCallbackList,为什么要用RemoteCallbackList,普通ArrayList不行吗?当然不行,不然干嘛又整一个RemoteCallbackList ,registerReceiveListener 和 unregisterReceiveListener在客户端传输过来的对象,经过Binder处理,在服务端接收到的时候其实是一个新的对象,这样导致在 unregisterReceiveListener 的时候,普通的ArrayList是无法找到在 registerReceiveListener 时候添加到List的那个对象的,但是它们底层使用的Binder对象是同一个,RemoteCallbackList利用这个特性做到了可以找到同一个对象,这样我们就可以顺利反注册客户端传递过来的接口对象了。
到此,服务端通知客户端相关的代码也写完了,运行结果无非就是正确打印就不贴图了,可以自己Run一下,打印的时候注意去选择不同的进程,不然瞪坏屏幕也没有日志。
DeathRecipient
你以为这样就完了?too young too simple…
不知道你有没有感觉到,两个进程交互总是觉得缺乏那么一点安全感…比如说服务端进程Crash了,而客户端进程想要调用服务端方法,这样就调用不到了。此时我们可以给Binder设置一个DeathRecipient对象,当Binder意外挂了的时候,我们可以在DeathRecipient接口的回调方法中收到通知,并作出相应的操作,比如重连服务等等。
DeathRecipient的使用如下:
在客户端MainActivity声明DeathRecipient:
Binder中两个重要方法:
此外,Binder中的isBinderAlive也可以判断Binder是否死亡
权限验证
就算是公交车,上车也得嘀卡对不,如果希望我们的服务进程不想像公交车一样谁想上就上,那么我们可以加入权限验证。
介绍两种常用验证方法:
自定义permission,在Androidmanifest.xml中增加自定义的权限:
服务端检查权限的方法:
根据不同进程,做不同的初始化工作
相信前一两年很多朋友还在使用Android-Universal-Image-Loader来加载图片,它是需要在Application类进行初始化的。打个比如,我们用它来加载图片,而且还有一个图片选择进程,那么我们希望分配更多的缓存给图片选择进程,又或者是一些其他的初始化工作,不需要在图片选择进程初始化怎么办?
这里提供一个简单粗暴的方法,博主也是这么干的…直接拿到进程名判断,作出相应操作即可:
每个进程创建,都会调用Application的onCreate方法,这是一个需要注意的地方,我们也可以根据当前进程的pid,拿到当前进程的名字去做判断,然后做一些我们需要的逻辑,我们这个例子,拿到的两个进程名分别是:
总结
结语
这篇文章断断续续写了很久,而且我相信真正使用起来的同学可能不多,选择这样一个话题我是吃力不讨好… 但是我还是希望可以在这里提供一个完整的解决方案给大家。简单的多进程使用,而且效果显著的,比如把图片选择和WebView配置到独立的进程,这个我希望可以大家行动起来。这篇文章的知识点非常多,理解起来可能不是太容易,如果有兴趣,我建议你手动去写一下,然后不理解的地方,打断点看看是什么样的运行步骤。
对于面试的同学,如果在面试过程中说到多进程,跟面试官聊得开,估计也是能加点分的,或者在实际工作中,一些使用多进程可以更好地解决问题的地方,你可以在会议中拍桌猛起,跟主管说:“我有一个大胆的想法…”,这样装 X 也不错(当然,被炒了的话就不关我的事了…)
文章不易,如果大家喜欢这篇文章,或者对你有帮助希望大家多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
花费2年,字数超一万最佳总结教你Android多进程,微信微博都在用
标签:填充 含义 多个 lse 怎样 远程服务器 ade 个人 bug
原文地址:https://blog.51cto.com/14775360/2484407