接口回调目的和用法解析
一、为什么会有接口回调?什么是接口回调?
其实这两个问题是一个问题,知道了接口回调的原理自然就知道了为什么会有这么个东西。我们知道java中接口是不可以直接创建实例的,那么问题来了,假如我把一个接口声明为一个变量,那么我执行这个接口中的方法,接口没有实例它该怎么办呢?啊哈,这里自然又改出现java中的另一个特性—“多态”,这时java虚拟机自然会去找其子类,调用其子类中已经重载的该方法,这里就是接口回调的本质!!我们只需要给该变量指向其子类的地址就可以在调用的时候知道调用子类的方法。那么我们就可以在A类中创建接口的子类实例,在B类中创建一个接口的变量,把A类的地址传给B类的变量,在变量执行接口中的方法的时候就会调用A类中重写的方法,这就是接口回调的执行步骤。我们在网络请求等耗时的操作的时候会使用到该机制,用来把得到的数据传回主线程中。
二、接口回调(Java代码)
一、回调的含义和用途
1. 什么是回调
一般来说,模块之间都存在一定的调用关系,从调用方式上来看,可分为三类:
-
同步调用:同步调用是一种阻塞式调用,即在函数A的函数体里通过书写函数B的函数名来调用之,使内存中对应函数B的代码得以执行。
-
异步调用:异步调用是一种类似消息或事件的机制解决了同步阻塞的问题,例如A通知B后,他们各走各的路,互不影响,不用像同步调用那样,A通知B后,非得等到B走完后,A才继续走。
-
回调:回调是一种双向的调用模式,也就是说,被调用的接口被调用时也会调用对方的接口,例如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 {@Overridepublic 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.测试类代码:
// 然而Java中没有指针,不能传递方法的地址,一般采用接口回调实现:把实现某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。
public class Client {public static void main(String[] args) {Employee emp = new Employee();//将回调对象(上层对象)传入,注册emp.setCallBack(new Boss());//开启控制器对象运行emp.doSome();}}
举例2:
对于回调函数的理解可以参照C或C++中对回调函数的定义:
程序在调用一个函数时,将自己的函数的地址作为参数传递给程序调用的函数时(那么这个自己的函数称回调函数)
然而Java中没有指针,不能传递方法的地址,一般采用接口回调实现:把实现某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。
例如:
一读者想借《软件技术学习与实践》这本书,但这本书已被其他读者借走了。于是,读者与图书馆管理员间发生了以下对话:
读者:“我把我的电话号码告诉你,等书一到就马上通知我。”
管理员:“好的。另一读者把书还回来后,马上给您打电话,书我先帮您留着。”
在上述这个场景中,读者就是“回调对象”,管理员就是“控制器对象”,读者的电话号码就是“回调对象的方法”。
在控制器类中引用了回调对象,因此就能调用回调方法,当控制器进行某些判断之后(如:监听鼠标单击操作)就会自动调用回调方法!简易流程图如下:
总结:
-
在三层中,当业务层调用数据层时,是不需要把业务层自身传递到数据层的,并且这是一种上层调用下层的关系,比如我们在用框架的时候,一般直接调用框架提供的API就可以了,但回调不同,当框架不能满足需求,我们想让框架来调用自己的类方法,怎么做呢?总不至于去修改框架吧。许多优秀的框架提几乎都供了相关的接口,我们只需要实现相关接口,即可完成了注册,然后在合适的时候让框架来调用我们自己的类。
-
最后,再举一例,为了使我们写的函数接近完美,就把一部分功能外包给别人,让别人个性化定制,至于别人怎么实现不管,我唯一要做的就是定义好相关接口,这一设计允许了底层代码调用高层定义的子程序,增强程序灵活性,和反射有着异曲同工之妙,这才是回调的真正原因!
-
用一段话来总结下回调:上层模块封装时,很难预料下层模块会如何实现,因此,上层模块只需定义好自己需要但不能预料的接口(也就是回调接口),当下层模块调用上层模块时,根据当前需要的实现回调接口,并通过注册或参数方式传入上层模块即可,这样就实现下层调用上层,并且上层还能根据传入的引用来调用下层的具体实现,将程序的灵活性大大的增加了。
二、接口回调基础理解(Android)
-
接口回调听起来好像很厉害的样子,但其实只要能够搞清楚代码的执行过程,多看几遍,并且认真思考,再加上勤奋的练习,熟练掌握简单的接口回调并不是难题,接下来,我会用一个简单的例子,来带大家一起分析代码的执行过程,以及这样写的好处,以及这样写的思路。
-
我们知道recyclerView是没有自带点击事件的,所以这里我们就拿给recyclerView添加点击事件为例。
ok 我们做好了一个简单的recycerView,但是此时点击他的条目是无效的,接下来我们为他添加自己定义的条目点击事件。
在适配器中加入:
//自定义公开接口,定义抽象方法public interface onItemClickListener{void onItemClick(int position,View itemView);}//创建接口类型变量(接受传值并设置事件)private onItemClickListener mOnItemClickListener;//暴露设置接口的方法public void setOnItemClickListener(onItemClickListener mOnItemClickListener){this.mOnItemClickListener = mOnItemClickListener;}
在适配器的onBindViewHolder方法中开始监听事件:
@Overridepublic void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {final MyViewHolder viewHolder = (MyViewHolder) holder;viewHolder.textView.setText(datas.get(position));if (position % 2 == 0){viewHolder.textView.setTextColor(Color.BLUE);}else {viewHolder.textView.setTextColor(Color.RED);}if (mOnItemClickListener != null){viewHolder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {int pos = holder.getLayoutPosition();mOnItemClickListener.onItemClick(pos,holder.itemView);}});}}
最后一步就是在MainActivity中,实现onItemClickListener接口,设置监听,并实现抽象方法:
public class MainActivity extends AppCompatActivity implements MyAdapter.onItemClickListener
adapter.setOnItemClickListener(this);
@Overridepublic void onItemClick(int position, View itemView) {Toast.makeText(MainActivity.this, "条目位置 :" + ( position + 1 ), Toast.LENGTH_SHORT).show();}
OK,点击事件添加成功
很简单,很快,但是这个自己添加的点击事件,其中代码的执行过程大家清楚么?
按照代码的执行顺序,我们添加的代码第一句执行的就是:
adapter.setOnItemClickListener(this);
这是调用了适配器设置监听的方法,this代表当前类,我们这里指的就是MainActivity,而这个方法定义的参数要求是一个我们自己定义的接口,所以我们要通过MainActivity去实现OnItemClickListener接口,那么此时我们的MainActivity就也可以称作是OnItemClickListener类型。
将MainActivity传入后,就是将我们传入的值赋值给了自己定义的接口类型变量
//暴露设置接口的方法public void setOnItemClickListener(onItemClickListener mOnItemClickListener){this.mOnItemClickListener = mOnItemClickListener;}
然后我们再来看一看这个自己定义的变量去做了什么:
if (mOnItemClickListener != null){viewHolder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {int pos = holder.getLayoutPosition();mOnItemClickListener.onItemClick(pos,holder.itemView);}});}
这是添加在onBindViewHolder中的,在调用了setOnItemClickListener方法后,mOnItemClickListener就不为空,然后当viewholder的条目点击之后,我们的mOnItemClickListener就调用了自己的onItemClick方法。
开始思考:我们这里的mOnItemClickListener不为空,那么他的值是哪里来的呢?是谁呢? 没有错,就是我们的MainActivity,那么这里调用了onItemClick方法,实际上调用的就是MainActivity中实现的抽象方法onItemClick
@Overridepublic void onItemClick(int position, View itemView) {Toast.makeText(MainActivity.this, "条目位置 :" + ( position + 1 ), Toast.LENGTH_SHORT).show();}
至此,我们得以解惑,只要在MainActivity中,实现一个接口,就实现了RecyclerView的点击事件,也了解了这个代码的执行过程,不过有人可能会疑惑,点击事件我直接写在适配器中不好吗,这样MainActivity中的代码不是会更少,更简洁吗?
好,根据质疑,我将代码做了如上修改,确实,点击事件依然好用,但是接口回调就没有作用吗?那还要他干嘛。好了,我要提出需求了。
如果我们的需求是点击了一条目之后,修改MainActivity中一个控件(例如Button)的背景颜色,想一想,此时怎么操作?
可能还真有头铁的愣头青会说:可以在MainActivity中通过适配器的构造方法,把控件(例如Button)对象传到Adapter里面,很好,很有思想。但是我这里首先不说你违背了通俗的编程习惯(我没怎么见过把一个控件对象传来传去的),如果我要有100个控件要改变呢?
显然 100个夸张了一点,但是基本的设计模式,我们还是要遵循的,所以接口回调的好处,大家体会到了吗?
参考:
https://blog.csdn.net/Adonis044/article/details/80183335
https://blog.csdn.net/qq_39085422/article/details/78453788?depth_1-utm_source=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-1&utm_source=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-1