Android 中的 IPC 机制

无论哪个平台,线程,进程 都是绝对的重点跟难点。

为什么要使用多进程?

一个应用默认只有一个进程,进程的名称就是应用的包名,进程是系统分配资源和调度的基本单位,每个进程都有自己独立的资源和内存空间,其它进程不能任意访问当前进程的内存和资源,系统给每个进程分配的内存会有限制。

如果一个进程占用内存超过了这个内存限制,就会报OOM的问题,很多涉及到大图片的频繁操作或者需要读取一大段数据在内存中使用时,很容易报OOM的问题,如果此时在程序中人为地使用GC会严重影响程序运行的流畅性,并且有时候并没有什么卵用,多数时候我们可以在 android:minSdkVersion=”11”及以上的应用中,给AndroidManifest.xml的Application标签增 加”android:largeHeap=”true”“这句话,请求系统给该应用分配更多可申请的内存:


但是这种做法有弊端:

·        有时候并不能彻底解决问题,比如API Level小于11时,或者是应用需要的内存比largeHeap分配的更大的时候;当该应用在后台时,仍然占用着的内存,系统总的内存就那么多,如果每个应用都向系统申请更多的内存,会影响整机运行效率。

为了彻底地解决应用内存的问题,Android引入了多进程的概念,它允许在同一个应用内,为了分担主进程的压力,将占用内存的某些页面单独开一个进程,比如Flash、视频播放页面,频繁绘制的页面等。Android多进程使用很简 单,只需要在AndroidManifest.xml的声明四大组件的标签中增加”android:process”属性即可,process分私有进程 和全局进程

由于Android系统中应用程序之间不能共享内存。因此,在不同应用程序之间交互数据(跨进程通讯)就稍微麻烦一些。在android SDK中提供了4种用于跨进程通讯的方式。这4种方式正好对应于Android系统中的四大组件

Ø  Activity可以跨进程调用其他应用程序的Activity;

Ø  Content Provider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;

Ø  Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播;

Ø  Service和ContentProvider类似,也可以访问其他应用程序中的数据,但不同的是,Content Provider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。

 

Android-IPC简介

IPCInter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程,我们首先需要知道进程与线程都是什么?是两个截然不同的概念。操作系统中的描述:线程是CPU调度的最小单元,是有限的系统资源。而进程是指一个执行单元,在移动设备上指一个应用。进程和线程是包含于被包含的关系。一个进程中可以只有一个线程,即主线程(UI线程),很多时候,一个进程中需要执行大量耗时任务,如果放在主线程执行,会造成界面无响应(ANR),所以耗时任务放在其他线程中即可。

IPC不是Android中独有的,任何一个操作系统都需要相应的IPC机制,比如Linux上可以通过命名管道、共享内存,信号量等进行进程间通信。对于Android来说,是基于Linux内核的系统,进程间通信方式并不能完全继承自Linux, 它自己实现了一套轻量级的IPC机制——Binder

通过Binder可以轻松实现进程间通信。当然,还有Socket也可以实现任意两个终端之间通信。

多进程的情况分为两种:

l  第一种:是一个应用因为某些原因自身需要采用多进程模式来实现,比如某些模块需要运行在单独的进程中,又或者为了加大可使用的内存所以需要多进程获取多份内存空间(Android早期对一些版本显示内存16MB,不同设备不同大小)。

l  第二种情况是当前应用需要向其他应用获取数据,由于是两个应用,必须跨进程获取。我们通过ContentProvider去查询数据的时候也是一种进程间通信。

Android中的多进程模式

开启多进程模式

通过给四大组件指定android:process属性,可以轻易开启多进程模式(只有这一种方式)。看起来虽然很简单。但实际却暗藏杀机

我们无法给一个线程和一个实体类指定其运行时所在的进程,其实还有另一种非常规的多进程方法,就是通过JNI在native层去fork一个新的进程(特殊情况,暂时不考虑)

JNI是本地语言编程接口-(中间语言的翻译官)。它允许Java代码和用C、C++或汇编写的本地代码相互操作。

命名,android:process=”:remote”, “:”的含义是指要在当前的进程名前面附加上当前的包名,这是一种简写的方法,进程名以“:”开头的属于当前应用的私有进程,其他应用的组件不可以和它跑在一个进程中,不以“:”开头的为全局进程,其他进程通过ShareUID方式和它跑在一个进程中。

查看进程:

除了在DDMS视图中查看进程信息,还可以通过shell,命令为:adb shell ps

或者 adb shell ps | grep 包名 

多进程模式的运行机制

       所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进程所带来的主要影响。正常情况下四大组件中间不可能不通过一些中间层来共享数据,那么通过简单地指定进程名来开启多进程都会无法正确运行。当然,在特殊情况下,某些组件之间不需要共享数据,这个时候可以直接指定android:process属性来开启多进程,但这种场景不常见,几乎所有情况都需要共享数据。

一般来说,使用多进程会造成如下几方面的问题:

1.     静态成员和单例模式完全失效

2.     线程同步机制完全失效

3.     SharedPreferences的可靠性下降

4.     Application会多次创建

为了解决以上问题,系统提供了很多跨进程通信的方法,虽然不能直接的共享内存,但是还是可以实现数据交互。实现跨进程通信的方式很多,比如通过Intent传递数据,共享文件和SharedPreferences,基于Binder的Messenger和AIDL,以及Socket等。

Binder

         Binder是个很深入的话题,不打算深入探讨,因为它太复杂了,下面简单的介绍它的使用及上层原理。

       直观来说,Binder是Android中的一个类,实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式。还可以理解为一种虚拟物理设备,设备驱动是:/dev/binder。Linux中是没有的;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager和WindowManager等)和响应ManagerService的桥梁;从Android应用层来说,它是客户端和和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个对象,客户端就可以获取服务端的数据和服务了(包含普通服务和AIDL的服务)。

Binder机制中的四个组件Client、Server、Service Manager和Binder驱动程序的关系如下图所示:


1.     Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中

2.     Binder驱动程序和Service Manager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server

3.     Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信

4.     Client和Server之间的进程间通信通过Binder驱动程序间接实现

5.     ServiceManager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力

       Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,较简单;而Messenger的底层其实是AIDL,所以下面选择用AIDL来分析Binder工作机制。我们需要一个AIDL示例,SDK会自动为我们生产AIDL所对应的Binder类,新建三个文件,Book.java Book.aidl  IBookManager.aidl 代码如下所示:

 


Book.java只是一个实体类实现了Parcelable接口,不做展示了。

Book.aidl 是Bool在AIDL中的声明。

IBookManager.aidl 使我们定义的一个接口,其中getBookList用于从远程服务端获得图书列表,addbook是远程添加信息。

我们可以看出,尽管Book类已经和IBoolManager在同一包中,还是要倒包,这是AIDL的特殊之处,下面是系统为IBookManager生成的Binder类,gen目录下包中有一个同名的.java的文件。接下来我们分析Binder的工作原理,代码如下:




上述代码是系统生成的,它继承了IInterface这个接口,同时它自己也是个接口,所有可以在Binder中传输的接口都需要继承IInterface接口,这个类看起来逻辑混乱,实则非常清晰。首先,它声明了两个方法getBookList和addBook。同时还用id标识这两个方法,id用于标识在transact过程中客户端所请求的到底是哪个方法,接着声明了一个内部类Stub,这个Stub就是一个Binder类!当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,反之,需要走transact过程。这个逻辑由Stub的内部代理类Proxy来完成。这么看来,这个IBookManager接口的确很简单,但是我们应该认识到这个接口的核心实现类就是它的内部类Stub和其内部代理类Proxy。下面是对这两个内部类的每个方法的含义:

DESCRIPTOR

Ø  Binder的唯一标识,一般用当前Binder的类名表示,比如本例中的"com.ryg.chapter_2.aidl.IBookManager"

asInterface ( android.os.IBinder  obj )

Ø  用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回系统封装后的Stub.proxy对象

asBinder

Ø  此方法用于返回当前的Binder对象

onTransact

Ø  这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理。该方法原型为public boolean onTransact(int code, android.os.Parcel data, android.os.Parcelreply, int flags),服务端通过code可以确定客户端所请求的目标,接着从data中取出目标方法所需的参数,然后执行目标方法。当执行完毕后,就向 reply 中写入返回值,onTransact方法的执行过程就是这样的,需要注意的是,如果此方法返回false,客户端请求会失败,因此可以在此来做权限验证,毕竟我们也不希望随便一个应用都能远程调用我们的服务。

l  Proxy#getBookList

Ø  运行在客户端,调用此方法时,它的内部实现是:首先,创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中,接着调用transact方法来发起RPC(远程过程调用)请求,同事当前线程挂起;然后服务端的onTransact方法会被调用,知道RPC过程返回,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。

通过上面的分析,大概了解到了Binder的工作机制,但是有两点需要额外说明下:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务器端进程返回数据,所以这样耗时的操作不可以放到主线程 中;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。下面是Binder的工作机制图:

                                      

Binder的工作机制

从上述分析,我们完全可以不提供AIDL文件即可实现Binder,之所以使用AIDL,完全是为了方便,AIDL不是实现Binder的必需品。如果是我们手写的Binder,那服务端只需要创建一个BookManagerImpl的对象并在Service的onBind访访中返回即可。AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具,仅此而已。

我们知道Binder运行在服务端进程,如果由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(Binder死亡),会导致远程调用失败。会影响到客户端的功能。为了解决这个问题,Binder提供了两个很重要的方法linkToDeath和unlinkToDeath,

可以给Binder设置一个死亡代理,当死亡时,我们就会收到通知,就可以重新发起请求从而恢复连接。

       首先,声明一个DeathRecipient对象。它是一个接口,其内部只有一个方法binderDied,我们需要实现,当Binder死亡的时候,系统会回调binderDied方法,然后我们就可以移除之前绑定的binder代理并重新绑定远程服务:


其次,在客户端绑定远程服务成功后,给binder设置死亡代理:


上图第二个参数是个标记为,直接设0即可。经过上述两个步骤,就成功设置了死亡代理,另外,通过Binder的方法isBinderAlive也可以判断Binder是否死亡

到这里,IPC的基础知识就介绍完了,下面才是进入正题:形形色色的进程间通信方式

Android中的IPC方式

Android中具体的跨进程通信方式有很多,比如可以通过在Intent中附加Extras来传递信息,或者通过共享文件的方式共享数据,还可以采用Binder来跨进程通信,另外,ContentProvider天生就支持跨进程访问的,因此可以刻采用它来进行IPC。此外还可以通过网络通信也是可以的,所以Socket也可实现IPC,下面一 一介绍。

使用Bundle

四大组件中三大组件都是支持在Intent中传递Bundle的(除ContentProvider)

由于Bundle实现了Parcelable接口,所以可以在不同进程间传递数据。基于这一点,当我们在一个进程中启动了另一个进程的Activity、Service、Receiver,就可以在Bundle中附加我们需要传递给远程进程的信息通过Intent发送出去,当然,需要数据能够被序列化。这是一种最简单的进程间通信方式

使用文件共享

两个进程通过IO流的方式读/写同一个文件来交换数据也是一种IPC方式

Android系统基于Linux,使得其并发读/写文件可以没有限制的进行,甚至两个进程或线程同时对一个文件操作都是允许的,但是要妥善处理并发问题了

当然,SharedPreferences是个特例,它是Activity中提供的轻量级存储方案,通过XML的键值对的方式存储数据,生成的路径:data/data/包名/shared_prefs 下,但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一份它的缓存,因此在多进程模式下,系统对它的读/写变得不靠谱,面对高并发的读/写访问,有很大几率会丢失数据,因此不建议在进程间通信使用SharedPreferences。

使用Messenger

       信使,顾名思义,通过它可以在不同进程中传递Message对象,在Message中放入需要传递的数据,就轻松实现数据的进程间传递。Messenger是轻量级的IPC方案,底层实现是AIDL,这点在它的构造方法中就可以看得出,不管是IMessenger还是Stub.asInterface,这种使用方法都表明它底层是AIDL:


它对AIDL进行了封装,使我们可以简便的进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端不存在并发执行的情形。

以下是工作机制图:


使用方式:服务端:


客户端:


使用AIDL

上节介绍了使用Messenger,可以发现Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然是一个一个处理,如果有大量的并发请求,那这种方式就不合适了。同时,Messenger的作用主要是为了传递消息,很多时候我们需要跨进程调用服务端的方法,Messenger是无法做到的。这个时候可以使用AIDL来实现跨进程方法调用。有了Binder的基础,就能更容易地理解AIDL,一下介绍使用流程:

1.     服务端:

创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件。在文件中声明暴露给客户端的接口,最后在service中实现这个接口即可

2.     客户端

首先需要绑定Service,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

在AIDL文件中,并不是所有的数据类型都是可以使用的,以下是AIDL所支持的:

l  基本数据类型

l  String和CharSequence

l  List:只支持ArrayList,里面的元素也必须都是AIDL所支持的

l  Map:只支持HashMap,里面的元素也必须都是AIDL所支持的

l  Parcelable

l  AIDL:它本身当然也可以在AIDL中使用

值得注意的是,如果用到自定义的Parcelable对象,那必须定义自定义对象的.aidl文件,并在其中声明为Parcelable类型的,并且同一个包下的.aidl也是需要引包。

使用ContentProvider

天生就适合进程间通信,和Messenger一样,底层也是Binder实现的

Binder线程池

请看相关博客

http://www.cnblogs.com/angeldevil/p/3296381.html

选择合适的IPC方式

名称

优点

缺点

适用场景

Bundle

简单易用

只能传输Bundle支持的数据类型

四大组件间的进程间通信

文件共享

简单易用

不适合高并发场景,并且无法做到进程间的即时通信

无并发访问情形,交换简单的数据实时性不高的场景

AIDL

功能强大,支持一对多并发通信,支持实时通信

使用稍复杂,需要处理好线程同步

一对多通信且有RPC需求

Messenger

功能一般,支持一对多串行通信,支持实时通信

不能很好处理高并发情形,不只是RPC,数据通过Message进行传输,只能传输Bundle支持的数据类型

低并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求

ContentProvider

在数据库访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作

可以理解为受约束的AIDL,主要提供数据源的CRUD操作

一对多的进程间的数据共享

Socket

功能强大,可以通过网络传输字节流,支持一对多并发实时通信

实现细节稍微有点繁琐,不只是直接的RPC

网络数据交换

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值