越过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对象对应的方法,反射果然最牛*。今天就记录到这里吧~

智能推荐

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告