android中的IPC机制
前言:碎片化学习是不好的习惯,必须整理总结成自己的知识体系
一.进程,线程
1.进程与线程之间的关系
从操作系统的角度来说:进程是系统资源分配的基本单位,线程是CPU调度最小的调度单位.一个进程可以包含多个线程。
从应用进程的角度来说:一个应用进程被Zygote fork出来,就已经有了一些默认的线程,比如最重要的a)主线程:应用的生命周期函数都是在主线程中调用;b)binder线程:binder线程是应用中最重要的基础线程,它负责与ams跨进程沟通,完成组件的创建,启动,销毁,同时请求系统的服务,如JobSchecduleService.
eg-1:system_server进程中的众多线程
system 2050 632 2381384 148564 unix_strea 0000000000 S system_server
system 2052 2050 2381384 148564 do_sigtime 0000000000 S Signal Catcher
system 2054 2050 2381384 148564 futex_wait 0000000000 S ReferenceQueueD
system 2056 2050 2381384 148564 futex_wait 0000000000 S FinalizerDaemon
system 2058 2050 2381384 148564 futex_wait 0000000000 S FinalizerWatchd
system 2060 2050 2381384 148564 futex_wait 0000000000 S HeapTaskDaemon
system 2593 2050 2381384 148564 binder_thr 0000000000 S Binder:2050_1
system 2600 2050 2381384 148564 binder_thr 0000000000 S Binder:2050_2
system 2938 2050 2381384 148564 SyS_epoll_ 0000000000 S android.bg
system 2939 2050 2381384 148564 SyS_epoll_ 0000000000 SActivityManager
system 2940 2050 2381384 148564 SyS_epoll_ 0000000000 S android.ui
system 2941 2050 2381384 148564 SyS_epoll_ 0000000000 S ActivityManager
system 2942 2050 2381384 148564 SyS_epoll_ 0000000000 S android.fg
system 2943 2050 2381384 148564 inotify_re 0000000000 S FileObserver
system 2944 2050 2381384 148564 SyS_epoll_ 0000000000 S android.io
system 2945 2050 2381384 148564 SyS_epoll_ 0000000000 S android.display
system 2946 2050 2381384 148564 futex_wait 0000000000 S CpuTracker
system 2947 2050 2381384 148564 SyS_epoll_ 0000000000 SPowerManagerSer
system 2948 2050 2381384 148564 pm_get_wak 0000000000 S system_server
system 2949 2050 2381384 148564 futex_wait 0000000000 S BatteryStats_wa
system 3005 2050 2381384 148564 SyS_epoll_ 0000000000 SPackageManager
eg-2:一个应用进程中的众多线程
USER PID PPID VSIZE RSS WCHAN PC NAME
u0_a55 4104 308 772924 28336 sys_epoll_ b6d2999c S com.xxx.xxx
u0_a55 4109 4104 772924 28336 do_sigtime b6d29c70 S Signal Catcher 这个线程是用来捕获linux信号和做一些后续处理的。如SIGBUS
u0_a55 4110 4104 772924 28336 poll_sched b6d29b8c S JDWP
u0_a55 4111 4104 772924 28336 futex_wait b6d00644 S ReferenceQueueD
u0_a55 4112 4104 772924 28336 futex_wait b6d00644 S FinalizerDaemon
u0_a55 4113 4104 772924 28336 futex_wait b6d00644 S FinalizerWatchd
u0_a55 4114 4104 772924 28336 futex_wait b6d00644 S HeapTaskDaemon
u0_a55 4115 4104 772924 28336 binder_thr b6d29ac8 S Binder:4104_1 binder线程池
u0_a55 4116 4104 772924 28336 binder_thr b6d29ac8 S Binder:4104_2
u0_a55 4121 4104 772924 28336 inotify_re b6d2a978 S FileObserver
u0_a55 4124 4104 772924 28336 sys_epoll_ b6d2999c S local_job_dispa
u0_a55 4164 4104 772924 28336 sys_epoll_ b6d2999c S remote_job_disp
2.Android中的多线程模型
[2.1]Android 中的多进程一般指一个应用中存在多个进程的情况。我们可以通过指定
android :process属性,让一个应用的不同组件运行在指定进程。
以“:”开头的进程属于当前应用的私有进程,其它应用的组件不能通过ShareUID与它跑在一个进程
指定全名的进程属于全局进程。其它应用的组件可以通过ShareUID与它跑在一个进程.
[2.2]多进程带来的问题
Android 为每个应用进程分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,所以运行在不同进程中的四大组件是不同通过内存来共享数据。多进程之间组件一定要共享数据吗?不一定,但是我们只要使用多进程模型,一般都是需要跨进程通信的。
一般来说,使用多进程会造成以下几个问题:(参考任玉刚老师的书):
1.静态成员和单例完全失效
解释:同一个类在不同进程中拥有不同的地址空间,虽然类名,类变量一样,但是分配的地址不一样,所以同一个类(指类名称)在不同的进程中是完全不同的类了。
2.线程同步机制失效
解释:不同地址空间,不同类,所以锁对象,锁全局类都不同了
3.Application会多次创建
当指定一个组件跑在另外一个进程中时,Zygote会fork新的进程,当然会分配独立的虚拟机,整个过程就是启动了一个应用的过程。创建了一个新的Application对象。
总结:运行在同一个进程中的组件属于同一个虚拟机同一个Application。多进程中不同进程的组件拥有不同的虚拟机,不同的Application,不同的内存地址。
[2.3]多进程之间的通信
由于不同进程所拥有的地址是两块不同的地址空间,所以不能直接通过共享内存共享数据了。直接上Linux,Android的通信方式:
Linux常用跨进程通信方式:管道,信号量,共享内存,socket 。。。
Android常用跨进程通信方式:Intent ,共享文件,SharedPreferences,Binder,socket,基于Binder的Messenger.下面详细学习进程间的通信方式。
二,进程之间的通信方式
1.Binder
Binder
是安卓中最重要的IPC通信方式了,一个应用进程的创建,应用进程中组件的生命周期的调度,系统服务的使用...处处都是binder,从而也可以看出,
安卓中进程间的通信是非常频繁的,一句话来说,在Binder通信机制的强有力支持下,安卓进程间进行了友好的数据交互。
Binder
Binder印象
Binder是进程间通信的一种架构,这个架构分为服务端接口,客户端接口,Binder驱动
1.1
aidl工具会把应用程序中的aidl文件生成一个非常复杂的java类。拆分
由aidl生成的java文件(一个接口文件;该接口文件中嵌套一个抽象的类Stub,该类实现外边的接口文件,没有实现具体接口方法,接口方法由其子类
实现;抽象类中有一个proxy类,该类是抽象Stub的代理类,同时也实现了最外层的接口文件,proxy文件的作用是将客户端的输入包装成统一形式,
具体的业务实现在服务端
Stub的子类中)
由aidl文件系统帮我们生成的三个文件,
a) 接口 IXXX extends IInterface
b) abstract class Stub extends Binder imp IXXX
该类重载了binder的 OnTransact方法:
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags){
.................
case TRANSACTION_startScan:
{
data.enforceInterface(DESCRIPTOR); //校验
java.lang.String[] _arg0;
_arg0= data.createStringArray(); //读取参数
com.miui.guardprovider.aidl.IVirusObserver _arg1;
_arg1= com.miui.guardprovider.aidl.IVirusObserver.Stub.asInterface(data.readStrongBinder());//读取参数
boolean _arg2;
_arg2= (0!=data.readInt());//读取参数
int _result = this.startScan(_arg0, _arg1, _arg2);//调用服务端服务函数
reply.writeNoException();
reply.writeInt(_result);//返回给客户端的结果
return true;
}
............................
}
c) static class Proxy imp IXXX
服务端的代理类,跨进程通信中,客户端使用代理类访问服务。要想使用服务端,首先要获取服务端在Binder驱动中对应的mRemote变量的引用
int
startScan(java.lang.String[] paths,
com.miui.guardprovider.aidl.IVirusObserver virusObserver, boolean
isCloud) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);//对应服务端data.enforceInterface(DESCRIPTOR)
_data.writeStringArray(paths);//写入参数
_data.writeStrongBinder((((virusObserver!=null))?(virusObserver.asBinder()):(null)));////写入参数
_data.writeInt(((isCloud)?(1):(0)));////写入参数
mRemote.transact(Stub.TRANSACTION_startScan, _data, _reply, 0);//调用
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
一个完整的通信过程:客户端获取服务端的Binder对象引用 ,然后调用相关服务。
客户端得到binder对象引用,通过 transact()将调用目标函数的参数写入包裹,再通过底层binder驱动转发到服务端,经过OnTransact分发调用具体实现,最终调用到service中的具体业务实现。
a)客户端如何获得服务端的Binder对象引用
系统服务:使用getSystemService()方法获取的服务,系统服务一般不使用Service类实现,一般都是基于binder类,可以仅使用Binder类扩展系统服务。系统服务由ServiceManager来管理,系统服务在使用前向ServiceManager进行注册,客户端使用服务时向ServiceManager获取服务的引用。注意ServiceManager也是一个系统服务,它的代理架构跟其他系统服务一样的
应用程序自定义服务:客户端服务则必须基于Service类来编写,通过bindService获取相关服务的binder引用,asInterface提供了统一的查询接口,如果IPC通信,返回Proxy对象引用,如果进程内部使用服务则返回 Stub的具体实现类对象引用。
自定义服务需要注册吗?
两种获取方式最终都是通过AMS服务查询得到服务端的Binder引用
b)binder 和 Service的关系
在Service中实现具体的业务,然后通过Binder载体将具体业务的实现暴露给客户端。binder框架在整个通信过程中不做具体的业务实现,整
个框架只负责运输数据,将客户端的请求精确传达给服务端,同时将服务端的处理结果传递给客户端。只做数据传输不做具体业务
c)Framework层的binder架构与native层的binder架构之间的关系
framework
层的binder通过JNI调用native的binder架构,所以framework层(Java层)对native层(c/c++层)进行了一层包
装,提供给应用层进行调用。native层binder是C/S架构,framework层的架构与相关类的设计原理与native层类似。所以理解了任
意一层都很好理解另一层.
1.2 binder的死亡监听
onServiceDisconnected()
1.3 aidl的权限验证
androidmanifest.xml文件中定义permission.在onBind函数中进行权限校验
2.ContentProvider
ContentProvider底层实现是binder
ContentProvider的唯一标识android:authorities="xxx"
ContentProvider是对外界提供数据的,为了对数据进行保护,所以需要相关权限验证
第三方应用进程访问时需要声明访问权限。(由此想到,不管是service,ContentProvider,还是broadcast,对访问者来说他们都是一种受保护的资源,所以整个访问过程中需要添加权限校验。不能随便访问。要安全的获取数据和服务,android是一个开源的系统,在开源的系统上防范变得很重要)
contentprovider是提供数据访问的接口,至于底层数据的组织可以由Sqlite数据库存储组织,内存List组织,MatrixCursor组织
contentprovider通过Uri来区分外界要访问的数据集合
一般为了区分要访问的数据:定义唯一的Uri 和Uri_code,再通过UriMatcher将两者关联起来。
数据访问流程:根据Uri--->Uri_code--->表名--->访问数据
需要注意的是CRUP四大方法是存在多线程并发访问,因此方法内部要做好线程同步。
如果底层数据是一块内存的话,如List,特别要做好数据同步。
3.Socket
通过Socket套接字进行跨进程的通信,eg:让服务端循环监听一个端口,客户端请求连接服务端。Socket本身支持传输任意字节流
4.文件