Android IPC
来源:互联网 发布:工业机器人编程软件 编辑:程序博客网 时间:2024/06/03 16:55
选取总结自《Android开发艺术探索》。
文本包含两部分,1、对知识点就行归纳汇总 2、讲解IPC知识点
问题汇总:
1、什么是IPC?
Inter-Process Communication(进程间通信)
2、进程间通信是什么?
两个进程之间进行数据交换的过程
3、进程是什么?
一般指一个执行单元,也是系统分配资源的最小单位。
4、线程是什么?
是CPU调度的最小单元,而且是有限的系统资源。一个进程可以包含多个线程。
5、ANR导致的原因?如何避免?
ANR-application not respongding是因为UI线程内部的耗时操作导致界面无响应。应该将耗时操作移到非UI线程即可。
6、什么时候需要用到多进程?
比如:当前应用需要从其他应用获取数据
7、开启多进程模式的方法
- 给四大组件添加属性
android:process
- 特殊方法:通过JNI在native层去fork一个新的进程。
8、多进程会造成的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences的可靠性下降(不支持两个进程同时读写)
- Application会多次创建
9、Serializable和Parcelable接口作用
- 可以完成对象的序列化过程
- 使用Intent和Binder传输数据时就需要Serializable或Parcelable
- 需要把对象持久化到存储设备,或者通过网络传给其他客户端。
10、使用Serializable
1.这个类实现Serializable接口
2.该类声明一个serialVersionUID
11、serialVersionUID的作用
1、序列化后的数据的ID只有和当前类的ID相同才能正常被反序列化。
2、可以手动设置ID为1L,这样会自动根据当前类结构去生成它的hash值
12、两个特别注意点:
1、静态成员变量不属于对象,不会参与序列化过程
2、用transient
关键字标记的成员变量不会参与序列化过程。
13、java.io.ObjectOutputStream和ObjectInputStream用于对象序列化
14、系统中实现了Parcelable接口的类
Intent、Bundle、Bitmap、List、Map
里面的每个元素也都要可序列化
15、Parcelable和Serializable
- Serializable是java中的序列化接口,简单,但开销很大(需要大量IO操作)
- Parcelable是Android首推方法,使用麻烦,效率很高
- Parcelable主要用于内存序列化上
- Serializable适用于将对象序列化到存储设备或通过网络传输(Parcelable也可以只是较复杂)
16、Binder是什么?
- Binder是android的一个类,实现了IBinder接口
- IPC角度:Binder是android的一种跨进程通信方式
- Binder也可以看做一种虚拟的物理设备,设备驱动是/dev/binder,Linux中没有这种通信方式
- Android Framework角度:Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager等)和相应ManagerService的桥梁
- Android应用层:Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过该对象,客户端可以获取服务端提供的服务和数据,服务包括普通服务和基于AIDL的服务。
17、Binder主要用在哪?
- Service
- AIDL
- Messenger(底层AIDL)
18、AIDL文件的本质作用
AIDL文件的本质就是系统提供了一种快速实现Binder的工具。
19、通过AIDL快速实现Binder的步骤
- 新建Book.java(简单的类,没有实际功能,实现Parcelable接口)
- 新建Book.aidl需要有parcelable Book;
- 新建IBookManager.aidl,里面需要导入Book类
import xxx.Book;
- 选择android studio的build中make project
20、AIDL工具快速实现的Binder中的四个要点
- 继承
IInterface
接口,本身也为接口- 声明了两个IBookManager.aidl中定义的getBookList和addBook方法(并且用两个id标识这两个方法,用于标识在transact中客户端请求的是哪个方法)
- 声明一个内部类Stub,该Stub就是Binder类
- Stub的内部代理类Proxy,用于处理逻辑-客户端和服务端都位于一个进程时,方法调用不会走跨进程的transact过程,当位于不同进程时,方法调用走transact过程。
21、Binder注意点
- 客户端发起远程请求后,当前线程会被挂起直到服务器返回结果,因此不要在UI线程发起远程请求。
- 服务端的Binder方法运行在Binder的线程池中,所以Binder方法是否耗时都要采用同步方法实现。
22、Binder遭遇服务端异常终止怎么办?
- 使用
linkToDeath
和unlinkToDeath
。如果服务端异常终止,而会导致客户端调用失败,甚至可能客户端都不知道binder已经死亡,就会产生问题。- linkToDeath作用给Binder设置一个死亡代理,当Binder会收到通知,还可以重新发起连接请求从而恢复连接。
- 此外binder的isBinderAlive也可以判断Binder是否死亡。
23、Binder的工作流程:
- Client向Binder发起远程请求,Client同时挂起
- Binder向data(输入端对象)写入参数,并且通过Transact方法向服务端发起远程调用请求(RPC)
- Service端调用onTransact方法(运行在服务端线程池中)向reply(输出端对象)写入结果
- Binder获取reply数据,返回数据并且唤起Client
[TOC]
24、Android中的IPC方法(6种)
1-Bundle
2-文件共享
3-Messenger
4-AIDL
5-ContentProvider
6-Socket
25、Bundle的作用
Bundle能携带数据-实现了Parcelable接口,常用于传递数据,如Acitivity、Service和Receiver都支持在Intent中通过Bundle传递数据。
26、Bundle在直接传递数据外的一个特殊使用场景。
场景:A进程在完成计算后需要启动B进程的一个组件并且将结果传递给B进程,但是这个结果不支持放入Bundle,因此无法通过Intent传输。
方案:A进程通过Intent启动进程B的service组件(如IntentService)进行计算,因为Service也在B进程中,目标组件就可以直接使用计算结果。
27、文件共享的特点:
- 通过序列化在进程间传递对象
- 只适合同步要求不高的进程间通信
- 要妥善处理并发读写问题,高并发情况下很容易出现数据丢失。
28、Messenger是什么?
- 轻量级的IPC方案
- 底层实现是AIDL
- 一次处理一个请求,因此在服务端不考虑线程同步问题。
- 1-IPC简介
- 2-Android中多进程模式
- 1-开启多进程模式
- 3-IPC基础概念介绍
- 1-Serializable接口
- 2-Parcelable
- 3-Binder
- 1-通过AIDL生成相应java文件
- 2-Binder所在java文件的代码解析
- 1-StubBinder解析
- 2-Binder注意点
- 3-Binder的两个重要方法
- 4-Android中的IPC方法
- 1-Bundle
- 2-文件共享
- 3-Messenger
- Messenger的使用实例
- 1客户端
- 2服务器端
- 3AndroidManifest中注册Service
- Messenger的使用实例
- 4-AIDL
- 1-AIDL进程间通信流程
- 1-服务端
- 2-客户端
- 2-AIDL实例
- 1-AIDL支持的数据类型
- 2-AIDL编译报错
- 3-远程服务端
- 4-本地客户端
- 3-AIDL实例3观察者模式
- 1-代码按上列步骤所有代码可以直接复制
- 2-RemoteCallbackList解除注册引发问题
- RemoteCallbackList
- 4-AIDL的注意点
- 5-Binder意外死亡
- 1-两者区别
- 2-各自实现方法
- 6-权限验证
- 1-AIDL进程间通信流程
- 5-ContentProvider
- 6-Socket
- 5-Binder连接池
- 6-选用合适的IPC方式
1-IPC简介
IPC指Inter-Process Communication(进程间通信),也就是两个进程之间进行数据交换的过程。
进程:一般指一个执行单元。
线程:是CPU调度的最小单元,而且是有限的系统资源。一个进程可以包含多个线程。
Android中主线程也就是UI线程,只有UI线程才能操作UI元素。如果将耗时操作放于主线程就会导致ANR(Application not responding)应用无响应。
2-Android中多进程模式
通过给四大组件添加android:process
属性就可以开启多进程模式。开启多进程虽然简单,但是内部却暗藏杀机。
1-开启多进程模式
正常情况都是一个应用中存在多个进程的情况,这里不涉及到两个应用间多进程的状况。
1. 给四大组件添加属性android:process
2. 特殊方法:通过JNI在native层去fork一个新的进程。
activity的android:process
属性=":remote"
和"com.example.remote"
的区别。:remote
是指在当前进程名前面加上当前的包名-com.example:remote
,且该进程是当前应用的私有进程,其他应用的组件不能和该进程跑在同一个进程内。后者是属于全局进程,其他应用可以通过ShareUID的方式和它跑在同一个进程中。
多进程会造成的问题:
1. 静态成员和单例模式完全失效
2. 线程同步机制完全失效
3. SharedPreferences的可靠性下降
4. Application会多次创建
3-IPC基础概念介绍
Serializable和Parcelable接口作用
1. 可以完成对象的序列化过程
2. 使用Intent和Binder传输数据时就需要Serializable或Parcelable
3. 需要把对象持久化到存储设备,或者通过网络传给其他客户端。
1-Serializable接口
Serializable接口为对象提供了标准的序列化和反序列化操作。Android使用serializable接口实现序列化需要两步:1.这个类实现Serializable接口 2.该类声明一个serialVersionUID(private static final long serialVersionUID=8711368828010083044L
)。 甚至可以不申明ID,但是这个ID会对反序列化产生影响。
serialVersionUID的作用:
1、序列化后的数据的ID只有和当前类的ID相同才能正常被反序列化。
2、可以手动设置ID为1L,这样会自动根据当前类结构去生成它的hash值
两个特别注意点:
1、静态成员变量不属于对象,不会参与序列化过程
2、用transient
关键字标记的成员变量不会参与序列化过程。
java.io.ObjectOutputStream和ObjectInputStream用于对象序列化
2-Parcelable
1、系统中实现了Parcelable接口的类
Intent、Bundle、Bitmap、List、Map
里面的每个元素也都要可序列化
2、Parcelable和Serializable
- Serializable是java中的序列化接口,简单,但开销很大(需要大量IO操作)
- Parcelable是Android首推方法,使用麻烦,效率很高
- Parcelable主要用于内存序列化上
- Serializable适用于将对象序列化到存储设备或通过网络传输(Parcelable也可以只是较复杂)
3-Binder
1、Binder是什么?
- Binder是android的一个类,实现了IBinder接口
- IPC角度:Binder是android的一种跨进程通信方式
- Binder也可以看做一种虚拟的物理设备,设备驱动是/dev/binder,Linux中没有这种通信方式
- Android Framework角度:Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager等)和相应ManagerService的桥梁
- Android应用层:Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过该对象,客户端可以获取服务端提供的服务和数据,服务包括普通服务和基于AIDL的服务。
2、Binder主要用在哪?
- Service
- AIDL
- Messenger(底层AIDL)
1-通过AIDL生成相应java文件
因为AIDL涉及到Binder的核心,所以通过AIDL实例进行讲解。
先通过系统自动生成Binder文件,步骤如下:
1、新建Book.java(简单的类,没有实际功能,实现Parcelable接口)
public class Book implements Parcelable{ public int bookId; public Book(int bookId){ this.bookId = bookId; } private Book(Parcel in){ bookId = in.readInt(); } public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(bookId); }}
2、新建Book.aidl
package com.example.administrator.featherdemos.aidl;parcelable Book;
3、新建IBookManager.aidl
package com.example.administrator.featherdemos.aidl;//关键:导入Book.javaimport com.example.administrator.featherdemos.aidl.Book;interface IBookManager { List<Book> getBookList(); void addBook(in Book book);}
4、选择android studio的build中make project
系统就会自动生成对应java文件
IBookManager
,位于目录app\build\generated\source\aidl\debug\包下
2-Binder所在java文件的代码解析
要点如下:
1. 继承IInterface
接口,本身也为接口
2. 声明了两个IBookManager.aidl中定义的getBookList和addBook方法(并且用两个id标识这两个方法,用于标识在transact中客户端请求的是哪个方法)
3. 声明一个内部类Stub,该Stub就是Binder类
4. Stub的内部代理类Proxy,用于处理逻辑-客户端和服务端都位于一个进程时,方法调用不会走跨进程的transact过程,当位于不同进程时,方法调用走transact过程。
1-Stub(Binder)解析
1、DESCRIPTOR
Binder的唯一标识,一般用类名表示
2、asInterface(android.os.IBinder obj)
将服务端Binder对象转换成客户端所需AIDL接口类型对象(xxx.aidl.IBookManager)。如果客户端和服务端位于同一进程,此方法返回就是服务端的Stub对象本身,否则返回系统封装后的Stub.proxy
3、asBinder
返回当前Binder对象
4、onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
- 运行在服务端的Binder线程池。
- 通过code确定Client请求的目标方法,从data中取得方法所需参数,执行目标方法。执行完毕后就向reply中写入返回值。
- 如果此方法返回false客户端就请求失败,我们可以用此来进行权限验证。
5、Proxy的getBookList
运行在客户端。
创建方法所需的data(输入)、reply(输出)和list(返回)对象。把该方法的参数信息写入data,调用transact方法发起RPC远程过程调用请求,同时当前线程挂起。服务端的onTransact会被调用,到RPC过程返回后,当前线程继续执行,并从reply中取出RPC过程的返回结果,最后返回reply中的数据。
6、Proxy的addBook
运行在客户端。过程和getBookList类似,但是没有返回值。
2-Binder注意点
1、客户端发起远程请求后,当前线程会被挂起直到服务器返回结果,因此不要在UI线程发起远程请求。
2、服务端的Binder方法运行在Binder的线程池中,所以Binder方法是否耗时都要采用同步方法实现。
AIDL文件的本质就是系统提供了一种快速实现Binder的工具,仅此而已。
3-Binder的两个重要方法
linkToDeath和unlinkToDeath。用于解决,如果服务端异常终止,而会导致客户端调用失败,甚至可能客户端都不知道binder已经死亡,就会产生问题。
linkToDeath作用给Binder设置一个死亡代理,当Binder会收到通知,还可以重新发起连接请求从而恢复连接。
此外binder的isBinderAlive也可以判断Binder是否死亡。
4-Android中的IPC方法
1-Bundle
1、Bundle的作用
Bundle能携带数据-实现了Parcelable接口,常用于传递数据,如Acitivity、Service和Receiver都支持在Intent中通过Bundle传递数据。
2、Bundle在直接传递数据外的一个特殊使用场景。
场景:A进程在完成计算后需要启动B进程的一个组件并且将结果传递给B进程,但是这个结果不支持放入Bundle,因此无法通过Intent传输。
方案:A进程通过Intent启动进程B的service组件(如IntentService)进行计算,因为Service也在B进程中,目标组件就可以直接使用计算结果。
2-文件共享
两个进程通过读/写同一个文件进行数据交换。也可以通过序列化在进程间传递对象。
文件共享的特点:
1. 通过序列化在进程间传递对象
2. 只适合同步要求不高的进程间通信
3. 要妥善处理并发读写问题,高并发情况下很容易出现数据丢失。
3-Messenger
1、Messenger是什么?
- 轻量级的IPC方案
- 底层实现是AIDL
- 一次处理一个请求,因此在服务端不考虑线程同步问题。
Messenger的使用实例
通过messenger在两个进程之间互相发送消息。
1、客户端
MessengerActivity.java
public class MessengerActivity extends Activity { private static final String TAG = "MessengerActivity"; private Messenger mMessenger; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mMessenger = new Messenger(iBinder); // Message msg = Message.obtain(null, Constant.MSG_FROM_CLIENT); //bundle携带消息 Bundle data = new Bundle(); data.putString("msg", "This is Client!"); //给msg绑定bundle msg.setData(data); //**将用于服务端回复的msger发送给服务端** msg.replyTo = mGetReplyMessenger; try { //发送消息 mMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName componentName) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); //绑定并启动服务 Intent intent = new Intent(this, MessengerService.class); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { //解除服务 unbindService(mServiceConnection); super.onDestroy(); } /**------------------- * 接受Service回复消息 * ------------------*/ private final Messenger mGetReplyMessenger = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case Constant.MSG_FROM_SERVICE: Log.i(TAG, "recv msg from service:" + msg.getData().getString("reply")); break; default: super.handleMessage(msg); break; } } }}
主要完成两方面功能:
1. 绑定并启动位于新进程的服务,通过msg发送消息
2. 设置接受新进程服务发送来的消息
2、服务器端
MessengerService.java
public class MessengerService extends Service { private static final String TAG = "MessengerService"; private final Messenger mMessenger = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case Constant.MSG_FROM_CLIENT: //接收到Client消息 Log.i(TAG, "receive msg from client: " + msg.getData().getString("msg")); /*--------------------------------------------- * 回复数据给Client * --------------------------------------------*/ Messenger clientMessenger = msg.replyTo; //获取到服务器传来的msger Message message = Message.obtain(null, Constant.MSG_FROM_SERVICE); //设置msg Bundle bundle = new Bundle(); bundle.putString("reply", "This is Service!"); message.setData(bundle); //发送消息 try { clientMessenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } public MessengerService() { } @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); }}
功能:接收消息,并通过client传送来的messenger回复消息。
3、AndroidManifest中注册Service
<service android:name=".MessengerService" android:enabled="true" android:exported="true" android:process=":remote"> </service>
android:process=":remote"
代表另开一个Service进程。
4-AIDL
Messenger缺点:
1. 以串行的方式处理客户端发送的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,Messenger就无法胜任。
2. 如果需要跨进程调用服务端的方法,这种情形Messenger就无法做到。
1-AIDL进程间通信流程
使用AIDL进行进程间通信的流程,分为服务端和客户端两个方面。
1-服务端
- 创建一个Service来监听客户端的连接请求。
- 创建一个AIDL文件。
- 将暴露给客户端的接口在该AIDL文件中声明。
- 最后在Service中实现这个AIDL接口即可。
2-客户端
- 绑定服务端的Service
- 将服务端返回的Binder对象转成AIDL接口所属的类型
- 最后就可以调用AIDL中的方法。
2-AIDL实例
这里先要创建你需要的接口文件:
ITuringManager.aidl(这里功能就是获取图灵机列表,以及增加一个图灵机)
package com.example.administrator.featherdemos;import com.example.administrator.featherdemos.TuringMachine;interface ITuringManager { List<TuringMachine> getTuringMachineList(); void addTuringMachine(in TuringMachine machine);}
实现TuringMachine.java(也就是接口文件导入的类,需要实现Parcelable接口):
package com.example.administrator.featherdemos;//import ....需要的包public class TuringMachine implements Parcelable{ int machineId; String description; protected TuringMachine(Parcel in) { machineId = in.readInt(); description = in.readString(); } public TuringMachine(int id, String description){ this.machineId = id; this.description = description; } public static final Creator<TuringMachine> CREATOR = new Creator<TuringMachine>() { @Override public TuringMachine createFromParcel(Parcel in) { return new TuringMachine(in); } @Override public TuringMachine[] newArray(int size) { return new TuringMachine[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(machineId); parcel.writeString(description); }}
因为使用到的类(例如TuringMachine.java)按规定需要一个对应aidl文件-TuringMachine.aidl:
package com.example.administrator.featherdemos;parcelable TuringMachine;
ITuringMachine.aidl和TuringMachine.aidl需要在aidl文件夹下的包内
TuringMachine.java要在java文件夹下的包内。
1-AIDL支持的数据类型
- 基本数据类型(int、long、char、boolean、double等)
- String和CharSequence
- List:只支持ArrayList,且里面所有元素必须是AIDL支持的数据。
- Map:只支持HashMap,且里面所有元素必须是AIDL支持的数据,包括key和value
- Parcelable:所有实现Parcelable接口的对象
- AIDL:所有AIDL接口都可以在AIDL中使用
2-AIDL编译报错
`Error:Execution failed for task ‘:app:compileDebugAidl’.
java.lang.RuntimeException: com.android.ide.common.process.ProcessException: Error while executing process D:\androidstudio2.3\android-sdk\build-tools\25.0.0\aidl.exe with arguments …
ITuringManager.aidl
在中使用的方法参数,需要根据方向加上
in\out\inout如
void addTuringMachine(in TuringMachine machine);`
3-远程服务端
ITuringMachineManagerService.java
public class ITuringMachineManagerService extends Service { /** * CopyOnWriteArrayList支持并发读/写: * 1. AIDL在服务端的Binder线程池中执行,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情况。 * 2. CopyOnWriteArrayList能进行自动的线程同步。 */ private CopyOnWriteArrayList<TuringMachine> mTuringMachineList = new CopyOnWriteArrayList<>(); private Binder mBinder = new ITuringManager.Stub(){ @Override public List<TuringMachine> getTuringMachineList() throws RemoteException { return mTuringMachineList; } @Override public void addTuringMachine(TuringMachine machine) throws RemoteException { mTuringMachineList.add(machine); } }; public ITuringMachineManagerService() { mTuringMachineList.add(new TuringMachine(1, "Machine 1")); mTuringMachineList.add(new TuringMachine(2, "Machine 2")); } @Override public IBinder onBind(Intent intent) { return mBinder; }}
AIDL中List只能用ArrayList,这里为何使用了CopyOnWriteArrayList(并非继承自ArrayList)?
Binder会根据List规范去访问数据,并且生成一个新的ArrayList传给客户端,因此没有违反数据类型的规定。
ConcurrentHashMap也是类似功能
该Service用android:process属性使其运行在新进程中,已达到进程间通信的效果。
<service android:name=".ITuringMachineManagerService" android:enabled="true" android:exported="true" android:process=":remote">
4-本地客户端
public class TuringActivity extends AppCompatActivity { private static final String TAG = TuringActivity.class.getName(); //Service连接:从服务端获取本地AIDL接口对象,并调用远程服务端的方法。 private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { //Binder的asInterface()将binder对象转换为客户端需要的AIDL接口对象 ITuringManager iTuringManager = ITuringManager.Stub.asInterface(iBinder); try { //获取服务端的List ArrayList<TuringMachine> turingMachineArrayList = (ArrayList<TuringMachine>) iTuringManager.getTuringMachineList(); for(TuringMachine machine : turingMachineArrayList){ Log.i(TAG, "onServiceConnected: "+machine.getMachineId() + "-" + machine.getDescription()); } } catch (RemoteException e){ e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName componentName) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_turing); //绑定远程服务端的 Service 并启动 Intent intent = new Intent(this, ITuringMachineManagerService.class); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mServiceConnection); //解绑 super.onDestroy(); }}
3-AIDL实例3:观察者模式
在上面创建图灵机的实例上,我们想在添加机器时,工厂会主动通知所有监听者,本实例就来实现这种观察者模式。
在上面代码基础上有如下步骤:
1、建立观察者接口(Observer)-ITMachineObserver.aidl
2、在ITuringManager.aidl中增加注册和解注册功能(register\unregister)
3、在服务端ITuringMachineManagerService中的binder对象里实现额外增加的注册和解注册功能。
4、在客户端中的binder对象里实现观察者接口中的更新方法。
使用:
1、客户端中通过从服务端获得的Binder对象,调用register/unregister等方法
2、服务端中通过Client客户端注册的Observer去调用客户端Binder中的更新方法
1-代码:按上列步骤(所有代码可以直接复制)
ITMachineObserver.aidl
package com.example.administrator.featherdemos;import com.example.administrator.featherdemos.TuringMachine;interface ITMachineObserver { void inform(in TuringMachine machine);}
ITuringManager.aidl
package com.example.administrator.featherdemos;import com.example.administrator.featherdemos.TuringMachine;import com.example.administrator.featherdemos.ITMachineObServer;interface ITuringManager { List<TuringMachine> getTuringMachineList(); void addTuringMachine(in TuringMachine machine); void registerListener(in ITMachineObserver observer); void unregisterListener(in ITMachineObserver observer);}
ITuringMachineManagerService:
只修改了private Binder mBinder = new ITuringManager.Stub()的内容
public class ITuringMachineManagerService extends Service { /** * CopyOnWriteArrayList支持并发读/写: * 1. AIDL在服务端的Binder线程池中执行,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情况。 * 2. CopyOnWriteArrayList能进行自动的线程同步。 */ private CopyOnWriteArrayList<TuringMachine> mTuringMachineList = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList<ITMachineObserver> mObserverList = new CopyOnWriteArrayList<>(); private Binder mBinder = new ITuringManager.Stub(){ @Override public List<TuringMachine> getTuringMachineList() throws RemoteException { return mTuringMachineList; } @Override public void addTuringMachine(TuringMachine machine) throws RemoteException { mTuringMachineList.add(machine); for(ITMachineObserver observer : mObserverList){ observer.inform(machine); } } @Override public void registerListener(ITMachineObserver observer) throws RemoteException { mObserverList.add(observer); } @Override public void unregisterListener(ITMachineObserver observer) throws RemoteException { mObserverList.remove(observer); } }; public ITuringMachineManagerService() { mTuringMachineList.add(new TuringMachine(1, "Old Machine 1949")); mTuringMachineList.add(new TuringMachine(2, "Old Machine 1949-2")); } @Override public IBinder onBind(Intent intent) { return mBinder; }}
TuringActivity:
- private ITMachineObserver mITMachineObserver获得观察者的Binder对象,并实现接口方法
- 调用iTuringManager.registerListener(mITMachineObserver)在服务端进行注册
public class TuringActivity extends AppCompatActivity{ private static final String TAG = TuringActivity.class.getName(); //Service连接:从服务端获取本地AIDL接口对象,并调用远程服务端的方法。 private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { //Binder的asInterface()将binder对象转换为客户端需要的AIDL接口对象 ITuringManager iTuringManager = ITuringManager.Stub.asInterface(iBinder); try { iTuringManager.addTuringMachine(new TuringMachine(10, "Machine 2008")); //获取服务端的List ArrayList<TuringMachine> turingMachineArrayList = (ArrayList<TuringMachine>) iTuringManager.getTuringMachineList(); for(TuringMachine machine : turingMachineArrayList){ Log.i(TAG, "onServiceConnected: "+machine.getMachineId() + "-" + machine.getDescription()); } iTuringManager.registerListener(mITMachineObserver); iTuringManager.addTuringMachine(new TuringMachine(3, "New Machine 2018!")); } catch (RemoteException e){ e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName componentName) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_turing); //绑定远程服务端的 Service 并启动 Intent intent = new Intent(this, ITuringMachineManagerService.class); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mServiceConnection); //解绑 super.onDestroy(); } private ITMachineObserver mITMachineObserver = new ITMachineObserver.Stub() { @Override public void inform(TuringMachine machine) throws RemoteException { Log.i(TAG, "inform: get a new machine-"+machine.getDescription()); } };}
2-RemoteCallbackList:解除注册引发问题
在onDestory时,我们可以解除在服务端的注册:
@Override protected void onDestroy() { //确保远程服务的Binder任然存活,就进行unregister if(mRemoteITuringManager != null && mRemoteITuringManager.asBinder().isBinderAlive()){ try { mRemoteITuringManager.unregisterListener(mITMachineObserver); Log.i(TAG, "onDestroy: unregister!"); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mServiceConnection); //解绑 super.onDestroy(); }
然而在服务端却无法在列表中找到该Observer
:因为已经是两个不同的对象了!对象的跨进程传输本身是反序列化的过程,而对象在服务端和客户端早已不是同一个对象。
RemoteCallbackList
系统专门提供用于删除跨进程Listener的接口。该类本身是一个泛型,支持任何AIDL接口。
- 工作原理
内部有一个Map结构,key是Ibinder,value是Callback类型。该结构能保存所有AIDL回调。将Listener的信息存入CallBack,如下:
IBinder key = listener.asBinder();Callback value = new Callback(key, cookie);
虽然每次跨进程Client的同一个对象,会在服务端生成多个对象。但是这些对象本身的Binder都是同一个。
以Binder作为Key,这样Listener对应着唯一Binder。
流程
客户端解注册,服务端会遍历所有listener,找出那个和解注册的listener具有相同Binder的listener,并删除。特点
- 客户端终止后,会自动移除客户端注册的所有listener
- 自动实现线程同步,无需额外操作。
Service服务端代码做出如下变换:
private RemoteCallbackList<ITMachineObserver> mObserverList = new RemoteCallbackList<>();
@Override public void registerListener(ITMachineObserver observer) throws RemoteException { mObserverList.register(observer); } @Override public void unregisterListener(ITMachineObserver observer) throws RemoteException { Log.i("ITuringService", "unregisterListener: size-"+mObserverList.getRegisteredCallbackCount()); mObserverList.unregister(observer); Log.i("ITuringService", "unregisterListener: size-"+mObserverList.getRegisteredCallbackCount()); }
inform: 需要按照RemoteCallbackList的方法进行遍历。
@Override public void addTuringMachine(TuringMachine machine) throws RemoteException { mTuringMachineList.add(machine); final int count = mObserverList.beginBroadcast(); //进行通知 for (int i = 0; i < count; i++){ mObserverList.getBroadcastItem(i).inform(machine); } mObserverList.finishBroadcast(); }
打印出解除前后的Listener数量,结果符合预期!
4-AIDL的注意点
- Client客户端调用远程方法,该方法运行在服务端的Binder线程池中,Client会被挂起。
- 服务端的方法执行过于耗时,会导致Client长时间阻塞,若该线程是UI线程,会导致ANR。
- 客户端的onServiceConnected和onServiceDisconnected方法都运行在UI线程中,不可以进行耗时操作
- 服务端的方法本身就在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,不要再开线程进行异步操作。
- 远程方法因为运行在Binder线程池中,如果要操作UI要通过Handler切换到UI线程(服务端通过远程方法去操作Cilent客户端的控件)
5-Binder意外死亡?
两种解决方法:
1. 给Binder设置DeathRecipient监听,Binder死亡时回调binderDied
2. 在onServiceDisconnected中重连远程服务。
1-两者区别
- onServiceDisconnected在客户端UI线程中被回调
- binderDied在客户端的Binder线程池中被回调(无法操作UI)
2-各自实现方法
binderDied,linkToDeath和unlinkToDeath实现
//设置Binder死亡代理 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){ @Override public void binderDied() { //远程服务端die,就不重新连接 if(mRemoteITuringManager == null){ return; } mRemoteITuringManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mRemoteITuringManager = null; //重新绑定远程Service //绑定远程服务端的 Service 并启动 Intent intent = new Intent(TuringActivity.this, ITuringMachineManagerService.class); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } };
客户端的onServiceConnected中iBinder.linkToDeath(mDeathRecipient, 0);
//Binder的asInterface()将binder对象转换为客户端需要的AIDL接口对象ITuringManager iTuringManager = ITuringManager.Stub.asInterface(iBinder);try { //设置死亡代理 iBinder.linkToDeath(mDeathRecipient, 0);} catch (RemoteException e) { e.printStackTrace();}
onServiceDisconnected是ServiceConnection的内部方法,在服务端Service断开后就会调用,可以进行操作。
6-权限验证
5-ContentProvider
6-Socket
5-Binder连接池
6-选用合适的IPC方式
- android ipc
- Android IPC
- Android IPC
- Android IPC
- Android IPC
- android IPC
- Android IPC
- Android 不支持 SYSV IPC (SYSV IPC)
- Android 不支持 SYSV IPC (SYSV IPC)
- Android IPC机制详解
- Android IPC机制详解
- Android IPC 机制详解
- Android IPC机制 详解
- Android IPC机制详解
- Android IPC机制详解
- Android的IPC机制
- Android IPC机制详解
- Android Binder IPC分析
- 《数字技术》连载4: 第1章 概述 第4节 数字技术,任务和应用.
- java里int类型的整数减去double类型的浮点类型数
- InterviewQuestionsMergesort
- centos 的小坑 只有lo网卡解决问题
- RDD的一些api的用法
- Android IPC
- Nginx 踩的坑
- PAT 1005 继续(3n+1)猜想
- hdu 1809(A New Tetris Game(2)) 字符串hash+sg函数+DP
- Intellij IDEA调试功能使用总结
- LeetCode 31.Next Permutation
- python----高效的解释性语言
- 【arm】ubuntu mate apt-get 404 not found
- MySQL简单语法(7)