程序员@IT·互联网Android知识

回调函数原理及应用

2017-05-11  本文已影响1356人  赵默阳

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

回调函数的原理描述

要理解回调函数,首先要明确什么时候使用回调函数?通俗的讲,一般给某个类的对象在某个触发时机,添加一个可触发的事件函数,并使此事件函数能调用一个函数。这个被调用的函数就是回调函数。这个机制就是回调机制。
如在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

简短的做下补充,等把设计模式和架构融会贯通,再整理到博客。希望大家多多指点。

上一篇下一篇

猜你喜欢

热点阅读