標籤:
一、前言
本周有位入行開發不久的朋友問我回調究竟是個什麼概念,在網上看了很多的回呼函數解釋,但是越看越亂。雖然回呼函數這個梗已經不新鮮了,這裡還是用書面的形式記錄下。
如果有瞭解的,就無需再看。
二、概念
概念上,這裡引用百度百科的解釋,如下:
回呼函數就是一個通過函數指標調用的函數。如果你把函數的指標(地址)作為參數傳遞給另一個函數,當這個指標被用來調用其所指向的函數時,我們就說這是回呼函數。回呼函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。
百度百科的定義就是上面這樣的,它提到了函數指標這個概念,函數指標一般是C/C++的專有名詞。Java中也是有這個概念的,只是我們把它叫做引用,淡化了函數指標的概念,使用也更加簡單。
這個概念的定義,看上去是有點繞,並且不容易理解。網上有很多的對回呼函數的說明,比如,A類調用B類的方法B1,B類又調用A類的方法A1之類。這樣的說法,其實看的也很迷茫。
因此,會在下文中對回呼函數做一個層次分明的解釋。
三、元素
根據概念,我們知道一個回呼函數的調用流程是需要以下三個元素,並分別給它們一個名字:
1、回呼函數本身——回呼函數;
2、回呼函數作為參數傳入的函數——中間函數;
3、調用者(調用函數)——調用函數。
根據以上的命名,回呼函數的流程就是:
(1)將回呼函數的引用,傳入到中間函數(這個也可以稱之為回呼函數的註冊/訂閱)。
(2)調用函數調用中間函數,觸發回呼函數的事件。
純文字說明可能沒有感覺,這裡我們引入一個很簡單的例子,來描述這一流程:
首先,你需要一個回呼函數:
package com.callback;public class Callback {public void call(){System.out.println("我是一個回呼函數,當我被列印出來的時候,說明回呼函數被觸發了");}}
其次,再來一個中間函數:
package com.callback;public class Middle {//參數是回呼函數所在類的引用public void mid(Callback callback){//觸發回呼函數callback.call();}}
最後,是調用函數:
package com.callback;public class Main {/** * @param args */public static void main(String[] args) { Middle m=new Middle(); //回呼函數註冊,將Callback的引用傳入中間函數 m.mid(new Callback()); }}
運行一下,你就可以看到列印結果:
我是一個回呼函數,當我被列印出來的時候,說明回呼函數被觸發了
以上是一個簡單例子,在該例子中,用最簡方式模仿了回呼函數的調用過程。但日常中我們是不會或者很少這麼用的,因為該例子沒有很好的表現出回呼函數的作用。
回呼函數有什麼作用?在百度百科上有一段對它們意義的說明:
1、因為可以把調用者與被調用者分開,所以調用者不關心誰是被調用者。它只需知道存在一個具有特定原型和限制條件的被調用函數。
2、回調可用於通知機制。
3、這一設計允許了底層代碼調用在高層定義的子程式。
簡單的說,可以用於解耦、通知以及其他。
為了完整說明回呼函數的作用,我們再原來的例子上,做個擴充,引入介面。擴充的還是上面的例子,引入介面:
新增介面:
package com.callback;public interface CallbackInterface {public void call();}
原有的回呼函數,實現上面的介面:
package com.callback;public class Callback implements CallbackInterface{public void call() {// TODO Auto-generated method stubSystem.out.println("我是一個回呼函數,當我被列印出來的時候,說明回呼函數被觸發了");}}
中間函數的參數,做以下修改(修改為介面):
package com.callback;public class Middle {//參數是回呼函數所在類的引用public void mid(CallbackInterface callback){//觸發回呼函數callback.call();}}
最後是調用函數:
package com.callback;public class Main {/** * @param args */public static void main(String[] args) { Middle m=new Middle(); //回呼函數註冊 CallbackInterface ifsImpl=new Callback(); m.mid(ifsImpl);}}對比兩個例子,在後面這個例子中,引入介面,實現了:調用函數、中間函數與回呼函數的解耦。底層函數對高層函數的調用。通知機制。
四、回呼函數作為參數傳入的方法
這是一個小細節,設計人員可以仿照依賴注入的三種方式,來實現回呼函數的參數傳入。
1、建構函式中傳入;
2、用set方式傳入;
3、直接將函數(介面)作為參數傳入;
例子中就是第三種方式。
到此,回呼函數的介紹就基本結束了。但是,上面的兩個例子和我們實際接觸的還是有那麼一些差距。
在看完上面的例子後,對回調有了比較深的認識。現在,已經可以理論聯絡實際了。
也舉一個日常用的很多的情境:Android中的按鈕點擊事件(這也是應用最廣泛的一個回呼函數)。
五、Andriod按鈕點擊事件的類比對比
1、新開一個Android工程,我們做一個真實的按鈕點擊事件,如下:
//原始 Button btn=(Button)findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View arg0) {// TODO Auto-generated method stubSystem.out.println("按鈕被點擊了-原始");}});
2、再根據前面我們自己的分析,做一個按鈕點擊的回呼函數事件:
也首先是一個回呼函數:
package com.example.callbackandroid;public interface MyOnClickListener {//回呼函數public void onClick();}
中間函數:
package com.example.callbackandroid;public class MyButton {private MyOnClickListener listener;//回呼函數註冊/訂閱/登記public void setOnclickListener(MyOnClickListener listener){this.listener=listener;}public void doOnclick(){listener.onClick();}}
調用函數:
//類比 MyButton mButton=new MyButton(); mButton.setOnclickListener(new MyOnClickListener() {@Overridepublic void onClick() {// TODO Auto-generated method stubSystem.out.println("按鈕被點擊了-原始");}}); mButton.doOnclick();
對比原始的android點擊事件以及我們類比的android點擊事件,發現有一點區別:原始的android點擊不需要沒有調用doOnclick事件,沒有調用函數??
實際上,原始的android點擊事件是有調用函數的,只是不需要寫在這裡而已。如果有看過Android的onClick事件(事件分發)源碼的朋友,會發現:在源碼中,當android系統檢測到該View的ACTION_UP的操作時,會調用performClick()這個函數,而這個函數的內容如下:
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false;}它的調用函數,是在這裡實現的。只要mOnClickListener不為null(這個就是通過我們setOnClickListener來賦值的),那麼onClick就會被調用。
因此,它和類比點擊原理其實是一樣的。
到這裡,回呼函數的分析就告一段落了。
下一段落,主要是一個概念的整理。
六、回呼函數與同步、非同步
在真正使用回呼函數的時,我們經常會接觸到非同步回調、非同步呼叫、同步調用這些詞。這裡簡單說下概念,以免混亂,作為結尾。
同步調用:
這個是日常使用最頻繁的一種調用方法,這是一個阻塞調用,如你調用一個方法,等待這個方法返回,或者執行完畢。
非同步呼叫:
這個在android中也使用頻繁,非阻塞調用,如你調用一個方法,無需等這個方法返回,即往下執行其他動作。
非同步回調:
這個是非同步+回調的形式。
Java(Android)回呼函數詳解