引言
在Android的学习过程中经常会听到或者见到“回调”这个词,那么什么是回调呢?所谓的回调函数就是:在A类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要B类去实现,B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用,这种机制就称为回调。
这么说可能还是有些模模糊糊,接下来我们用类比的方法一步步来看到底该怎么写一个回调函数,因为android回调中最常见的是Button的点击事件的回调,这里以此为参照:
- 1、在A类中定义一个接口:需要我们在类中定义出一个接口,并且给这个接口定义出一个抽象方法,就像下面这样:
1 | public interface CallBack{ |
以下是View.java
类中定义的响应点击事件的接口:
1 | /** |
- 2、在A类中定义出该接口的一个成员变量:
1 | public CallBack mCallBack |
以下是View.java
类中获取点击事件接口成员变量的源码:1
2
3
4
5
6/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
- 3、在A类中定义出一个公共方法,可以用来设置这个接口的对象,调用该方法可以给接口对象变量赋值:
1 | public void setCallBack(CallBack callBack) { |
这里看英文注释也看得出来是什么意思,是不是想到了我们平常使用setOnClickListener(OnClickListener l)
的时候呢:1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
最后一步,如果说前面的都没问题,但这一步可能更不好理解了,不过没关系,我们先看一下
- 4、在A类中调用接口对象中的方法:
1 | public void doWork(){ |
在View.java
中的体现:
1 | /** |
这里附上整个项目的代码,这里A类映射到实际中使用Employee
这个类来代表:
1 | public class Employee { |
我们在定义出一个B类,就用Boss
类吧:
1 | public class Boss { |
如果第一眼看不明白,我们附上我们最常用的Button点击事件的处理的代码,这里Employee 类类比一下就是View
类:
1 | public class TestCallBack{ |
这时候我们在再回头关于回调的定义:
在A类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要B类去实现,B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用
这里回到我们的代码中就是:
我们在Employee(View)类中定义了一个接口,接口当中还含有一个抽象方法,这个抽象方法没有具体的实现,当我们需要时候自己去实现这个方法,比如这里的Boss (Button)类,这句话可能难以理解:B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用,有些人会想,诶,在onClick()
方法中我不是写了具体的实现嘛,其实真的是这样吗,我们接着往下看,我们就来好好的分析一下这个Button点击事件。
分析
首先,在View类中我们能找到setOnClickListener(OnClickListener l)方法:
1 | public void setOnClickListener(@Nullable OnClickListener l) { |
这里将OnClickListener赋值给了mOnClickListener,我们想要找到onClick()方法是由View回调而不是Button自己回调的证据,就在这里:
1 | public boolean performClick() { |
对此我们的解释是:在父类中我们要用到onClick()
方法,但是父类却没有去实现该方法,而是定义了一个方法setOnClickListener(OnClickListener l),如果子类想要自己能够响应点击事件,则它就必须重写父类的该方法,实现OnClickListener接口和它的onClick()
方法。在子类实现该接口和方法后,将其通过参数传递给父类,在父类中执行onClick()
方法。那么我们是如何运行到这个OnClick()
函数的呢,这里由于涉及到View的事件分发机制不细说,想了解的话网上有很多资料,这里只给出结论,因为我们在TestCallBack这个类中没有实现OnTouchListener
这个接口,那么当点击事件发的时候必然会运行到onTouchEvent()
这个方法,我们来看一下这个方法:
1 | public boolean onTouchEvent(MotionEvent event) { |
还记得前面我们在performClick();类里面找到的关于View.java类中对于回调方法onClick的调用么
1 | li.mOnClickListener.onClick(this);//就是这里 |
这样我们就完整的了解了整个OnClickListener()
接口中体现出来的回调机制
总结一下
为了实现一个回调方法,首先要先定义一个包含了接口的类,并且这个接口中要有一个抽象方法,这个抽象方法的具体实现由其他类来完成(比如我们响应Button的点击事件,onClick()
方法里写上当点击事件产生并且该方法被调用时候需要做的操作,比如显示一些文本信息等等),最后该方法的回调是之前的包含有抽象方法的那个接口所在的类去调用的,比如说onClick()方法是当点击事件产生之后经过一系列的事件分发在View
类中被调用的。这其中的奥秘就是:
回调其实是一种双向调用模式,也就说调用方在接口被调用时也会调用对方的接口,实现方法交还给提供接口的父类处理!
为什么要用回调
我们都知道Java是一门面向对象的语言,有一句很著名的话就是”万事万物皆为对象”,我们把普通事物的共性抽取出来,而这些共性之中又充斥着特性,每个不同的特性就需要交给特定的情况处理,通过暴露接口方法可以减少很多重复,代码更加优雅。
打个比方,Button、ImageButton等都具有可被点击的共性,但是被点击之后相关事件的处理是不同的,比如说我想我要点击的这个Button弹出一个消息提示,然而我希望我的ImageButton点击之后可以弹出一个Notifaction通知,这个时候回调方法的好处就体现出来了,因为android对外暴露的OnClickListener()
接口中含有一个OnClick()
方法,你需要怎样的具体实现都由你自己定义,而这个回调方法的所在类View
不会管你怎么实现的,它只负责调用这个回调方法,这就是使用回调的好处。
主要参考
android中的回调
Android中回调函数机制解析