前言:
回調函數在開發中是很實用的一塊知識點。
本文從原理及應用兩個角度深入理解回調函數。
希望在交流中得到進步,也本著分享精神把知識傳播出去,希望后來人少走我走過的彎路。
所以開始寫博客,路漫漫其修遠兮,吾將上下而求索。
回調函數的原理描述
要理解回調函數,首先要明確什么時候使用回調函數?通俗的講,一般給某個類的對象在某個觸發時機,添加一個可觸發的事件函數,并使此事件函數能調用一個函數。這個被調用的函數就是回調函數。這個機制就是回調機制。
如在Android中Button摁扭的點擊事件,是給Button對象在觸發時機為點擊時,執行觸發的事件函數,并在事件函數內調用設置的一個點擊回調函數。
(一)實現回調函數的原理其實很簡單
1.定義一個回調接口,并定義一個回調方法
2.定義出要設置回調機制的類,并使此類持有回調接口的指針
3.在要設置回調機制的類中,初始化回調接口指針,并使用指針調用回調函數
4.在要設置回調機制的類中設置觸發時機及執行的觸發事件函數
(二)按照上面思路實現回調機制方式有三種
1.在構造器中初始化回調接口指針
2.在自定義方法中實現初始化回調接口指針
3.將要設置回調機制的類,設置為抽象類并實現回調接口
回調機制的代碼實現
下面分別使用上面提到的實現回調機制的三種方式,使用代碼實現。
方式一(在構造器中初始化回調接口指針)
/**
*
* @author 趙默陽
* @date 2017年5月10日 上午10:18:20
*
* 第一步定義一個回調接口及回調方法
*
*/
public interface CallBackInterface {
void callback();
}
/**
* @author 趙默陽
* @date 2017年5月10日 上午10:18:25
* 定義要設置回調機制的類
*
* 1)設置此類持有 回調接口指針對象
* 2)設置構造器初始化回調接口對象
* 3)設置觸發事件函數,調用回調函數;設置觸發機制及觸發事件函數
*/
public class CallObject {
//第二步持有回調接口指針對象
private CallBackInterface callBackInterface=null;
//第三步在構造方法里初始化回調接口
public CallObject(CallBackInterface callBackInterface){
this.callBackInterface=callBackInterface;
}
/*
* 第四步 設置觸發事件函數,并在內部使用回調函數接口指針調用回調函數
*/
public void goCallMethord(){
callBackInterface.callback();
}
/*
* 第四步 設置執行觸發事件函數的觸發時機
* 舉例:這里是 CallObject執行的一個方法,執行完畢即觸發事件函數
*/
public void doSomething(){
for(int i=0;i<10;i++){
System.out.println("CallObject操作方法 ···");
}
goCallMethord(); //執行觸發事件函數
}
}
通過上面的代碼,我們已經給類設置了回調機制。其執行時機是當執行完doSomething方法內的for循環操作后,執行觸發事件函數,在觸發事件函數內執行回調函數。下面,來看下運行結果吧。
/**
*
* @author 趙默陽
* @date 2017年5月10日 上午10:18:28
* 查看測試結果
*/
public class Test{
/**
* @param args
*/
public static void main(String[] args) {
//方式一實現回調機制,需要在new對象的時候傳入回調接口的實現對象
CallObject callObject=new CallObject(new CallBackInterface() {
@Override
public void callback() {
// TODO Auto-generated method stub
System.out.println("do something···");
}
});
callObject.doSomething(); //執行觸發時機方法
}
}
查看console中結果,回調方法在執行doSomething方法內操作完成后,觸發了回調機制。
方式二(在自定義方法中實現初始化回調接口指針)
在andoid中,這是非常常見的回調機制實現方式。如使用serOnClickListener()等方法實現初始化回調接口指針。
/**
*
* @author 趙默陽
* @date 2017年5月10日 上午10:18:20
*
* 第一步定義一個回調接口及回調方法
*
*/
public interface CallBackInterface {
void callback();
}
/**
* @author 趙默陽
* @date 2017年5月10日 上午10:18:25
*
* 需求: 給一個類的對象設置觸發事件給出回調方法
* 定義要設置回調機制的類
*
* 1) 設置此類持有 回調接口指針對象
* 2) 設置自定義初始化回調接口指針對象的方法,
* 一般以回調時機命名,如setDoSomethingDownListener 即當
* doSomething方法操作執行完的監聽
* 3) 設置觸發事件函數,調用回調函數;設置觸發機制及觸發事件函數
*/
public class CallObject {
//第二步持有的回調接口指針對象
private CallBackInterface callBackInterface=null;
//第三步聲明 空構造
public CallObject(){}
/*
* 第三步設置自定義初始化回調接口指針對象的方法,并把回調接口對象當參數
* 比如執行完某段代碼,調用這個方法
*/
public void setDoSomethingDownListener (CallBackInterface callBackInterface1){
this.callBackInterface=callBackInterface1;
};
/*
* 第四步 設置觸發事件函數,并在內部使用回調函數接口指針調用回調函數
*/
public void goCallMethord(){
callBackInterface.callback();
}
/*
* 第四步 設置執行觸發事件函數的觸發時機
* 舉例:這里是 CallObject執行的一個方法,執行完畢即觸發事件函數
*/
public void doSomething(){
for(int i=0;i<10;i++){
System.out.println("CallObject操作方法 ···");
}
goCallMethord(); //執行觸發事件函數
}
}
同樣,我們來看看方式二的使用結果吧。
先看使用匿名內部類,做Android開發的同學是不是特別熟悉呢?
/**
*
* @author 趙默陽
*
* @date 2017年5月10日 上午10:18:28
*
*/
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
CallObject callObject2=new CallObject();
//使用匿名內部類
callObject2.setDoSomethingDownListener(new CallBackInterface() {
@Override
public void callback() {
// TODO Auto-generated method stub
System.out.println("我是設置的匿名回調···");
}
});
//執行觸發時機的函數,其內部執行完for循環 操作會調用觸發事件函數
callObject2.doSomething();
}
}
再看看使用實現接口效果,做Android開發的同學是不是還是特別熟悉呢?
/**
*
* @author 趙默陽
*
* @date 2017年5月10日 上午10:18:28
*
*/
public class Test implements CallBackInterface{
/**
* @param args
*/
public static void main(String[] args) {
Test test=new Test();
test.test();
}
/* (non-Javadoc)
* @see com.zmy.callback.CallBackInterface#callback()
*/
@Override
public void callback() {
// TODO Auto-generated method stub
System.out.println("我是設置的set監聽的回調···");
}
public void test(){
//設置監聽
CallObject callObject1=new CallObject();
callObject1.setDoSomethingDownListener(this);
//執行觸發時機的函數,其內部執行完for循環 操作會調用觸發事件函數
callObject1.doSomething();
}
}
方式三(將要設置回調機制的類,設置為抽象類并實現回調接口)
/**
*
* @author 趙默陽
*
* @date 2017年5月10日 上午10:18:20
*
* 定義一個回調接口及回調方法
*
*/
public interface CallBackInterface {
void callback();
}
/**
* @author 趙默陽
* @date 2017年5月11日 上午1:07:48
* 定義一個實現回調機制的類
*
* 步驟:
* 1) 將此類定義為一個抽象方法,并實現回調接口
* 2) 定義觸發時機,及觸發時機時執行的函數
*/
public abstract class CallObject implements CallBackInterface{
/*
* 設置執行觸發事件函數的觸發時機
* 舉例:這里是 CallObject執行的一個方法,執行完畢即觸發事件函數
*/
public void doSomething(){
for(int i=0;i<10;i++){
System.out.println("CallObject操作方法 ···");
}
goCallMethord(); //執行觸發事件函數
}
//觸發時機時執行的觸發事件函數
public void goCallMethord(){
callback(); //調用回調函數,將實現交給實現類
}
}
方式三實現比較簡單,但是效果是一樣的。我們來看下執行結果吧。
/**
* @author 趙默陽
*
* @date 2017年5月11日 上午1:14:40
*
*/
public class Test {
public static void main(String[] args) {
//聲明并初始化 定義回調方法的類 的時候會實現回調方法
// TODO Auto-generated method stub
CallObject callObject=new CallObject() {
@Override
public void callback() {
// TODO Auto-generated method stub
System.out.println("I am callback,you can do something ···");
}
};
//觸發時機即執行完doSomething方法內的操作后執行觸發事件函數
callObject.doSomething();
}
}
回調機制的應用
回調函數的應用非常常見和方便。通過上面我們了解了回調機制的原理
和基本實現方式。下面我們把回調機制使用在我們的開發工作中吧。
一)回調機制在系統代碼里的應用
最近在看Dialog的源碼,下面我們看下Window類的回調機制,怎么在Doalog類下使用吧。
/**
* API from a Window back to its caller. This allows the client to
* intercept key dispatching, panels and menus, etc.
* 首先,在Window有個內部接口,里面定義了很多回調方法,我們只取一個舉例
*/
public interface Callback {
/**
* This is called whenever the current window attributes change.
* window屬性改變回調方法
*/
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
}
/*
* Window類,只截取有用代碼
* 1. 持有回調接口指針
* 2. 使用方法初始化回調接口指針
* 3. 觸發時機時 執行的觸發事件函數
*/
public abstract class Window {
private Callback mCallback; //持有回調接口指針
/**
* Set the Callback interface for this window, used to intercept key
* events and other dynamic operations in the window.
* 初始化回調函數指針的方法
* @param callback The desired Callback interface.
*/
public void setCallback(Callback callback) {
mCallback = callback;
}
/**
* {@hide}
* 觸發時機時 執行的觸發事件函數
*/
protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
if (mCallback != null) {
mCallback.onWindowAttributesChanged(attrs);
}
}
/**
* Specify custom window attributes. <strong>PLEASE NOTE:</strong> the
* layout params you give here should generally be from values previously
* retrieved with {@link #getAttributes()}; you probably do not want to
* blindly create and apply your own, since this will blow away any values
* set by the framework that you are not interested in.
*
* @param a The new window attributes, which will completely override any
* current values.
*/
public void setAttributes(WindowManager.LayoutParams a) {
mWindowAttributes.copyFrom(a);
/*
* 這是其中一個觸發時機 執行的函數
* 在這里執行了 觸發事件函數
*/
dispatchWindowAttributesChanged(mWindowAttributes);
}
}
/**
* 下面來看在Dialog中的使用吧
*/
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == 0) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//來看重點,這里new出來window實現類對象
final Window w = new PhoneWindow(mContext);
mWindow = w;
/*
*這一句初始化了window持有的回調函數接口指針
*傳入了 this,說明dialog實現了回調函數接口
*/
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
/*
* 回調實現方法
*/
@Override
public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
if (mDecor != null) {
mWindowManager.updateViewLayout(mDecor, params);
}
}
}
當然以上代碼只是實現了回調機制,但是并沒有觸發,如果要觸發這個回調機制,需要window類對象調用 觸發時機的函數即setAttributes函數。這時,就會觸發回調機制,在Dialog類中執行回調方法。這時也實現了,window類中的改變及時通知Dialog。
二)回調機制在自己代碼里的應用
對于回調機制在自己代碼的應用,就給大家展示一張我封裝的一個仿H5兩級聯動庫的效果圖吧。其使用方式就是
用了上面代碼實現的三種方式之一。大家先看圖吧,也算是個預告,下一篇博客我會介紹這個庫的使
用方法及實現原理,本著分享精神,我會把這個仿H5兩級聯動庫共享出來。
這里主要是自定義了一個PopupWindow,實現了仿H5樣式的兩級聯動。當選擇好數據后,點擊確定會
執行一個回調方法onSure函數,其參數即選擇的數據,以便于處理選擇好的數據,目前只是Toast出
來。
補充:
今天看了一點架構知識。發現回調函數是實現主動型API架構的一種方式。至于主動型API架構和被動型API架構,后續學習完設計模式和Android框架層會慢慢整理到博客。此處先稍做記錄。
來看主動型API架構的三個特點:
1)定義:自己給出定義接口和基類
2)實現:使用者實現接口或基類
3)呼叫:呼叫我的理解其實就是控制權,控制權一定要在自己手里
做到以上三點即是主動型API架構。
來看Google的一個子吧:
簡短的做下補充,等把設計模式和架構融會貫通,再整理到博客。希望大家多多指點。