回调函数原理及应用

前言:
回调函数在开发中是很实用的一块知识点。
本文从原理及应用两个角度深入理解回调函数。
希望在交流中得到进步,也本着分享精神把知识传播出去,希望后来人少走我走过的弯路。
所以开始写博客,路漫漫其修远兮,吾将上下而求索。

回调函数的原理描述

要理解回调函数,首先要明确什么时候使用回调函数?通俗的讲,一般给某个类的对象在某个触发时机,添加一个可触发的事件函数,并使此事件函数能调用一个函数。这个被调用的函数就是回调函数。这个机制就是回调机制。
如在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阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,332评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,547评论 18 399
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,117评论 29 470
  • 慈禧太后作为清末的统治者,可谓大权在握。她是一个很有手腕的政客,但对于风起云涌的十九世纪,就显得较为单薄。不过,关...
    有趣的历史段子阅读 401评论 0 1
  • 也许幸运总是再不经意间出现在你面前,努力想要达到,却发现生活怎么也不给机会,当你从其他渠道靠近它的时候,它可能就眷...
    Jacob_kk阅读 171评论 0 0