前言
之前對源碼的閱讀,總是用時一通亂七八糟的跳轉,以學會使用為目的;過了一段時間,就忘記了,因此打算將一些源碼的閱讀經歷記錄下來,也通過敲一遍的過程,加深理解。
最開始,用一個比較簡單的例子來小試牛刀吧
問題描述
對于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ù)mResolvedMethod和mResolvedContext。
-
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). -->
最后
- 對于android:onClick屬性,是不建議這樣用的,官方也進行了@deprecated注解,這樣的代碼不利于維護。
- 通過追蹤它的實現(xiàn)方式,慢慢開始源碼閱讀之旅。
- 其實,很多注解框架,都是通過反射實現(xiàn)的,比如XUtils等。