问题
对于 Android 初学者,可能对这个问题会比较疑惑: 对于一个 View
,比如 Button
,如果为其设置多次点击监听 OnClickListener
回调方法,同时还在布局中设置了 onClick
属性,并且也实现了点击回调方法,那么问题来了,哪些回调方法会执行呢?又是以怎样的顺序执行呢?请跟随脚步和我一探究竟…
实验现象
我们先来做个实验,观察一下实验现象。
首先在布局文件中声明一个 Button
,并为其设置好点击属性:
1 | <Button |
嗯,对,然后再在 Activity
中实现方法:
1 | void click(View v) { |
这样第一组测试样例放置好了,第二组和第三组很容易,先后在 onCreate()
中设置两次监听,都记得打上 Log
日志:
1 | mButton.setOnClickListener(new View.OnClickListener() { |
然后,见证奇迹了,运行观察实验结果:
1 | I/MainActivity: click: in onCreate() second |
你没有看错,只有一条结果,而且是第二次的结果,说明优先级 java
设置监听的优先级大于布局文件,而且最后一次设置的监听会覆盖前一次设置的监听。
结论
点击监听优先级: 之后设置的优先级大于之前设置的优先级,代码中设置的优先级大于布局中设置的优先级
寻找答案
结果很出乎意料,也许猜的都会执行,也许你猜的布局先执行,却没想到只有最后一次设置的执行,这其中的神秘之处究竟何在呢?让我们 Read The Fuck Source Code.
setOnClickListener
先从 setOnClickListener()
入手:
1 | public void setOnClickListener(@Nullable OnClickListener l) { |
先是将该 View
设置为可点击状态,所以即便一个 View
是不可点击的,你为其设置了监听,也会将其恢复成可点击状态。再是将 getListenerInfo()
返回的对象中的成员 mOnClickListener
直接复制为参数 l
,还不明白?再看:
1 | ListenerInfo getListenerInfo() { |
哈,是不是恍然大悟,所以每一次设置监听都是对这个 mOnClickListener
成员进行替换赋值,所以说最后一次设置监听才是有效的。
布局文件
Java
中的监听确实明白了,那么布局文件中又是怎么回事呢?
通过 setOnClickListener()
可以对 mOnClickListener
进行修改,那么我们找一下这个函数在哪里有调用,果然找到了,
在 View
的构造函数中,有这样两行代码:
1 | // 获取布局 android:onClick="" 属性值 |
也就是说,View
默认就设置了一个叫做 DeclaredOnClickListener
的监听器:
1 | private static class DeclaredOnClickListener implements OnClickListener { |
默认的实现是通过 resolveMethod()
获取到方法,并调用之:
1 | private Method resolveMethod(@Nullable Context context, @NonNull String name) { |
是不是很眼熟了,这不就是反射获取到方法吗?!一切都明白了吧,在 View
创建之初就设置了一个默认监听, 默认监听是调用的所在 Context
中的符合布局中定义的方法签名的方法。
结论
所以,总的来说是这样的,View
在构造之初就默认设置好了一个监听器,View
的构造也是在 onCreate()
方法执行前完成的,对于每一次设置监听都会覆盖上一次的监听,所以最后一次设置的监听才会是有效的。