回調函數原理及應用

前言:
回調函數在開發中是很實用的一塊知識點。
本文從原理及應用兩個角度深入理解回調函數。
希望在交流中得到進步,也本著分享精神把知識傳播出去,希望后來人少走我走過的彎路。
所以開始寫博客,路漫漫其修遠兮,吾將上下而求索。

回調函數的原理描述

要理解回調函數,首先要明確什么時候使用回調函數?通俗的講,一般給某個類的對象在某個觸發時機,添加一個可觸發的事件函數,并使此事件函數能調用一個函數。這個被調用的函數就是回調函數。這個機制就是回調機制。
如在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出
來。
仿H5樣式兩級聯動庫-回調方法的應用

補充:
今天看了一點架構知識。發現回調函數是實現主動型API架構的一種方式。至于主動型API架構和被動型API架構,后續學習完設計模式和Android框架層會慢慢整理到博客。此處先稍做記錄。
來看主動型API架構的三個特點:
1)定義:自己給出定義接口和基類
2)實現:使用者實現接口或基類
3)呼叫:呼叫我的理解其實就是控制權,控制權一定要在自己手里
做到以上三點即是主動型API架構。
來看Google的一個子吧:

回調機制實現Google主動型API架構示例.png

簡短的做下補充,等把設計模式和架構融會貫通,再整理到博客。希望大家多多指點。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,659評論 25 708
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,719評論 18 399
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,195評論 30 471
  • 慈禧太后作為清末的統治者,可謂大權在握。她是一個很有手腕的政客,但對于風起云涌的十九世紀,就顯得較為單薄。不過,關...
    有趣的歷史段子閱讀 416評論 0 1
  • 也許幸運總是再不經意間出現在你面前,努力想要達到,卻發現生活怎么也不給機會,當你從其他渠道靠近它的時候,它可能就眷...
    Jacob_kk閱讀 182評論 0 0