越过Android中布局文件中使用onClick属性的坑

最近在使用onClick属性时,发现总是崩溃,没办法,回归本源这个时候就去翻官方文档,希望从中找到答案。先来看看官方文档是怎么描述的吧:

  • Name of the method in this View's context to invoke when the view is clicked. This name must correspond to a public method that takes exactly one parameter of type View. For instance, if you specify android:onClick="sayHello", you must declare a public void sayHello(View v) method of your context (typically, your Activity).

重点就是那个context,也就是说在声明onClick属性时,与之对应的回调方法必须要声明在上下文中,否则就会出错。原来是因为我在自定义的继承LinearLayout的类中使用了这个属性,那只能老老实实地设置监听Listener了。到这里还不够,虽然解决掉问题,但是没有找到对应的代码总是心里不踏实(以前听张龙老师的JavaSE的视频时,老师说源码摆在面前才能说明问题),结果就在View的源码中找到了如下代码(大概4200多行):

    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) {
                    setOnClickListener(new DeclaredOnClickListener(this, handlerName));
                }
                break;

通过a(TypedArray,自定义属性的时候肯定都用过这个类)去获取onClick属性的值,如果handlerName不为空,说明在xml布局文件中设置了onClick属性,那么就调用setOnClickListener()方法设置监听,这里传入了一个DeclaredOnClickListener类型的对象。接下来去看看这个DeclaredOnClickListener:

/**
 * 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);
    }
}

很明显,它是实现了OnClickListener接口,重写了onClick方法,在这个方法中就两行代码重要:mMethod = resolveMethod(mHostView.getContext(), mMethodName); mMethod.invoke(mHostView.getContext(), v);

先看第一行代码,看看这个resolveMethod方法的实现:

return context.getClass().getMethod(mMethodName, View.class);

同样只需要看这一行,足以说明所有问题。context.getClass()这个返回一个Class对象,那么这个context引用到底指向什么类型的对象?答:Activity,实际上到这里已经可以通过代码层面去理解之前贴出的官方的那一段解释了。

我们还是接着分析下去吧。那么通过getMethod()方法去拿到一个Method对象,这里可以回顾一下getMethod方法的两个参数:第一个当然是方法名,第二个就是方法形参的Class对象,这里也决定了为什么在Activity编写的点击事件回调方法中要有一个View类型参数。

第二行代码也很简单:mMethod.invoke(),就是调用mMethod对象对应的方法,反射果然最牛*。今天就记录到这里吧~

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值