参考资料:
《Android开发艺术探索》
https://note.youdao.com/old-web/#/file/recent/note/1862018FBC3B483E96CAB46B92811EF9/328432cea4f2eeddc18f0ca0446558d9
-
为什么进程间要通信
数据传输:一个进程需要将它的数据发送给另一个进程。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
-
进程通信方式
-
使用 Bundle/Intent传递数据
四大组件的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便的支持在不同的进程间传输。
-
使用 文件共享
两个进程通过读/写同一个文件来交换数据。Android系统是基于Linux,使得其并发读写文件可以没有限制的进行,甚至两个线程同时对一个文件进行操作都是可以的,所以说,文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且需要妥善处理好并发读写问题。顺便一提:Windows下不支持并发读或写。
-
使用 Messenger(基于Binder)
一种轻量级的IPC方案,它的底层实现就是AIDL。Messager是以串行的方式处理客户端发来的消息,如果有大量的消息同时发送给服务器,服务器只能一个个处理。双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。
-
服务端进程
Messenger是基于AIDL实现的,服务端(被动方)创建一个Service来处理客户端(主动方)连接,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的 onBind() 时返回Messenger对象底层的Binder即。
-
客户端进程
首先需要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messager,通过这个Messager就可以向服务端发送消息了。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handle并创建一个新的Messager,并把这个Messager对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。 Messager是以串行的方式处理客户端发来的消息,如果有大量的消息同时发送给服务器,服务器只能一个个处理。双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。
-
-
使用 AIDL(基于Binder)
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可,具体的实现是写在Stub中,用Binder对象传递给客户端。
客户端首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转换为AIDL接口所属的类型,用asInterface的形式,接着就可以调用AIDL中的方法了。
AIDL支持的数据类型:基本数据类型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL;
某些类即使和AIDL文件在同一个包中也要显式import进来;
AIDL中除了基本数据类,其他类型的参数都要标上方向:in、out或者inout;
AIDL接口中支持方法,不支持声明静态变量;
为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另一个应用的时候,可以直接把整个包复制到客户端工程中。
RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自IInterface接口。
-
使用 ContentProvider(基于Binder)
系统四大组件之一,专门提供用于不同应用间进行数据共享的方式,底层也是Binder实现,但是它的使用过程比AIDL简单很多,因为系统已经帮我们做了一次封装。对于系统应用的一些信息,只需要通过ContentResolver的CRUD就可以即可。对于自定义的ContentProvider,需要实现6个抽象方法,onCreate、CRUD、getType,onCreate代表ContentProvider的创建,由系统回调并运行在主线程里,其他方法是运行在Binder线程池之中的。getType()用来返回一个Uri请求所对应的MIME类型,比如图片、视频等。 ContentProvider主要以表格的形式来组织数据,并且可以包含多个表;还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider;ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行;要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者,unregisterContentObserver来解除观察者。
-
使用 Socket
套接字,分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中TCP和UDP协议。需要注意,Android不允许在主线程中请求网络,而且请求网络必须要注意声明相应的permission。然后,在服务器中定义ServerSocket来监听端口,客户端使用Socket来请求端口,连通后就可以进行通信。
TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过"三次握手"才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传功能,因此具有很高的稳定性。
UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能,在性能上,UDP具有更好的效率,其缺点是不保证数据能够正确传输,尤其是在网络拥塞的情况下。
https://note.youdao.com/yws/public/resource/328432cea4f2eeddc18f0ca0446558d9/xmlnote/7D78A97B9AF24E6B94871208D4372247/23490补充:
Binder连接池
当项目规模很大的时候,创建很多个Service是不对的做法,因为service是系统资源,太多的service会使得应用看起来很重,所以最好是将所有的AIDL放在同一个Service中去管理。整个工作机制是:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。
Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service去执行,从而避免了重复创建Service的过程。
建议在AIDL开发工作中引入BinderPool机制。当新业务模块加入新的AIDL,那么在它实现自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,给自己添加一个新的binderCode并返回相对应的Binder对象即可,不需要添加新的Service。
-
-
进程与线程的区别
img进程是系统进行资源分配和调度的一个独立单位, 线程是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,最小的 CPU 执行单元。
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
进程切换时,消耗的资源大,效率不高。所以涉及到频繁的切换时,使用线程要好于进程。
作用:进程,使多个程序并发执行,以提高系统的资源利用率和吞吐量;线程,减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
如果是出于面试的角度,我们需要关注的点有哪些呢?
进程的执行过程是线状的,尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。线程的改变只代表了 CPU 执行过程的改变,而没有发生进程所拥有的资源变化。计算机内的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源。
-
Android如何启动多线程
在Android中使用多进程只有一种方法,那就是在AndroidManifest中给四大组件指定 android:process 属性。除此之外没有其他的办法,也就是说我们无法给一个线程活一个实体类指定其运行时所在的进程。其实还有另一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程,但这种方法属于特殊情况,并不是常用的创建多进程的方式,所以我们也暂不考虑这种情况。
-
进程优先级
在安卓系统中:当系统内存不足时,Android系统将根据进程的优先级选择杀死一些不太重要的进程,优先级低的先杀死。进程优先级从高到低如下。
-
前台进程
处于正在与用户交互的activity
与前台activity绑定的service
调用了startForeground()方法的service
正在执行oncreate(),onstart(),ondestroy方法的 service。
进程中包含正在执行onReceive()方法的BroadcastReceiver。
系统中的前台进程并不会很多,而且一般前台进程都不会因为内存不足被杀死。特殊情况除外。当内存低到无法保证所有的前台进程同时运行时,才会选择杀死某个进程。
-
可视进程
为处于前台,但仍然可见的activity(例如:调用了onpause()而还没调用onstop()的activity)。典型情况是:运行activity时,弹出对话框(dialog等),此时的activity虽然不是前台activity,但是仍然可见
- 可见activity绑定的service。(处于上诉情况下的activity所绑定的service)
可视进程一般也不会被系统杀死,除非为了保证前台进程的运行不得已而为之。
-
服务进程
- 已经启动的service
-
后台进程
- 不可见的activity(调用onstop()之后的activity)
后台进程不会影响用户的体验,为了保证前台进程,可视进程,服务进程的运行,系统随时有可能杀死一个后台进程。当一个正确实现了生命周期的activity处于后台被杀死时,如果用户重新启动,会恢复之前的运行状态。
-
空进程
- 任何没有活动的进程
系统会杀死空进程,但这不会造成影响。空进程的存在无非为了一些缓存,以便于下次可以更快的启动。
-