详细介绍Android中回调函数机制
提示:在阅读本文章之前,请确保您对touch事件的分发机制有一定的了解
在android的学习过程中经常会听到或者见到“回调”这个词,那么什么是回调呢?所谓的回调函数就是:在a类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要b类去实现,b类实现该方法后,它本身不会去调用该方法,而是传递给a类,供a类去调用,这种机制就称为回调。
下面我们拿具体的button的点击事件进行模拟分析:
首先,在view类中我们能找到setonclicklistener(onclicklistener l)方法:
public void setonclicklistener(onclicklistener l) {
if (!isclickable()) {
setclickable(true);
}
getlistenerinfo().monclicklistener = l;
}
可以看到,在该方法中将onclicklistener赋值给了monclicklistener,那么我们继续向下找,会看到在performclick()方法中执行了我们实现的onclick()方法。
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()方法。
那么,为什么会在父类中执行到该方法呢,这就要说到android中的另一个重要的机制——触摸事件的传递机制。
我们知道,只要我们的手指触摸到手机屏幕,就一定会执行dispatchtouchevent(motionevent event)方法,接下来我们就看一下dispatchtouchevent方法中都有哪些内容:
public boolean dispatchtouchevent(motionevent event) {
if (minputeventconsistencyverifier != null) {
minputeventconsistencyverifier.ontouchevent(event, 0);
}
if (onfiltertoucheventforsecurity(event)) {
//noinspection simplifiableifstatement
listenerinfo li = mlistenerinfo;
if (li != null && li.montouchlistener != null
&& (mviewflags & enabled_mask) == enabled
&& li.montouchlistener.ontouch(this, event)) {
return true;
}
if (ontouchevent(event)) {
return true;
}
}
if (minputeventconsistencyverifier != null) {
minputeventconsistencyverifier.onunhandledevent(event, 0);
}
return false;
}
这里我们不细讲touch事件的分发机制,因为网上有哥们已经讲的很清楚了。请参看篇首提供的链接。
我们看一下第17行,由于我们没有实现ontouchlistener接口,而ontouch()方法的默认返回值为false,所以第一个if语句中的代码不会被执行到,进入第二个if语句中,执行了ontouchevent()方法。那么我们再来看一下该方法:
public boolean ontouchevent(motionevent event) {
final int viewflags = mviewflags;
if ((viewflags & enabled_mask) == disabled) {
if (event.getaction() == motionevent.action_up
&& (mprivateflags & pflag_pressed) != 0) {
setpressed(false);
}
// a disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewflags & clickable) == clickable ||
(viewflags & long_clickable) == long_clickable));
}
if (mtouchdelegate != null) {
if (mtouchdelegate.ontouchevent(event)) {
return true;
}
}
if (((viewflags & clickable) == clickable ||
(viewflags & long_clickable) == long_clickable)) {
switch (event.getaction()) {
case motionevent.action_up:
boolean prepressed = (mprivateflags & pflag_prepressed) != 0;
if ((mprivateflags & pflag_pressed) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focustaken = false;
if (isfocusable() && isfocusableintouchmode() && !isfocused()) {
focustaken = requestfocus();
}
if (prepressed) {
// the button is being released before we actually
// showed it as pressed. make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setpressed(true);
}
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();
}
}
}
if (munsetpressedstate == null) {
munsetpressedstate = new unsetpressedstate();
}
if (prepressed) {
postdelayed(munsetpressedstate,
viewconfiguration.getpressedstateduration());
} else if (!post(munsetpressedstate)) {
// if the post failed, unpress right now
munsetpressedstate.run();
}
removetapcallback();
}
break;
case motionevent.action_down:
mhasperformedlongpress = false;
if (performbuttonactionontouchdown(event)) {
break;
}
// walk up the hierarchy to determine if we're inside a scrolling container.
boolean isinscrollingcontainer = isinscrollingcontainer();
// for views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isinscrollingcontainer) {
mprivateflags |= pflag_prepressed;
if (mpendingcheckfortap == null) {
mpendingcheckfortap = new checkfortap();
}
postdelayed(mpendingcheckfortap,
viewconfiguration.gettaptimeout());
} else {
// not inside a scrolling container, so show the feedback right away
setpressed(true);
checkforlongclick(0);
}
break;
case motionevent.action_cancel:
setpressed(false);
removetapcallback();
removelongpresscallback();
break;
case motionevent.action_move:
final int x = (int) event.getx();
final int y = (int) event.gety();
// be lenient about moving outside of buttons
if (!pointinview(x, y, mtouchslop)) {
// outside button
removetapcallback();
if ((mprivateflags & pflag_pressed) != 0) {
// remove any future long press/tap checks
removelongpresscallback();
setpressed(false);
}
}
break;
}
return true;
}
return false;
}
代码太长,我们只看重点,在action_up这个case当中,我们找到了关键的代码(第109行):performclick()。
至此,我们已经基本搞清楚了回调机制的整个过程。