给一个View设置监听点击事件是再普通不过的事情,比如

    view.setOnClickListener(onClickListener);

另外一种做法是直接在XML布局里面指定View点击时候的回调方法,首先需要在Activity中编写用于回调的方法,比如

    public void onClickView(View view){        // do something    }

然后在XML设置View的android:onClick属性

    <View        android:layout_width="match_parent"        android:layout_height="match_parent"        android:onClick="onClickView" />

有的时候从XML布局里直接设定点击事件会比较方便(特别是在写DEMO项目的时候),这种做法平时用的人并不多,从使用方式上大致能猜出来,View应该是在运行的时候,使用反射的方式从Activity找到“onClickView”方法并调用,因为这种做法并没有用到任何接口。

接下来,我们可以从源码中分析出View是怎么触发回调方法的。


View有5个构造方法,第一个是内部使用的,平时在Java代码中直接创建View实例用的是第二种方法,而从XML布局渲染出来的View实例最后都是要调用第五种方法。

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        this(context);        final TypedArray a = context.obtainStyledAttributes(                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);                        for (int i = 0; i < N; i++) {            int attr = a.getIndex(i);            switch (attr) {                ……                // 处理onClick属性                case R.styleable.View_onClick:                    if (context.isRestricted()) {                        throw new IllegalStateException("The android:onClick attribute cannot "                                + "be used within a restricted context");                    }                    final String handlerName = a.getString(attr);                    if (handlerName != null) {                        // 给当前View实例设置一个DeclaredOnClickListener监听器                        setOnClickListener(new DeclaredOnClickListener(this, handlerName));                    }                    break;                }        }}

处理onClick属性的时候,先判断View的Context是否isRestricted,如果是就抛出一个IllegalStateException异常。看看isRestricted方法

    /**     * Indicates whether this Context is restricted.     *     * @return {@code true} if this Context is restricted, {@code false} otherwise.     *     * @see #CONTEXT_RESTRICTED     */    public boolean isRestricted() {        return false;    }

isRestricted是用于判断当前的Context实例是否出于被限制的状态,按照官方的解释,处限制状态的Context,会忽略某些特点的功能,比如XML的某些属性,很明显,我们在研究的android:onClick属性也会被忽略。

a restricted context may disable specific features. For instance, a View associated with a restricted context would ignore particular XML attributes.

不过isRestricted方法是Context中为数不多的有具体实现的方法(其余基本是抽象方法),这里直接返回false,而且这个方法只有在ContextWrapper和MockContext中有重写

public class ContextWrapper extends Context {    Context mBase;    @Override    public boolean isRestricted() {        return mBase.isRestricted();    }}public class MockContext extends Context {    @Override    public boolean isRestricted() {        throw new UnsupportedOperationException();    }}

ContextWrapper中也只是代理调用mBase的isRestricted,而MockContext是写单元测试的时候才会用到,所以这里的isRestricted基本只会返回false,除非使用了自定义的ContextWrapper并重写了isRestricted。
回到View,接着的final String handlerName = a.getString(attr);其实就是拿到了android:onClick="onClickView"中的“onClickView”这个字符串,接着使用了当前View的实例和“onClickView”创建了一个DeclaredOnClickListener实例,并设置为当前View的点击监听器。

/**     * An implementation of OnClickListener that attempts to lazily load a     * named click handling method from a parent or ancestor context.     */private static class DeclaredOnClickListener implements OnClickListener {        private final View mHostView;        private final String mMethodName;        private Method mMethod;        public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {            mHostView = hostView;            mMethodName = methodName;        }        @Override        public void onClick(@NonNull View v) {            if (mMethod == null) {                mMethod = resolveMethod(mHostView.getContext(), mMethodName);            }            try {                mMethod.invoke(mHostView.getContext(), v);            } catch (IllegalAccessException e) {                throw new IllegalStateException(                        "Could not execute non-public method for android:onClick", e);            } catch (InvocationTargetException e) {                throw new IllegalStateException(                        "Could not execute method for android:onClick", e);            }        }        @NonNull        private Method resolveMethod(@Nullable Context context, @NonNull String name) {            while (context != null) {                try {                    if (!context.isRestricted()) {                        return context.getClass().getMethod(mMethodName, View.class);                    }                } catch (NoSuchMethodException e) {                    // Failed to find method, keep searching up the hierarchy.                }                if (context instanceof ContextWrapper) {                    context = ((ContextWrapper) context).getBaseContext();                } else {                    // Can't search up the hierarchy, null out and fail.                    context = null;                }            }            final int id = mHostView.getId();            final String idText = id == NO_ID ? "" : " with id '"                    + mHostView.getContext().getResources().getResourceEntryName(id) + "'";            throw new IllegalStateException("Could not find method " + mMethodName                    + "(View) in a parent or ancestor Context for android:onClick "                    + "attribute defined on view " + mHostView.getClass() + idText);        }}

到这里就清楚了,当点击View的时候,DeclaredOnClickListener实例的“onClick”方法会被调用,接着会调用“resolveMethod”方法,使用反射的方式从View的Context中找一个叫“onClickView”方法,这个方法有一个View类型的参数,最后再使用反射调用该方法。要注意的是,“onClickView”方法必须是public类型的,不然反射调用时会抛出IllegalAccessException异常。

同时从源码也能看出,使用android:onClick设置点击事件的方式是从Context里面查找回调方法的,所以如果对于在Fragment的XML里创建的View,是无法通过这种方式绑定Fragment中的回调方法的,因为Fragment自身并不是一个Context,这里的View的Context其实是FragmentActivity,这也意味着使用这种方式能够快速地从Fragment中回调到FragmentActivity。

此外,从DeclaredOnClickListener类的注释也能看出android:onClick的功能,主要是起到懒加载的作用,只有到点击View的时候,才会知道哪个方法是用于点击回调的。

最后,特别需要补充说明的是,使用android:onClick给View设置点击事件,就意味着要在Activity里添加一个非接口的public方法。现在Android的开发趋势是“不要把业务逻辑写在Activity类里面”,这样做有利于项目的维护,防止Activity爆炸,所以尽量不要在Activity里出现非接口、非生命周期的public方法。因此,贸然使用android:onClick可能会“污染”Activity。

更多相关文章

  1. XML实体扩展攻击代码实例分享
  2. 详细介绍xml的使用方法总结
  3. 详细介绍解析Xml四种方法的示例代码
  4. Schema验证XML概念的实例代码详解
  5. XML数据解析的系统方法的代码分享
  6. 详解XML命名空间(XML Namespaces)介绍以及节点读取方法的示例代码
  7. FOR XML之RAW模式的代码实例详解
  8. 详解在XML文档中替换元素名称的方法(图)
  9. XML Spy实例代码详解(图)

随机推荐

  1. amlogic 编译 android
  2. android颜色管理
  3. android Rect类的使用
  4. Android的Activity启动流程分析
  5. Android保持屏幕常亮
  6. Android中文语音合成(TTS)各家引擎对比 .
  7. android gradle 配置 build.gradle
  8. Qt for Android(九) ——APP 崩溃卡死拉起
  9. Android(安卓)网络连接:Volley(齐射)之简单
  10. Android之service一些基础回顾