本文通過介紹Android事件監聽機制來了解下Java的回調機制,即當你點擊一個button的時候發生了什么,點擊之后是如何調到自己寫的onClick函數的。
寫過Android小程序的同學肯定都寫過如下的代碼:
button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
...
}
});
我們都知道這是在給一個Button注冊監聽事件,當點擊這個button的時候會調到里面寫的onClick函數。但是,這是怎么發生的呢?onClick函數是怎么被調用的?
其實背后運行的是JAVA的回調函數機制。也就是說onClick函數是一個回調函數,當你點擊button的時候,系統監聽到你的點擊然后回調了這個函數。
我們先簡單介紹一下JAVA的回調機制。
JAVA回調機制
上圖形象的展示了java回調的原理思想:
- 類A的a()方法調用類B的b()方法
-
類B的b()方法執行完畢主動調用類A的callback()方法
onClick函數其實就跟這里的callback() 一樣,就是回調函數。回調機制文末有鏈接可詳細了解,下面介紹一下是如何實現onClick函數的回調的。
onClick函數的調用實現
onClick函數是實現OnClickListener接口,然后通過new出一個OnclickListener的對象作為參數傳給setOnClickListener函數。
我們去 setOnClickListener 函數看一看
于是跳到了 View.java,這個方法是寫在 View中的:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
setOnClickListener 方法就如同我們調用時的那樣,傳入一個 OnClickListener 對象作為參數,其實它就是個接口,我們來看一看 OnClickListener 長什么樣子。
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
我們應重點關注setOnClickListener 方法中的這一行
getListenerInfo().mOnClickListener = l;
它把我們傳進來的OnClickListener 對象賦給了另一個值。我們在OnclickListener對象中實現了onClick函數,所以mOnClickListener便擁有了我們onClick函數的實現。我們來看看mOnClickListener是什么。這得先看看getListenerInfo()方法
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
getListenerInfo()返回一個ListenerInfo,如果mListenerInfo已經存在,就返回,如果不存在,就new一個這個對象返回,這是單例模式(感興趣自行搜索,不做贅述),目的是保證只有一個ListenerInfo對象。
然后我們來看看ListenerInfo是什么
static class ListenerInfo {
protected OnFocusChangeListener mOnFocusChangeListener;
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
public OnClickListener mOnClickListener;
protected OnLongClickListener mOnLongClickListener;
protected OnContextClickListener mOnContextClickListener;
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
private OnHoverListener mOnHoverListener;
private OnGenericMotionListener mOnGenericMotionListener;
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}
原來是一個內部靜態類,成員包括各種事件的監聽接口,其中包括
public OnClickListener mOnClickListener;
誒喲,和我們傳入的一樣的一個OnClickListener接口引用,于是繞了這么一大圈(我們先不管為啥繞),我們傳入的持有我們實現的onClick(View view)方法的OnClickListener接口對象(還記得嗎?),被賦值到了View中的mListenerInfo中的mOnClickListener對象,也就是,我們實現的onCLick(View view) 方法,被mListenerInfo.mOnClickListener持有了。
這下我們知道了我們所實現的onClick函數存到了哪里,但是他是什么時候被調用的呢?
其實這是一個異步處理機制,想象一下,系統一直在監聽著屏幕的點擊事件,在我們觸摸到屏幕的時候進行響應,這是一個線程操作,因為如果這個放在主線程,那在事件被響應之前,我們的線程都是阻塞的,因為屏幕的資源被占用了,無法進行其他操作,而在子線程中,系統監聽著屏幕的活動,然后在我們觸摸(click)時,其實是調用performClick()方法實現了點擊,并且調用了onClick(View view)方法實現了點擊事件的回調,我們就可以恰恰剛好在點擊時間觸發的時候,進行我們想要的操作,也就是我們實現的onClick方法。
我們來看下performClick()函數。
View.java
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this); // 啦啦啦,回調!!!
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
這個函數中通過調用li.mOnClickListener.onClick(this);
實現回調我們的onClick函數。
總結
以前寫事件監聽,書上告訴怎么寫自己就怎么寫,并不知道是如何實現的,其實IDE也會自動補全這部分代碼,但是讀完這篇文章大家應該會在自動補全的時候想一想這個原理,知其然也知其所以然,了解背后的原理可能并不會加快你寫代碼的速度,但是遇到問題時應該還是會有點用的。
最后, 同學點個贊吧!!! 加個關注好么