android源码解析(1)--如何处理布局文件中添加的点击事件

来源:互联网 发布:朴槿惠犯了什么罪 知乎 编辑:程序博客网 时间:2024/05/01 10:12

 我们都知道给view设置点击事件有三种方式:

         第一种:View.setOnClickLintener(new OnClicklistener(...));

         第二种:View.setOnClickListener(this); 然后让class去实现点击事件

         第三种:xml文件中写onClick="XXXX";然后在activity中写方法XXX(View view)

 其实前两种方法本质上没有什么区别,第二种就是有时候可以避免内存泄漏(不是一定可以避免,只有在activity中可以),今天我们来分析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);        //此处省略n行无关代码        final int N = a.getIndexCount();        for (int i = 0; i < N; i++) {            int attr = a.getIndex(i);            switch (attr) {                //此处省略N行无关代码                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;                //此处省略N行无关代码            }            //此处省略N行无关代码        }        //此处省略N行无关代码    }
我们会发现,在构造器中会去读取onClick属性是否存在,如果存在则会读取到String类型的name,即handlerName,然后立即给我们设置了点击事件,原来系统帮我们设置了点击事件,那么又是如何传递到activity中的呢?为什么我们activity中要写handlerName名字的方法呢?我们就需要进入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 mResolvedMethod;        private Context mResolvedContext;        public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {            mHostView = hostView;            mMethodName = methodName;        }        @Override        public void onClick(@NonNull View v) {            if (mResolvedMethod == null) {                resolveMethod(mHostView.getContext(), mMethodName);            }            try {                mResolvedMethod.invoke(mResolvedContext, 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 void resolveMethod(@Nullable Context context, @NonNull String name) {            while (context != null) {                try {                    if (!context.isRestricted()) {                        final Method method = context.getClass().getMethod(mMethodName, View.class);                        if (method != null) {                            mResolvedMethod = method;                            mResolvedContext = context;                            return;                        }                    }                } 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里面的一个静态内部类,实现了onClickListener接口,很简单,构造器传过来了点击的View和hanlderName,然后我们看onClick(View v)方法,会进入resolveMethod这个方法,进去看看吧。这个方法好像就是通过反射获取到了方法,然后通过context=null跳出while循环,如果找了半天发现没有找到这个方法,那就直接挂了!然后又回到onClick方法中执行了此方法,完了,就这么简单。。。但是,问题来了,那么是如何知道在activity中写的方法呢?假如我把此方法写到fragment中可不可以呢?答案是不可以的,这里我们会看到一个关键的context上下文,其实这个上下文就是activity,这就是为什么我们在activity中写此方法会被调用了。我们可以做个试验,假如说我在fragment的xml布局中添加点击事件,我把方法写到activity中,是否会执行呢?肯定会的,但是写到fragment中就不行。好了,很简单就这么多......


0 0