回调函数机制

引言

在Android的学习过程中经常会听到或者见到“回调”这个词,那么什么是回调呢?所谓的回调函数就是:在A类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要B类去实现,B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用,这种机制就称为回调。

这么说可能还是有些模模糊糊,接下来我们用类比的方法一步步来看到底该怎么写一个回调函数,因为android回调中最常见的是Button的点击事件的回调,这里以此为参照:

  • 1、在A类中定义一个接口:需要我们在类中定义出一个接口,并且给这个接口定义出一个抽象方法,就像下面这样:
1
2
3
public interface CallBack{
public abstract void work()
}

以下是View.java类中定义的响应点击事件的接口:

1
2
3
4
5
6
7
8
9
10
11
/**
* Interface definition for a callback to be invoked when a view is clicked.
*/
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
  • 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
2
3
public void setCallBack(CallBack callBack) {    
this.mCallBack = 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
2
3
public void doWork(){
mCallBack.work();
}

View.java中的体现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);//就是这里
result = true;
} else {
result = false;
}

这里附上整个项目的代码,这里A类映射到实际中使用Employee 这个类来代表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Employee {    
/*
* 定义回调接口的成员变量
*/
private CallBack mCallBack;
/*
* 声明回调接口
*/
public interface CallBack{
public abstract void work();
}
/*
* 设置回调接口对象成员变量
*/
public void setCallBack(CallBack callBack) {
this.mCallBack = callBack;
}
/*
* 调用回调接口对象中的方法
*/
public void doWork() {
mCallback.work();
}
}

我们在定义出一个B类,就用Boss类吧:

1
2
3
4
5
6
7
8
9
10
11
12
public class Boss {    
private Employee employee;
/*
* 为Employee设置回调函数, 在这里定义具体的回调方法
*/
employee.setCallback(new Employee.Callback() {
@Override
public void work() {
System.out.println("work");
}
});
}

如果第一眼看不明白,我们附上我们最常用的Button点击事件的处理的代码,这里Employee 类类比一下就是View类:

1
2
3
4
5
6
7
8
9
10
11
12
public class TestCallBack{
private Button button;
button.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
//做一些操作
doWork();
}
});

}

这时候我们在再回头关于回调的定义:

在A类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要B类去实现,B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用

这里回到我们的代码中就是:
我们在Employee(View)类中定义了一个接口,接口当中还含有一个抽象方法,这个抽象方法没有具体的实现,当我们需要时候自己去实现这个方法,比如这里的Boss (Button)类,这句话可能难以理解:B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用,有些人会想,诶,在onClick()方法中我不是写了具体的实现嘛,其实真的是这样吗,我们接着往下看,我们就来好好的分析一下这个Button点击事件。
分析
首先,在View类中我们能找到setOnClickListener(OnClickListener l)方法:

1
2
3
4
5
6
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}

这里将OnClickListener赋值给了mOnClickListener,我们想要找到onClick()方法是由View回调而不是Button自己回调的证据,就在这里:

1
2
3
4
5
6
7
8
9
10
public boolean performClick() {  
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
return true;
}
return false;
}

对此我们的解释是:在父类中我们要用到onClick()方法,但是父类却没有去实现该方法,而是定义了一个方法setOnClickListener(OnClickListener l),如果子类想要自己能够响应点击事件,则它就必须重写父类的该方法,实现OnClickListener接口和它的onClick()方法。在子类实现该接口和方法后,将其通过参数传递给父类,在父类中执行onClick()方法。那么我们是如何运行到这个OnClick()函数的呢,这里由于涉及到View的事件分发机制不细说,想了解的话网上有很多资料,这里只给出结论,因为我们在TestCallBack这个类中没有实现OnTouchListener这个接口,那么当点击事件发的时候必然会运行到onTouchEvent()这个方法,我们来看一下这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public boolean onTouchEvent(MotionEvent event) {  
// 略去无用代码...
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

switch (event.getAction()) {

case MotionEvent.ACTION_UP:

// 略去无用代码...
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick(); //重点在这
}
}
}
//略去无用代码..
break;
}
return true;
}
return false;
}

还记得前面我们在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中回调函数机制解析

文章作者: 李牧羊
文章链接: https://www.limuyang.cc/2016/04/01/回调函数机制/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Atlantis