Java的回调机制之前一直不太明白,只知道(嗯,这个地方好像是采用了回调,balabala),这两天工程不是很忙就抽了些时间从源码上学习巩固一下。
在Android上,最经典的回调应该是监听器Onclick回调了,其实还有Thread啊等等。辣么,重点来瞅瞅这个onClick()具体是怎么实现回调的涅。
1、先分析一下回调实现的理论知识
首先,来回顾下JAVA回调机制的过程
大家都喜欢用打电话来形容回调。不! 我才不会这么俗,我不用打电话来形容。我们来发短信吧。
小鸟:大鸟,有个问题要问你,为什么人们都叫你大鸟呢?
大鸟:Let me see see,我也不晓得,待我分析分析再回你短信
...
...(1天后,期间小鸟自己去泡其他鸟,就叫异步。煞笔的像暗恋一样等着大鸟回短信,就叫同步吧)
...
大鸟:想了很久这个问题我知道答案了,因为我的名字叫大鸟
小鸟:=。= 噢
以上呢就是回调与暗恋的过程。不知道我有没有表达清楚。
听说不爆照都没人看,辣么我补个图
在别处看到对回调的定义值得借鉴理解:回调函数就是预留给系统调用的函数,而且我们往往知道该函数被调用的时机
我们从代码上来看下实现,ClassA是小鸟,ClassB是大鸟,至于这个interface嘛,我觉得可以理解为两只鸟的约定(通过短信来告知答案)
a接口是作为回调的纽带
public interface CompomentCallbacks {
int TEST_CALLBACK = 1;
void method(String answer);
}
public interface CompomentCallbacks {
int TEST_CALLBACK = 1;
void method(String answer);
}
b、ClassA实现这个接口,并在这个接口定义的方法里头写上对于答案的反馈(=。= 噢)。即小鸟想知道答案,所以她得实现这个接口。
public class ClassA implements CompomentCallbacks {
@Override
public void method(String answer) {
System.out("I am called back by ClassB, the answer is " + answer);
}
}
c、ClassB需要写个方法来接收这个接口,也就是接收小鸟发给自己的消息,并且通过发过来的接口去回调ClassA中的接口方法,以便去通知小鸟自己知道答案了。即大鸟想和小鸟通信,告诉小鸟自己的答案,必须留个渠道(也就是留个电话号码)给她问。
public class ClassB {
static void iThinkThink(final CompomentCallbacks callbacks){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100000000000000000);//花了一天的时间
callbacks.method("Answer:My name is 大鸟");//不管花了多少天,通过小鸟给的这个接口短信,大鸟都可以告诉她答案了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
d、亲测一下
小鸟想知道大鸟为什么叫大鸟,这个问题可以通过自己来亲自问,也可以托朋友问,答案都会通过回调来反馈给小鸟。
我们原来的设定是自己问:
public void callbacktest(){
ClassB b = new ClassB();
b.iThinkThink(ClassA.this);//这个调用如果发生在ClassA里面就是小鸟自己问的,如果发生在别的类里面就是托别人问的
}
当然,这个method方法的参数不是CompomentCallbacks接口吗,为什么可以传ClassA进去呢?这个涉及一个转型的问题,具体自行度娘。反正我的粗暴理解是把接口看成一个抽象类,你参数规定是爹CompomentCallbacks,那我传个干儿子(注意不是亲儿子,继承的才是亲儿子)ClassA进去也是可以的。
可以移步 上一篇
2、辣么,步入正题看看我们是如何实现onclick的
(a)采用匿名内部类实现
这种方式传参是接口。new的是一个接口。
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
<span> </span>//BALABALA
}
});
或者:
(b)类实现OnClickListener接口
该接口呢是View的一个内部接口,只有一个实现方法。这种方式传参是实现该接口的类
//源码
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
/**
* 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);
}
}
我们代码里一般这么用
/**
* Created by kongtianhao on 2016/3/7.这个类相当于ClassA小鸟,想知道答案,所以和大鸟约定用短信onClick()来回答
*/
public class TestA implements View.OnClickListener {
button.setOnClickListener(this);
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.test1:
//BALBALA
break;
case
...
}
}
}
这两种方式是一样一样的,只是前一种采用匿名内部类实现,后一种更适合监听多个view。为方便理解,可以第二种方式作为基础来理解。这个其实没多大关系啦。。。
接下来追踪一下:
setOnClickListener方法相当于ClassA把自己当参数传给ClassB,以便它调用自己
/**
* 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(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;//一个view组件含多个Listener监听器,这个只赋值click,其他的还有OnFocusChangeListener、OnLongClickListener等
}
从系统的触摸事件触发调用onclick方法
↓
额 本来想只贴重点出来的,发现特么的这个onTouchEvent对于整个事件流程很关键,就全贴了
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
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, x, y);
}
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) {
//这里就是重点了,触摸事件流程走到这里如果这个组件注册了listener,就会回调该组件的onClick方法
// 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();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// 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;
}
辣么我们继续追踪performClick()↓
/**
* 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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
源码的注释也写得很清楚了,如果view定义了监听器(也就是setOnClickListener(this)这个步骤),和点击事件有关联的action都会触发这个操作,也就是说会触发回调之前传进来的实现了OnClickListener接口的类对应的onClick方法,时序就会又跑到我们自己的代码中去了。
以上为个人总结,如有偏颇谬误敬请你 不要说!
没有啦~ 这只是内心独白 要是有意见大家可以讨论啦
另外补几个干货文章:
http://blog.csdn.net/xiaanming/article/details/8703708/
http://blog.csdn.net/bjyfb/article/details/10462555
http://www.2cto.com/kf/201412/365788.html
http://blog.csdn.net/lindir/article/details/7819720
转载请注明出处:http://blog.csdn.net/kong92917