最后
为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,没有人能随随便便成功。
加油,共勉。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
- 让客户端和服务端具备交互的能力。(AIDL使用);
例子具体实现
为了阅读方便,下文中代码将省略非重点部分,可以把本文完整代码Clone到本地再看文章:
Step0. AIDL调用流程概览
开始之前,我们先来概括一下使用AIDL进行多进程调用的整个流程:
- 客户端使用bindService方法绑定服务端;
- 服务端在onBind方法返回Binder对象;
- 客户端拿到服务端返回的Binder对象进行跨进程方法调用;
整个AIDL调用过程概括起来就以上3个步骤,下文中我们使用上面描述的例子,来逐步分解这些步骤,并讲述其中的细节。
Step1.客户端使用bindService方法绑定服务端
1.1 创建客户端和服务端,把服务端配置到另外的进程
- 创建客户端 -> MainActivity;
- 创建服务端 -> MessageService;
- 把服务端配置到另外的进程 -> android:process=”:remote”
上面描述的客户端、服务端、以及把服务端配置到另外进程,体现在AndroidManifest.xml中,如下所示:
开启多进程的方法很简单,只需要给四大组件指定android:process标签。
1.2 绑定MessageService到MainActivity
创建MessageService:
此时的MessageService就是刚创建的模样,onBind中返回了null,下一步中我们将返回一个可操作的对象给客户端。
客户端MainActivity调用bindService方法绑定MessageService
这一步其实是属于Service组件相关的知识,在这里就比较简单地说一下,启动服务可以通过以下两种方式:
- 使用bindService方法 -> bindService(Intent service, ServiceConnection conn, int flags);
- 使用startService方法 -> startService(Intent 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 这条正路继续展开讨论(可不能把人给带偏了是吧)
[图片上传中…(image-63404a-1591606905444-27)]
2.3 定义AIDL接口
很明显,接下来我们需要搞一波上面说的Binder,让客户端可以调用到服务端的方法,而这个Binder又是通过AIDL接口自动生成,那我们就先从AIDL搞起,搞之前先看看注意事项,以免出事故:
AIDL支持的数据类型:
- Java 编程语言中的所有基本数据类型(如 int、long、char、boolean 等等)
- String和CharSequence
- Parcelable:实现了Parcelable接口的对象
- List:其中的元素需要被AIDL支持,另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口
- Map:其中的元素需要被AIDL支持,包括 key 和 value,另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口
其他注意事项:
- 在AIDL中传递的对象,必须实现Parcelable序列化接口;
- 在AIDL中传递的对象,需要在类文件相同路径下,创建同名、但是后缀为.aidl的文件,并在文件中使用parcelable关键字声明这个类;
- 跟普通接口的区别:只能声明方法,不能声明变量;
- 所有非基础数据类型参数都需要标出数据走向的方向标记。可以是 in、out 或 inout,基础数据类型默认只能是 in,不能是其他方向。
下面继续我们的例子,开始对AIDL的讲解~
2.4 创建一个AIDL接口,接口中提供发送消息的方法(Android Studio创建AIDL:项目右键 -> New -> AIDL -> AIDL File),代码如下:
一个比较尴尬的事情,看了很多文章,从来没有一篇能说清楚in、out、inout这三个参数方向的意义,后来在stackoverflow上找到能理解答案(stackoverflow.com/questions/4…我翻译一下大概意思:
- 被“in”标记的参数,就是接收实际数据的参数,这个跟我们普通参数传递一样的含义。在AIDL中,“out” 指定了一个仅用于输出的参数,换而言之,这个参数不关心调用方传递了什么数据过来,但是这个参数的值可以在方法被调用后填充(无论调用方传递了什么值过来,在方法执行的时候,这个参数的初始值总是空的),这就是“out”的含义,仅用于输出。
- 而“inout”显然就是“in”和“out”的合体了,输入和输出的参数。区分“in”、“out”有什么用?这是非常重要的,因为每个参数的内容必须编组(序列化,传输,接收和反序列化)。in/out标签允许Binder跳过编组步骤以获得更好的性能。
上述的MessageModel为消息的实体类,该类在AIDL中传递,实现了Parcelable序列化接口,代码如下:
手动实现Parcelable接口比较麻烦,安利一款AS自动生成插件android-parcelable-intellij-plugin创建完MessageModel这个实体类,别忘了还有一件事要做:”在AIDL中传递的对象,需要在类文件相同路径下,创建同名、但是后缀为.aidl的文件,并在文件中使用parcelable关键字声明这个类“。代码如下:
对于没有接触过aidl的同学,光说就能让人懵逼,来看看此时的项目结构压压惊:
我们刚刚新增的3个文件:
- MessageSender.aidl -> 定义了发送消息的方法,会自动生成名为MessageSender.Stub的Binder类,在服务端实现,返回给客户端调用
- MessageModel.java -> 消息实体类,由客户端传递到服务端,实现了Parcelable序列化
- MessageModel.aidl -> 声明了MessageModel可在AIDL中传递,放在跟MessageModel.java相同的包路径下
OK,相信此时懵逼已解除~
2.5 在服务端创建MessageSender.aidl这个AIDL接口自动生成的Binder对象,并返回给客户端调用,服务端MessageService代码如下:
[图片上传中…(image-692e97-1591606905443-21)]
MessageSender.Stub是Android Studio根据我们MessageSender.aidl文件自动生成的Binder对象(至于是怎样生成的,下文会有答案),我们需要把这个Binder对象返回给客户端。
2.6 客户端拿到Binder对象后调用远程方法
调用步骤如下:
- 在客户端的onServiceConnected方法中,拿到服务端返回的Binder对象;
- 使用MessageSender.Stub.asInterface方法,取得MessageSender.aidl对应的操作接口;
- 取得MessageSender对象后,像普通接口一样调用方法即可。
此时客户端代码如下:
在客户端中我们调用了MessageSender的sendMessage方法,向服务端发送了一条消息,并把生成的MessageModel对象作为参数传递到了服务端,最终服务端打印的结果如下:
这里有两点要说:
- 服务端已经接收到客户端发送过来的消息,并正确打印;
- 服务端和客户端区分两个进程,PID不一样,进程名也不一样;
到这里,我们已经完成了最基本的使用AIDL进行跨进程方法调用,也是Step.0的整个细化过程,可以再回顾一下Step.0,既然已经学会使用了,接下来…全剧终。。。
如果写到这里全剧终,那跟咸鱼有什么区别…
知其然,知其所以然
我们通过上述的调用流程,看看从客户端到服务端,都经历了些什么事,看看Binder的上层是如何工作的,至于Binder的底层,这是一个非常复杂的话题,本文不深究。(如果看到这里你又想问什么是Binder的话,请手动倒带往上看…)
我们先来回顾一下从客户端发起的调用流程:
- MessageSender messageSender = MessageSender.Stub.asInterface(service);
- messageSender.sendMessage(messageModel);
抛开其它无关代码,客户端调跨进程方法就这两个步骤,而这两个步骤都封装在 MessageSender.aidl 最终生成的 MessageSender.java 源码(具体路径为:build目录下某个子目录,自己找,不爽你来打我啊 )
请看下方代码和注释,前方高能预警…
只看代码的话,可能会有点懵逼,相信结合代码再看下方的流程图会更好理解:
从客户端的sendMessage开始,整个AIDL的调用过程如上图所示,asInterface方法,将会判断onBind方法返回的Binder是否存处于同一进程,在同一进程中,则进行常规的方法调用,若处于不同进程,整个数据传递的过程则需要通过Binder底层去进行编组(序列化,传输,接收和反序列化),得到最终的数据后再进行常规的方法调用。
敲黑板:对象跨进程传输的本质就是 序列化,传输,接收和反序列化 这样一个过程,这也是为什么跨进程传输的对象必须实现Parcelable接口
跨进程的回调接口
在上面我们已经实现了从客户端发送消息到跨进程服务端的功能,接下来我们还需要将服务端接收到的远程服务器消息,传递到客户端。有同学估计会说:“这不就是一个回调接口的事情嘛”,设置回调接口思路是对的,但是在这里使用的回调接口有点不一样,在AIDL中传递的接口,不能是普通的接口,只能是AIDL接口,所以我们需要新建一个AIDL接口传到服务端,作为回调接口。
新建消息收取的AIDL接口MessageReceiver.aidl:
接下来我们把回调接口注册到服务端去,修改我们的MessageSender.aidl:
以上就是我们最终修改好的aidl接口,接下来我们需要做出对应的变更:
- 在服务端中增加MessageSender的注册和反注册接口的方法;
- 在客户端中实现MessageReceiver接口,并通过MessageSender注册到服务端。
客户端变更,修改MainActivity:
客户端主要有3个变更:
- 增加了messageReceiver对象,用于监听服务端的消息通知;
- onServiceConnected方法中,把messageReceiver注册到Service中去;
- onDestroy时候解除messageReceiver的注册。
下面对服务端MessageServie进行变更:
服务端主要变更:
- MessageSender.Stub实现了注册和反注册回调接口的方法;
- 增加了RemoteCallbackList来管理AIDL远程接口;
- FakeTCPTask模拟了长连接通知客户端有新消息到达。(这里的长连接可以是XMPP,Mina,Mars,Netty等,这里弄个假的意思意思,有时间的话咱开个帖子聊聊XMPP)
-
这里还有一个需要讲一下的,就是RemoteCallbackList,为什么要用RemoteCallbackList,普通ArrayList不行吗?当然不行,不然干嘛又整一个RemoteCallbackList ,registerReceiveListener 和 unregisterReceiveListener在客户端传输过来的对象,经过Binder处理,在服务端接收到的时候其实是一个新的对象,这样导致在 unregisterReceiveListener 的时候,普通的ArrayList是无法找到在 registerReceiveListener 时候添加到List的那个对象的,但是它们底层使用的Binder对象是同一个,RemoteCallbackList利用这个特性做到了可以找到同一个对象,这样我们就可以顺利反注册客户端传递过来的接口对象了。
-
RemoteCallbackList在客户端进程终止后,它能自动移除客户端所注册的listener,它内部还实现了线程同步,所以我们在注册和反注册都不需要考虑线程同步,的确是个666的类。(至于使用ArrayList的幺蛾子现象,大家可以自己试试,篇幅问题,这里就不演示了)
到此,服务端通知客户端相关的代码也写完了,运行结果无非就是正确打印就不贴图了,可以自己Run一下,打印的时候注意去选择不同的进程,不然瞪坏屏幕也没有日志。
DeathRecipient
你以为这样就完了?too young too simple…
尾声
在我的博客上很多朋友都在给我留言,需要一些系统的面试高频题目。之前说过我的复习范围无非是个人技术博客还有整理的笔记,考虑到笔记是手写版不利于保存,所以打算重新整理并放到网上,时间原因这里先列出面试问题,题解详见:
展示学习笔记
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
)]
[外链图片转存中…(img-FQShoM7y-1715680656953)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!