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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章