一般来说,模块之间都存在一定的调用关系,从调用方式上来看,可分为三类:
回调一般用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调。例如作为一个驱动,是一个底层,他在收到一个数据时,除了完成本层的处理工作外,还将进行回调,将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。
有一位老板(上层模块)很忙,他没有时间盯着员工(下层模块)干活,然后他告诉自己的雇员,干完当前这些事情后,告诉他干活的结果。这个例子其实是一个回调+异步的例子,再举一个例子,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();
}
}
对于回调函数的理解可以参照C或C++中对回调函数的定义:
程序在调用一个函数时,将自己的函数的地址作为参数传递给程序调用的函数时(那么这个自己的函数称回调函数)
然而Java中没有指针,不能传递方法的地址,一般采用接口回调实现:把实现某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。
例如:
一读者想借《软件技术学习与实践》这本书,但这本书已被其他读者借走了。于是,读者与图书馆管理员间发生了以下对话:
读者:“我把我的电话号码告诉你,等书一到就马上通知我。”
管理员:“好的。另一读者把书还回来后,马上给您打电话,书我先帮您留着。”
在上述这个场景中,读者就是“回调对象”,管理员就是“控制器对象”,读者的电话号码就是“回调对象的方法”。
在控制器类中引用了回调对象,因此就能调用回调方法,当控制器进行某些判断之后(如:监听鼠标单击操作)就会自动调用回调方法!简易流程图如下:
如果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