Android 中多次设置 OnClickListener 只执行一次吗?

问题

对于 Android 初学者,可能对这个问题会比较疑惑: 对于一个 View,比如 Button,如果为其设置多次点击监听 OnClickListener 回调方法,同时还在布局中设置了 onClick 属性,并且也实现了点击回调方法,那么问题来了,哪些回调方法会执行呢?又是以怎样的顺序执行呢?请跟随脚步和我一探究竟…

实验现象

我们先来做个实验,观察一下实验现象。
首先在布局文件中声明一个 Button,并为其设置好点击属性:

<Button
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="click()"
    android:text="BUTTON"/>

嗯,对,然后再在 Activity 中实现方法:

void click(View v) {
    Log.i(TAG, "click: in layout file");
}

这样第一组测试样例放置好了,第二组和第三组很容易,先后在 onCreate() 中设置两次监听,都记得打上 Log 日志:

mButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.i(TAG, "click: in onCreate() first");
    }
});
mButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.i(TAG, "click: in onCreate() second");
    }
});

然后,见证奇迹了,运行观察实验结果:

I/MainActivity: click: in onCreate() second

你没有看错,只有一条结果,而且是第二次的结果,说明优先级 java 设置监听的优先级大于布局文件,而且最后一次设置的监听会覆盖前一次设置的监听。

结论
点击监听优先级: 之后设置的优先级大于之前设置的优先级,代码中设置的优先级大于布局中设置的优先级

寻找答案

结果很出乎意料,也许猜的都会执行,也许你猜的布局先执行,却没想到只有最后一次设置的执行,这其中的神秘之处究竟何在呢?让我们 Read The Fuck Source Code.

setOnClickListener

先从 setOnClickListener() 入手:

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

先是将该 View 设置为可点击状态,所以即便一个 View 是不可点击的,你为其设置了监听,也会将其恢复成可点击状态。再是将 getListenerInfo() 返回的对象中的成员 mOnClickListener 直接复制为参数 l,还不明白?再看:

ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    // 如果为空,重新创建用于保存所有监听器的容器类
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

// 容器类定义
static class ListenerInfo {
    ...
    // 是数组 事件发生回调所有监听器
    private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;

    // 不是数组 直接对其进行赋值
    public OnClickListener mOnClickListener;

    protected OnLongClickListener mOnLongClickListener;
    ...

哈,是不是恍然大悟,所以每一次设置监听都是对这个 mOnClickListener 成员进行替换赋值,所以说最后一次设置监听才是有效的。

布局文件

Java 中的监听确实明白了,那么布局文件中又是怎么回事呢?
通过 setOnClickListener() 可以对 mOnClickListener 进行修改,那么我们找一下这个函数在哪里有调用,果然找到了,
View 的构造函数中,有这样两行代码:

// 获取布局 android:onClick="" 属性值
final String handlerName = a.getString(attr);
if (handlerName != null) {
    setOnClickListener(new DeclaredOnClickListener(this, handlerName));
}

也就是说,View 默认就设置了一个叫做 DeclaredOnClickListener 的监听器:

private static class DeclaredOnClickListener implements OnClickListener {

    ...
    @Override
    public void onClick(@NonNull View v) {
        if (mMethod == null) {
            mMethod = resolveMethod(mHostView.getContext(), mMethodName);
        }
        // 调用方法,并携带参数 v
        mMethod.invoke(mHostView.getContext(), v);
        ...
    }

默认的实现是通过 resolveMethod() 获取到方法,并调用之:

private Method resolveMethod(@Nullable Context context, @NonNull String name) {
    ...
    if (!context.isRestricted()) {
        // 使用参数 name 反射取得方法
        return context.getClass().getMethod(mMethodName, View.class);
    }
    ...

是不是很眼熟了,这不就是反射获取到方法吗?!一切都明白了吧,在 View 创建之初就设置了一个默认监听, 默认监听是调用的所在 Context 中的符合布局中定义的方法签名的方法。

结论

所以,总的来说是这样的,View 在构造之初就默认设置好了一个监听器,View 的构造也是在 onCreate() 方法执行前完成的,对于每一次设置监听都会覆盖上一次的监听,所以最后一次设置的监听才会是有效的。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值