Android源碼—android:onClick

前言

之前對源碼的閱讀,總是用時一通亂七八糟的跳轉,以學會使用為目的;過了一段時間,就忘記了,因此打算將一些源碼的閱讀經歷記錄下來,也通過敲一遍的過程,加深理解。

最開始,用一個比較簡單的例子來小試牛刀吧

問題描述

對于View(Button、TextView等)的點擊事件,常用的寫法是通過findViewById獲取View的實例,然后通過setOnClickListener設置監(jiān)聽事件,比如我們有如下Button控件。

<Button
        android:id="@+id/btn_submit"
        android:text="Submit"/>

設置點擊事件(假設在Activity中)

findViewById(R.id.btn_submit).setOnClickListener(v -> {
            // 提交
            doSubmit(v);
        }
  )
  
/**
* 提交處理
*/
public void doSubmit(View v) {
    ……
}

但是還有一種寫法是在xml布局中通過android:onClick屬性直接指定點擊執(zhí)行的函數(shù)。

<Button
        android:id="@+id/btn_submit"
        android:text="Submit"
        android:onClick="doSubmit"/>

【思考】

  • xml中設置onClick屬性,如何映射到doSubmit方法?

源碼分析過程

首先我們知道諸如android:xxx之類的屬性是會在某個attrs文件中定義的,此處的android:onClick是View的屬性,定義在如下文件中。

Android>sdk>platforms>android-xx>data>res>values>attrs.xml

<declare-styleable name="View">
    ……
        <attr name="onClick" format="string" />  <!--是一個string類型的值。-->
    ……
</declare-styleable name="View">

在View的構造函數(shù)中,會解析出此屬性的值。

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    this(context);
  // 1 獲取屬性
    // attrs就是xml中的屬性,com.android.internal.R.styleable.View就是上述 <declare-styleable name="View">
    inal TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
  
  // 2 處理屬性值
   for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.View_onClick:
                    ……
                    final String handlerName = a.getString(attr);
                    if (handlerName != null) {
                        setOnClickListener(new DeclaredOnClickListener(this, handlerName));
                    }
                        break;
            }
  
}

看這里,如果變量handlerName不為空,就會為此View設置點擊事件了,這個handlerName就是onClick屬性的值doSubmit,但這個點擊事件,并不是我們所熟悉的OnClickListener。

進一步看看這個DeclaredOnClickListener

private static class DeclaredOnClickListener implements OnClickListener {
        private final View mHostView;
        private final String mMethodName;

        private Method mResolvedMethod; // 【Method屬性】
        private Context mResolvedContext; // 【Context】

        public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {
            mHostView = hostView;
            mMethodName = methodName;
        }

        @Override
        public void onClick(@NonNull View v) {
            if (mResolvedMethod == null) { // 初始化mResolvedMethod 和 mResolvedContext變量
                resolveMethod(mHostView.getContext(), mMethodName);
            }
            try {
                // 【重點】通過反射
                mResolvedMethod.invoke(mResolvedContext, v);
            } catch (IllegalAccessException e) {
                ……
            }
        }

        @NonNull
        private void resolveMethod(@Nullable Context context, @NonNull String name) {
            while (context != null) {
                try {
                    if (!context.isRestricted()) {
                      // 【重點】通過Class獲取方法。
                        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;
                }
            }
          ……
        }
    }

DeclaredOnClickListener實現(xiàn)了OnClickListener,其中重點是參數(shù)mResolvedMethodmResolvedContext

  • mResolvedMethod是Method類型,通過context.getClass().getMethod(mMethodName, View.class)獲取,這個mMethodName就是上面獲取的handlerName(doSubmit)。
  • mResolvedContext這里就可以理解為doSubmit所在的類,這里是Activity。

結論

在onClick事件中最終通過反射mResolvedMethod.invoke(mResolvedContext, v);執(zhí)行了doSubmit方法。

思考

doSubmit的訪問權限是否可以設置為private呢?

答案:不可以,因為源碼中沒有調用mMethod.setAccessible(true);注入所有修飾符。

其實在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
             <code>android:onClick="sayHello"</code>, you must declare a
             <code>public void sayHello(View v)</code> method of your context
             (typically, your Activity). -->

最后

  1. 對于android:onClick屬性,是不建議這樣用的,官方也進行了@deprecated注解,這樣的代碼不利于維護。
  2. 通過追蹤它的實現(xiàn)方式,慢慢開始源碼閱讀之旅。
  3. 其實,很多注解框架,都是通過反射實現(xiàn)的,比如XUtils等。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容