Android IPC 之服务端回调

作者: 小鱼人爱编程 | 来源:发表于2021-11-19 23:22 被阅读0次

    前言

    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、跨进程传输接口

    跨进程传递对象

    image.png

    基本数据类型,如int、short 、String 等不用做任何处理可通过Binder直接传送。而复杂数据类型,如自定义的类,需要实现Parcelable 接口才能通过Binder传送。

    以之前的获取学生信息为例:


    image.png

    如上图所示,客户端通过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、我们就会想到学生成绩发生变化了,服务端就主动通知我们就好啦。

    如下图所示:


    image.png

    现在的问题重点是:服务端如何主动通知客户端。
    依据以往的经验,有两种方式可以实现:

    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
    先来看看客户端表现:

    tt0.top-671071.gif

    步骤如下:
    1、当点击按钮时,客户端判断没有连接上服务端,于是开始连接。
    2、连接成功后,开始注册服务端接口。
    3、再次点击按钮时,通过getStudentInfo()方法主动获取学生信息。

    再来看服务端表现:

    tt0.top-354096.gif
    步骤如下:
    1、服务端收到客户端绑定请求。
    2、服务端收到客户端注册的回调接口。
    3、服务端点击按钮改变学生分数,并通过回调接口通知客户端。
    4、客户端收到后弹出Toast。

    通过以上两个测试效果可以看出,客户端不仅能够主动调用服务端方法,同时也可以通过回调监听服务端的变化。

    注意事项

    1、自定义类型Student.java 与Student.aidl 需要在同一个包名下。
    2、客户端与服务端定义的aidl 文件需要在同一个包名下。通常来说,一般先定义服务端aidl 接口,最后将这些aidl文件拷贝到客户端相同包名下。
    3、bindService Intent 需要指定ComponentName。

    image.png

    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 点个赞呗~

    您若喜欢,请点赞、关注,您的鼓励是我前进的动力

    持续更新中,和我一起步步为营系统、深入学习Android/Java

    1、Android各种Context的前世今生
    2、Android DecorView 必知必会
    3、Window/WindowManager 不可不知之事
    4、View Measure/Layout/Draw 真明白了
    5、Android事件分发全套服务
    6、Android invalidate/postInvalidate/requestLayout 彻底厘清
    7、Android Window 如何确定大小/onMeasure()多次执行原因
    8、Android事件驱动Handler-Message-Looper解析
    9、Android 键盘一招搞定
    10、Android 各种坐标彻底明了
    11、Android Activity/Window/View 的background
    12、Android Activity创建到View的显示过
    13、Android IPC 系列
    14、Android 存储系列
    15、Java 并发系列不再疑惑
    16、Java 线程池系列

    相关文章

      网友评论

        本文标题:Android IPC 之服务端回调

        本文链接:https://www.haomeiwen.com/subject/gnxytrtx.html