前言
IPC 系列文章:
建议按顺序阅读。
Android IPC 之Service 还可以这么理解
Android IPC 之Binder基础
Android IPC 之Binder应用
Android IPC 之AIDL应用(上)
Android IPC 之AIDL应用(下)
Android IPC 之Messenger 原理及应用
Android IPC 之服务端回调
Android IPC 之获取服务(IBinder)
前面几篇文章详细分析了AIDL的使用,包括数据在客户端和服务端的传输,本篇将分析AIDL 回调的使用。
通过本篇文章,你将了解到:
1、跨进程传输接口
2、AIDL 回调的使用
3、回调在四大组件里的应用
1、跨进程传输接口
跨进程传递对象
基本数据类型,如int、short 、String 等不用做任何处理可通过Binder直接传送。而复杂数据类型,如自定义的类,需要实现Parcelable 接口才能通过Binder传送。
以之前的获取学生信息为例:
如上图所示,客户端通过IPC 从服务端获取学生信息,学生信息封装在Student类里:
public class Student implements Parcelable {
private String name;
private int age;
private float score;
...
}
学生信息包括姓名、年龄、分数三个字段。
我们定义AIDL接口如下:
interface IStudentInfo {
//主动获取
Student getStudentInfo();
}
客户端通过调用 getStudentInfo() 方法即可获取从服务端返回的学生信息。
跨进程传递接口
客户端想要获取学生信息,需要主动调用 getStudentInfo() 方法。考虑一种场景:
1、学生每一门考试,分数都在变化,客户端需要一直轮询去调用getStudentInfo() 方法才能获取最新的成绩。我们知道轮询是效率比较低的做法,要尽量避免。
2、我们就会想到学生成绩发生变化了,服务端就主动通知我们就好啦。
如下图所示:
现在的问题重点是:服务端如何主动通知客户端。
依据以往的经验,有两种方式可以实现:
1、客户端通过绑定服务端的Service,进而与服务端通信,那么可以换种思路,客户端也可以定义Service,而后服务端通过绑定客户端,进而调用客户端的接口,主动给客户端传递消息。
2、客户端绑定了服务端的Service,两者之间就能够通信。实际上服务端传递了Binder给客户端,客户端拿到Binder之后就可以进行通信了,这就说明了Binder对象本身能够跨进程传输。
于是改造之前的接口:
客户端调用服务端接口的时候将自己生成的Binder传递给服务端,那么服务端发生变化的时候就可以通过这个Binder来通知客户端了。
通过比对1、2两种方式:
第一种方式过于复杂,对于客户端、服务端的角色容易搞混。
第二种方式符合我们认知的"回调",也就是说跨进程的回调和同一个进程里的回调理解上是一致的。
2、AIDL 回调的使用
服务端声明回调接口
定义AIDL 回调接口:
import com.fish.ipcserver.Student;
interface RemoteCallback {
//回调
oneway void onCallback(in Student student);
}
Student 为学生信息类,该对象支持跨进程传输。
in 表示数据流方向,表示该Student 对象传递给客户端。
oneway 表示调用onCallback(xx) 方法的线程立即返回,不阻塞等待方法调用结果。
服务端暴露注册回调接口方法
服务端定义了回调接口,客户端需要给服务端传递接口的实现。因此服务端还需要将注册回调的接口暴露给客户端。
定义AIDL 文件如下:
import com.fish.ipcserver.Student;
import com.fish.ipcserver.RemoteCallback;
interface IStudentInfo {
//主动获取
Student getStudentInfo();
//注册回调
oneway void register(in RemoteCallback callback);
}
至此,服务端提供了两个方法:
1、getStudentInfo() 客户端调用此方法主动获取学生信息。
2、register(xx) 客户端调用此方法注册回调实例。
服务端编写回调逻辑
public class StudentService extends Service {
private Student student;
private RemoteCallback remoteCallback;
private MyStudent myStudent;
@Override
public void onCreate() {
super.onCreate();
student = new Student();
student.setAge(19);
student.setName("小明");
myStudent = new MyStudent();
}
class MyStudent extends IStudentInfo.Stub {
@Override
public Student getStudentInfo() throws RemoteException {
return student;
}
@Override
public void register(RemoteCallback callback) throws RemoteException {
//客户端注册的回调实例保存到成员变量 remoteCallback
remoteCallback = callback;
}
public void changeScore() {
//学生成绩发生改变
student.setScore((float)(Math.random() * 100));
try {
if (remoteCallback != null)
//调用回调实例方法,将变化后的学生信息传递给客户端
remoteCallback.onCallback(student);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//将Stub 返回给客户端
return myStudent.asBinder();
}
}
可以看出,声明了IStudentInfo 实例。
小结上面的逻辑:
1、服务端声明了Stub(桩,实际上是Binder实例),并将Stub返回给客户端。
2、客户端收到Stub(实际上是BinderProxy),然后转换为IStudentInfo 接口。而该接口里声明了两个方法,分别是getStudentInfo()和register(xx)。
3、客户端调用register(RemoteCallback) 将回调注册(传递)给服务端。
4、服务端发生变化的时候通过RemoteCallback 通知客户端数据已经发生改变。
客户端编写调用逻辑
分三步:
(1)、客户端绑定服务端Service。
(2)、建立连接后客户端将IBinder 转化为IStudentInfo 接口,并注册回调。
(3)、客户端处理回调内容。
来看看代码实现:
(1)绑定服务
//参数1:运行远程服务的包名
//参数2:远程服务全限定类名
ComponentName componentName = new ComponentName("com.fish.ipcserver", "com.fish.ipcserver.StudentService");
Intent intent = new Intent();
intent.setComponent(componentName);
//绑定远程服务
v.getContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
(2)IBinder 转换为IStudentInfo 接口
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isConnected = true;
//转为对应接口
iStudentInfo = IStudentInfo.Stub.asInterface(service);
try {
//注册回调
iStudentInfo.register(remoteCallback);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
isConnected = false;
}
};
(3)客户端处理回调
//声明回调
RemoteCallback remoteCallback = new RemoteCallback.Stub() {
@Override
public void onCallback(Student student) throws RemoteException {
Log.d("fish", "call back student:" + student);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(IPCActivity.this, "client receive change:" + student.toString(), Toast.LENGTH_SHORT).show();
}
});
}
};
此处收到服务端的回调后,仅仅Toast 学生信息。
测试效果
为了更贴近实际应用效果,客户端、服务端分别跑在不同的App里。
客户端的应用名为:AndroidDemo
服务端的应用名为:IPCServer
先来看看客户端表现:
步骤如下:
1、当点击按钮时,客户端判断没有连接上服务端,于是开始连接。
2、连接成功后,开始注册服务端接口。
3、再次点击按钮时,通过getStudentInfo()方法主动获取学生信息。
再来看服务端表现:
步骤如下:
1、服务端收到客户端绑定请求。
2、服务端收到客户端注册的回调接口。
3、服务端点击按钮改变学生分数,并通过回调接口通知客户端。
4、客户端收到后弹出Toast。
通过以上两个测试效果可以看出,客户端不仅能够主动调用服务端方法,同时也可以通过回调监听服务端的变化。
注意事项
1、自定义类型Student.java 与Student.aidl 需要在同一个包名下。
2、客户端与服务端定义的aidl 文件需要在同一个包名下。通常来说,一般先定义服务端aidl 接口,最后将这些aidl文件拷贝到客户端相同包名下。
3、bindService Intent 需要指定ComponentName。
3、回调在四大组件里的应用
以ContentProvider 为例:
想要获取相册数据,可以通过ContentProvider获取,而相册是公共的存储图片区域,其它App都可以往里面插入数据或者删除数据。
而系统也提供了监听相册变化的回调:
Handler handler = new Handler(Looper.getMainLooper());
ContentObserver contentObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
//数据变化回调
super.onChange(selfChange);
}
};
getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver);
如上,通过registerContentObserver(xx)向系统(服务端)注册了回调接口,当有数据变化的时候服务端会调用onChange(xx)通知客户端。
不仅ContentProvider 运用到了回调,Service、Activity、Broadcast也用到了。
理解了进程间的回调原理及其使用,对理解四大组件的通信帮助很大。
下篇将重点分析四大组件的框架。
本文基于Android 10.0
完整代码演示 若是有帮助,给github 点个赞呗~