接口回调目的和用法解析

一、回调的含义和用途

1. 什么是回调

一般来说,模块之间都存在一定的调用关系,从调用方式上来看,可分为三类:

  1. 同步调用:同步调用是一种阻塞式调用,即在函数A的函数体里通过书写函数B的函数名来调用之,使内存中对应函数B的代码得以执行。
  2. 异步调用:异步调用是一种类似消息或事件的机制解决了同步阻塞的问题,例如A通知B后,他们各走各的路,互不影响,不用像同步调用那样,A通知B后,非得等到B走完后,A才继续走。
  3. 回调:回调是一种双向的调用模式,也就是说,被调用的接口被调用时也会调用对方的接口,例如A要调用B,B在执行完又要调用A。

三种回调

2. 回调的用途

回调一般用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调。例如作为一个驱动,是一个底层,他在收到一个数据时,除了完成本层的处理工作外,还将进行回调,将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。


二、为什么会存在回调机制

举例1:

有一位老板(上层模块)很忙,他没有时间盯着员工(下层模块)干活,然后他告诉自己的雇员,干完当前这些事情后,告诉他干活的结果。这个例子其实是一个回调+异步的例子,再举一个例子,A程序员写了一段程序a,其中预留了回调函数接口,并封装好了该程序,程序员B让a调用自己的程序b中的一个方法,于是,他通过a中的接口回调自己b中的方法。下面把上面的例子变成代码

1.首先创建一个回调接口,让老板得告知干完活如何找到他的方式:留下老板办公室地址:

/** 
 * 此接口为联系的方式,不论是电话号码还是联系地址,作为 
 * 老板都必须要实现此接口
 */  
public interface CallBackInterface {  

    public void execute();  
}  

2.创建回调对象,就是老板本人,因为员工干完活后要给他打电话,因此老板必须实现回调接口,不然员工去哪里找老板?

/** 
 * 老板是作为上层应用身份出现的,下层应用(员工)是不知道 
 * 有哪些方法,因此他想被下层应用(员工)调用必须实现此接口 
 */  
public class Boss implements CallBackInterface {  

    @Override  
    public void execute() {  
        System.out.println("收到了!!" + System.currentTimeMillis());  

    }  
} 

3.创建控制类,也就是员工对象,他必须持有老板的地址(回调接口),即使老板换了一茬又一茬,办公室不变,总能找到对应的老板。

/** 
 * 员工类,必须要记住,这是一个底层类,底层是不了解上层服务的 
 */  
public class Employee {  

    private CallBackInterface callBack = null;  

    //告诉老板的联系方式,也就是注册  
    public void setCallBack(CallBackInterface callBack){  
        this.callBack = callBack;  
    }  

    //工人干活  
    public void doSome(){  
        //1.开始干活了  
        for(int i=0;i<10;i++){  
            System.out.println("第【" + i + "】事情干完了!");  
        }  

        //2.告诉老板干完了  
        callBack.execute();  
    }  
} 

4.测试类代码:

public class Client {  

    public static void main(String[] args) {  

        Employee emp = new Employee();  

        //将回调对象(上层对象)传入,注册  
        emp.setCallBack(new Boss());  

        //开启控制器对象运行  
        emp.doSome();  
    }  

} 

举例2:

对于回调函数的理解可以参照C或C++中对回调函数的定义:

程序在调用一个函数时,将自己的函数的地址作为参数传递给程序调用的函数时(那么这个自己的函数称回调函数)

这里写图片描述

然而Java中没有指针,不能传递方法的地址,一般采用接口回调实现:把实现某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。

例如:

一读者想借《软件技术学习与实践》这本书,但这本书已被其他读者借走了。于是,读者与图书馆管理员间发生了以下对话:

读者:“我把我的电话号码告诉你,等书一到就马上通知我。”

管理员:“好的。另一读者把书还回来后,马上给您打电话,书我先帮您留着。”

在上述这个场景中,读者就是“回调对象”,管理员就是“控制器对象”,读者的电话号码就是“回调对象的方法”。

在控制器类中引用了回调对象,因此就能调用回调方法,当控制器进行某些判断之后(如:监听鼠标单击操作)就会自动调用回调方法!简易流程图如下:

这里写图片描述

举例3:

假如有两个类
  • classA
  • classB

如果A要调用B的方法,则:在classA中创建B的对象
如果B要调用A的方法,则:在classB中创建A的对象

两个类构成一个循环的关系,“你中有我,我中有你”。这样操作在实际中最不可取,因为耦合度最高。所以要想办法打断其中的一条线,打破循环关系。
怎么打断呢?——接口回调

接口回调三部曲:

一、 在B类中创建事件接口interface

//B类是自定义Video类。在B类中创建,供逻辑层来实现具体逻辑
public interface ADVideoPlayerListener {

        public void onBufferUpdate(int time);

        public void onClickFullScreenBtn();

        public void onClickVideo();

        public void onClickBackBtn();

        public void onClickPlay();

        public void onAdVideoLoadSuccess();

        public void onAdVideoLoadFailed();

        public void onAdVideoLoadComplete();
    }

二、在A类中implement此接口

在逻辑层中实现此接口,并且实现相应方法

public class VideoAdSlot implements CustomVideo.ADVideoPlayerListener{

}

三、在A类中创建B类实例时,同时为B类setListener

在创建B类(自定义类CustomVideo)的时候,同时为它绑定一个Listener。

private void initVideoView() {
        //创建B类(自定义Video类)
        mVideoView = new CustomVideoView(mContext, mParentView);
        if (mXAdInstance != null) {
            mVideoView.setDataSource(mXAdInstance.resource);
            //注入一个listener
            mVideoView.setListener(this);
        }
        mParentView.addView(mVideoView);
    }

这样当,在B类(CustomVideo)中触发对应事件时,就会通知到A类(逻辑层)实现此方法。


private ADVideoPlayerListener listener;

@Override
    public void onClick(View v) {
        if (v == this.mMiniPlayBtn) {
            if (this.playerState == STATE_PAUSING) {
                if (Utils.getVisiblePercent(mParentContainer)
                        > SDKConstant.VIDEO_SCREEN_PERCENT) {
                    resume();
                    this.listener.onClickPlay();
                }
            } else {
                load();
            }
        } else if (v == this.mFullBtn) {
            this.listener.onClickFullScreenBtn();
        } else if (v == mVideoView) {
            /**
            *当点击视频区的时候,就会调用listener的onClickVideo
            *当事件真正产生的时候,就会调用A类(逻辑层)的onClickVideo()
            *这样也就完成了相互的通信。
            *A类既可以调用B类的的方法,B类也可以调用A类中的方法。
            */
            this.listener.onClickVideo();
        }
    }


总结:

在三层中,当业务层调用数据层时,是不需要把业务层自身传递到数据层的,并且这是一种上层调用下层的关系,比如我们在用框架的时候,一般直接调用框架提供的API就可以了,但回调不同,当框架不能满足需求,我们想让框架来调用自己的类方法,怎么做呢?总不至于去修改框架吧。许多优秀的框架提几乎都供了相关的接口,我们只需要实现相关接口,即可完成了注册,然后在合适的时候让框架来调用我们自己的类。

最后,再举一例,为了使我们写的函数接近完美,就把一部分功能外包给别人,让别人个性化定制,至于别人怎么实现不管,我唯一要做的就是定义好相关接口,这一设计允许了底层代码调用高层定义的子程序,增强程序灵活性,和反射有着异曲同工之妙,这才是回调的真正原因!

用一段话来总结下回调:上层模块封装时,很难预料下层模块会如何实现,因此,上层模块只需定义好自己需要但不能预料的接口(也就是回调接口),当下层模块调用上层模块时,根据当前需要的实现回调接口,并通过注册或参数方式传入上层模块即可,这样就实现下层调用上层,并且上层还能根据传入的引用来调用下层的具体实现,将程序的灵活性大大的增加了。


参考:

http://www.jcodecraeer.com/a/chengxusheji/java/2012/0822/370.html
https://blog.csdn.net/bjyfb/article/details/10462555

猜你喜欢

转载自blog.csdn.net/adonis044/article/details/80183335
今日推荐