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

 我们都知道给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中就不行。好了,很简单就这么多......


发布了62 篇原创文章 · 获赞 28 · 访问量 12万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章