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

您所在的位置:网站首页 重复声明两次的变量以第二次为准 Android 中多次设置 OnClickListener 只执行一次吗?

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

2024-06-07 14:15| 来源: 网络整理| 查看: 265

问题

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

实验现象

我们先来做个实验,观察一下实验现象。 首先在布局文件中声明一个 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 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() 方法执行前完成的,对于每一次设置监听都会覆盖上一次的监听,所以最后一次设置的监听才会是有效的。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3