Android开发艺术探索 第二章IPC机制
- Linux中IPC通信方式?
答:命名管道,共享内存,信号量(具体再细看) - Android中的IPC通信方式?
答:从宏观分的话有两种:Binder和Socket
细分的话:
从四大组件来说有4种:Activity:Intent;Service:Binder(AIDL);Receive:broadcast;ContentProvider:content provider
从表现形式方面有6种:1.Bundle 2.文件共享 3.使用Message 4.使用AIDL 5.使用ContentProvider 6.使用Socket - Android为什么要采用多进程?
答:
① 一个应用因为某些原因自身需要采用多进程实现:
<1>某些模块由于特殊原因要运行在单独进程中(举例:某一模块activity配置android:process为共有进程)
、<2>为了加大一个应用可以使用的内存
② 当前应用需要向其他应用获取数据 - Android如何运行多进程模式?
答:在四大组件中指定 android:process属性(出JNI外只有这一种)【默认的进程名是包名】 - android:process两种命名方式(“:”和完整命名)的区别?
答:
以“:”开头的进程: 属于当前进程的私有进程,其他进程的组件不能和他跑在同一进程中
完整命名的进程: 其他应用可以通过ShareUID方式和他跑在用一个进程中 - Android系统会给每一个应用分配一个UID,只有具有相同UID的应用才能共享数据
- 两个应用通过shareUID跑在同一进程的要求:ShareUID相同且签名也相同
- Android为每个应用分配一个独立的虚拟机,不同的虚拟机中访问同一个对象会产生多份副本
- 多进程带来的主要影响:所有运行在不同进程中的四大组件,只要他们通过内存共享数据,都会共享失败
- 使用多进程会出现的问题:
(1)静态成员和单例模式完全失效
(2)线程同步机制完全失效
(3)SharedPreferences的可靠性下降(不支持两个进程同时执行读/写操作)
(4)会多次常见Application - 一个应用中多进程的实质:相当于两个不同的应用采用了SharedUID的模式
- Serializable接口和Parcelable接口的异同?
答:
(1)Serializable接口是Java提供的,Parcelable接口是Android提供的序列化方式
(2)Serializable接口使用简单但是开销较大,存在大量I/O操作,Parcelable开销小,主要用在内存序列化上
(3)通过Parcelable将对象序列化到存储设备中或者对象序列化后通过网络传输也可以,但实现上比较复杂(怎么复杂??),推荐用Serializable - Serializable中serialVersionUID有什么作用?
答:serialVersionUID是用来标识序列化对象能否进行反序列化。 当对象被序列化后,会存储对象的UID,反序列化时只有UID相同才可以反序列化成功,否则会报错
注:手动确定serialVersionUID有以下优点:当序列化对象增加或删除属性时,仍然可以保证反序列化成功。但如果类结构发生改变(修改了类名或者修改了成员变量的类型),则序列化失败
注:①静态成员变量属于类不属于对象,所以不会参与序列化过程 ②用transient关键字标记的成员变量不参与序列化过程 - Binder的定义
答:从IPC角度来说:Binder是Android中一种跨进程的通信方式
从Android Framework角度来说:Binder是ServiceManaget连接各种Manager和相应ManagerService的桥梁
从应用层角度来说:Binder是客户端和服务器端通信的媒介 - AIDL与普通Java接口文件的特殊之处:当AIDL需要用到类对象时,类需要实现Parcelable接口,并且类文件和I***Manager在相同包中时,仍然要导入类
- AIDL生成Java文件中主要方法的作用:
(1)asInterface:客户端调用,用于将服务器端的Binder对象转化成客户端所需的AIDL接口类型的对象。这一方法是区分进程的,如果客户端和服务端位于同一进程,则直接返回Stub对象本身,如果不在同一进程,则返回的是系统封装后的Stub.proxy对象
(2)asBinder:返回当前的Binder对象
(3)onTransact:在服务器端调用。①客户端发起请求时,远程请求会通过系统底层封装后交给此方法执行;②该方法通过传入的code确定应该执行哪一个方法,然后从data中取出客户端的请求参数;③然后执行目标方法;④执行完毕后将结果写入reply中返回
(4)Proxy#方法:在客户端调用。①将方法参数写入到_data中;②然后调用IBinder.transact方法,发起RPC(远程过程调用);③接着将当前线程挂起;④RPC结束返回后,将结果从reply中取出 - 跨进程通信注意的要点:
(1)客户端发起RPC请求后会一直挂起直至服务器端进程返回数据,因此不能运行在UI线程中
(2)由于服务器端的Binder方法运行在线程池中,所以Binder方法不管是否耗时都应该采用同步方法实现 - RPC过程中Binder异常终止怎么办?
答:Binder中提供了linkToDeath和unlinkToDeath方法,通过linkToDeath可以给Binder设置一个死亡代理,当服务器端Binder发生异常中断时,会收到通知,这是可以重新发送请求而恢复连接 -
重点:Android中IPC方式:
答: - 使用Bundle
Intent中设置Action,添加Bundle进行通信
注意点:如果A进程进行计算,计算结果传给B进程,但Bundle不支持结果类型怎么办?
答:将在A进程进行的计算转移到B进程中的一个Service中进行,这样可以成功避免进程间通信问题 - 使用文件共享
注意点:(1)文件共享下IPC会出现什么问题?
答:并发读写问题。如果并发读写,那么我们读出的数据就可能不是最新的。所以文件共享进行IPC通信只能在并发度不高的进程之间通信。
(2)SharedPreferences是一个特例,他采用XML形式,存储键值对。但系统对读写有一定的缓存策略,即在内存中有一份SharedPreferences文件的缓存,这样多进程读写就会很不可靠,因此一般不采用 - 使用Message
Message是一种轻量级的IPC解决方案(他只能传递“消息”,不能调用方法),底层是用AIDL实现的。不考虑线程同步的问题,因为他一次只处理一个请求。
实现方法:
3.1 服务端进程
①在服务端创建一个Service来处理客户端的连接请求
②创建一个Handler并通过它来创建一个Messenger对象
③在Service的onBind中返回这个Messenger对象底层的Binder即可
3.2 客户端进程
①绑定服务端的Service
②用服务端返回的IBinder对象创建一个Messenger(可以用来发送消息,对象为Message)---单向传递
③如果需要服务端能回应客户端,即客户端可以接收消息,需要在客户端创建Handler并新建一个Messenger,把这个Messenger对象通过Message的replyTo参数传递给服务端
④客户端的Handler对Message对出动作
注意:
(1)Message中只能传递基础数据类型和系统规定实现Parcelable接口的对象,自定义对象不能通过它传递,所以Message常常和Bundle一起使用
(2)使用Message进行AIDL通信的缺点?
答:1)Message是以串行的方式处理客户端发来的消息的,如果有大量的并发消息,仍然只能一个一个处理,效率很低;
2)Message主要是用来传递消息的,有时后我们需要调用服务端的方法,这种方式就不适用,可以采用AIDL方式 - 使用AIDL(重!)
Android Interface Design Language
实现方法:
4.1 服务端
①创建一个Service监听连接请求
②创建AIDL文件
③用Service实现这个文件即可
4.2 客户端
①绑定服务端的Service
②将返回的Binder对象转化为AIDL接口所属的类型
注意:
(1)AIDL文件用到了自定以的Parcelable对象,必须新建一个和它同名的AIDL文件,并在其中声明他为Parcelable类型
(2)AIDL自定义的Parcelable对象必须把java文件和定义的AIDL文件显式的import进来
(3)AIDL中除了基本数据类型,其它类型的参数必须标上方向:in out 或者 inout
(4)AIDL和传统接口的区别是什么?
答:AIDL接口中只支持方法,不支持申明静态常量
(5)为什么客户端的AIDL包要和服务端一致?
答:因为客户端需要发序列化服务端中和接口相关的类,如果类的完整路径不一致的话就无法完成反序列化
(6)AIDL中需要实现线程同步,实现的方法?
答:CopyOnWriteArrayList<同步对象>实现自动的线程同步---引出:Android中实现线程同步的方式?
(7)由于AIDL调用产生的ANR问题
答:客户端在调用服务端的方法时,可能因为服务端的方法需要很久才能完成(>5s),此时会产生ANR问题(同步调用?)---引出Android中ANR的产生场景及解决*
(8)AIDL中的难点:
难点1:客户端每次要调用服务端方法,如果需求比较大,频繁的调用会极大损耗性能,这时候可以考虑实现观察者模式。当客户端关注的数据发生变化时,服务端通知给客户端,客户端再进行业务处理。比如:每个客户端的请求listener传递给服务端,服务端用一个list保存,当数据变化时进行依次通知,客户端再用listener进行回调处理。这里要注意两点:
①服务端回调客户端listener的方法,但这个方法的实现是在客户端的Binder线程池中执行的,因此,为了便于UI操作,我们需要有一个Handler可以切换到主线程中执行
②Binder进行对象传输实际上是通过序列化和反序列化进行,也就是Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然在客户端注册和解注册使用的是同一个对象,但因为是两次传输,所以解注册与注册时的对象不一致,这就使得listener解注册时对象为空,这时候就可以使用RemoteCallbackList
难点2:跨进程实现解注册的方法是什么?
答:使用RemoteCallbackList ----引出RemoteVIew RemoteCallbackList通过查看源码,底层是通过Map存储的,key是IBinder类型,value是Callback类型。原理就是虽然客户端每次传输对象都会不一致,但传输使用的Binder是一样的,RemoteCallbackList通过将Callback与Binder对应起来,就可以保证对象的一致性
(2)RemoteCallback的优点是什么?
答:①当客户端解注册的时候,只要遍历服务端所有的listener,找出和解注册具有相同的Binder对象的Listener,删掉即可
②客户端进程终止后,能自动移除客户端注册的listener
③RemoteCallbackList内部自动实现了线程同步的功能,进行注册和解注册时,不用做额外的线程同步工作
RemoteCallbackList注意点:在使用时候,一定要begBroadcast和finishBroadcast配对使用
(3)为什么有Binder线程池还会产生ANR问题?
答:以客户端举例,客户端调用服务端方法,这个方法是运行在Binder线程中的,但是客户端绑定服务端的方法却运行在主线程中,所以会发生ANR问题;同时由于服务端的方法本身就运行在服务端的Binder线程中,所以服务端方法本身就可以执行大量耗时操作,这时候切记不要在服务端方法中开线程去进行异步任务(后果??);还有,由于服务端调用客户端方法,运行在客户端的Binder线程中,所以不能用它去访问UI相关内容,解决方法是用Handler切换到UI线程
(4)Binder意外死亡的解决办法?
答:①给Binder设置死亡监听,在binderDied方法中重连
②在onServiceDisconnected中重连远程服务
区别:binderDied在客户端的Binder线程池中被回调,onServiceDisconnected在客户端UI线程中回调
难点3:AIDL如何进行权限认证?
答:①在onBind中进行认证,验证不通过直接返回null(可用申明permission等方法),无法建立连接
②在服务端的onTransact方法,验证不同过返回false - 使用ContentProvider
ContentProvide是Android提供的专门用来进行不同应用间数据共享的方式,他底层同样是通过Binder实现的
难点:CRUD操作,放置SQL注入和权限控制【后面再整理】
(1)如何实现一个自定义的ContentProvider?
答:继承ContentProvider类实现6个抽象方法即可,分别为onCreat,query,update,insert,delete,getType
(2)ContentProvider的CRUD是否需要开启多线程?
答:不需要,除了onCreat运行在UI线程中,其他所有方法都运行在Binder线程池中
(3)外部应用如何访问ContentProvider?
答:android:authorities是CP的唯一标识,通过这个访问
(4)重:CRUD操作由getContentResolver.query(uri,****)实现
(5)如何区分client请求访问不同的数据表
答:UriMatcher的addURI方法可以将Uri和Uri_Code关联到一起,客户端访问时通过取出Uri_Code进行匹配
(6)query,inset等CRUD操作存在多进程并发访问,因此要做好线程同步(怎么做??)
(7)一个SQLiteDatabase内部对数据库的操作有同步处理,但多个SQLiteDatabase之间无法同步 - 使用Socket
Socket也称为套接字,分为流式套接字和用户数据报套接字两种,分别对应网络传输层中的TCP和UDP协议-----引出TCP和UDP的区别,为什么TCP三次握手,四次挥手等等
具体实现:
服务端:
(1)创建Service,然后在线程中建立TCP服务(ServerSocket),并监听相应的接口,等待客户端连接请求
(2)当客户端连接时,会生成一个新的Socket(serverSocket.accept( ))对象,服务端就可以用这个对象与客户端进行数据传输
(3)如果客户端断开连接时(这里使用客户端输入流是否为空进行判断的),服务端关闭相应的Socket并结束线程
客户端:
(1)客户端开启一个线程通过Socket发出连接请求(必要时候可以进行超时重传处理,while(socket==null))
(2)客户端读取服务端消息
(3)关闭Socket
注意点:
(1)不能在主线程中访问网络,这会导致程序无法在Android4.0及以上的设备中运行 - 在实际业务中,如果多个业务模块都要使用AIDL服务,如何解决?
答:一般来说,业务模块使用AIDL服务可以通过新建Service进行连接,但需要的业务模块很多时,肯定不可能新建那么多Sercice,这时候就要引入Binder线程池:每个业务模块建立自己的AIDL接口,但服务端只需要建立一个Service,它提供一个queryBinder接口,他能根据业务模块的特征返回相应的Binder对象给他们,这样客户端就可以用所需的Binder对象进行远程方法的调用。Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中执行,避免重复创建Service的过程(单例实现)* - 在Android开发中提高开发效率的方法?
答:使用Binder连接池,避免了Service的反复建立,同时使AIDL统一管理【也可以说提高了性能,性能优化等等】 - 多种IPC通信方式的比较 P.121表格
Effective Java
第一条:考虑用静态工厂方法代替构造器
- 使用静态工厂方法代替构造器的好处:
(1)有名称。更易阅读
(2)不必每次调用的时候都创建一个新对象,可以将构建好的实例缓存起来,重复利用。如果程序经常请求创建相同的对象,这项技术可以大幅度提高性能。
(3)可以返回原返回类型的任何子类型对象(???)
(4)在构建参数化类型实例时,使代码更加简洁(类型推导) - 缺点:
(1)类如果不含公有的或者受保护的构造器,就不能被子类化
(2)与其他静态方法没什么差别
第二条:遇到多个构造器参数时要考虑用构建器
- 静态工厂和构造器都不能很好的扩展到大量的可选参数
- 当一个类中有大量的可选参数时(其中有必须域和可选域),应该如何构造?
答:
(1)使用重叠构造----【难以阅读,属性和传入的参数不能很明确的进行匹配】
(2)进阶:使用JavaBean构造器----【JavaBean可能处于不一致的状态】
Ps:对JavaBean可能处于不一致的状态的理解:JavaBean因为需要无参构造方法,所以成员变量不能是final类型。所以当判断两个JavaBean相同后,其中一个JavaBean可能改变属性,两个JavaBean从而发生错误和混乱。所以引出JavaBeans模式阻止了把类做成不可变的可能,就是因为不能使用final
(3)再进阶:使用Builder模式----【①所有必要域调用构造器得到builder对象;②builder调用方法设置其他属性(返回值是Builder实现重复调用 . . .);③调用无参build方法生成不可变对象(对象属性都是final类型)】
3.Builder模式的不足:①为了创建对象,必须先创建它的构建器;②比较冗长,只在很多参数的时候才使用【引出Rxjava?】
总而言之:如果类的构造器或者静态工程中具有多个参数,设计这种类时,Builder模式就是种不错的选择
问题:
- IInterface接口探究?为什么在Binder中传输的接口都需要继承它?
答:IInerface中实现了asBinder方法,这个方法可以根据当前调用情况返回代理(Proxy)的Binder对象,没有继承它就不能进行使用到Binder的IPC服务。(这个方法要区分asInterface,asInterface是根据asBinder的结果来决定是返回代理还是真正的Binder对象) - 线程同步方法是什么?该如何实现?
答:线程同步:当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。
方法:
(1)同步代码块:
synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。
(2)同步方法:
public synchronized 数据返回类型 方法名(){}
同步方法的同步监视器是 this也就是该对象的本身(这里指的对象本身有点含糊,其实就是调用该同步方法的对象)通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:
1,该类的对象可以被多个线程安全的访问。
2,每个线程调用该对象的任意方法之后,都将得到正确的结果。
3,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等。
线程通讯:
当使用synchronized 来修饰某个共享资源时(分同步代码块和同步方法两种情况),当某个线程获得共享资源的锁后就可以执行相应的代码段,直到该线程运行完该代码段后才释放对该共享资源的锁,让其他线程有机会执行对该共享资源的修改。当某个线程占有某个共享资源的锁时,如果另外一个线程也想获得这把锁运行就需要使用wait() 和notify()/notifyAll()方法来进行线程通讯了。
wait()方法
wait方法导致当前线程等待,直到其他线程调用同步监视器的notify方法或notifyAll方法来唤醒该线程。
notify()方法
唤醒在同步监视器上等待的单个线程,如果所有线程都在同步监视器上等待,则会选择唤醒其中一个线程,选择是任意性的,只有当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。
notifyAll()方法
唤醒在同步监视器上等待的所有的线程。只用当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
线程的同步:
原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。
同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。
同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。
**死锁 **
当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。JVM不监测也不试图避免这种情况,因此保证不发生死锁就成了程序员的责任。
如何避免死锁
一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证每个线程都按照同样的顺序去访问他们。 - 使用静态工厂方法代替构造器,可以返回原返回类型的任何子类型对象
问题: - Handler是什么?
答:Handler是Android消息机制的上层接口,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行 - Messenger和Binder、Handler的关系?
答:Messenger是Binder(AIDL)的封装,通过Messenger.getBinder( )方法可以返回Binder对象
Messenger的构造方法中,参数可以是Binder和Handler类型 - IBinder和Binder的区别?
答:IBinder是接口 Binder是它的实现类 - 客户端会产生ANR问题,服务端会不会产生?(会) 为什么有Binder线程池还会产生ANR?(因为客户端的绑定方法在主线程中,执行方法在Binder线程中)
- TCP和UDP区别
答:
1。TCP是面向链接的,TCP的三次握手在很大程度上保证了连接的可靠性;而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议。
2。也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。