Android中的回调机制

相信大家平时开发过程中交流听到的高频词汇,回调肯定少不了,平时开发中也经常会有回调的应用场景,但是都是对着别人写的然后模仿写了用,自己没有好好研究过,今天就来研究一下开发中的高频词汇:回调

回调简介

在知乎看到个排第一的通俗解释,点赞数两千多,引用如下(有些许变动):

场景:你到一个商店买东西,刚好要的东西没有货,于是,你在店员那里留下了电话。过了几天,店里有货了,店员就打了你的电话,然后,你接到电话后就到店里取了货。

解析:你的电话号码叫做回调函数,你把电话留给店员叫做登记回调函数,店里后来有货了叫做触发回调关联的事件,店员给你打电话叫做调用回调函数,你到店里取货叫做响应回调事件

回调是一种双向调用模式,调用方在接口被调用时,也会调用对方的接口,意即实现了抽象类或接口的实例,实现了父类提供的抽象方法后,将该方法交还给父类来处理。更简明的说,就是实现方法交还给提供接口的父类来处理

研究回调

所谓的回调函数就是:
你在A类里面定义了一个方法,这个方法里面又用到接口和接口中的函数,但是这个函数没有具体的实现,而是在B类中实现该方法,但是B类实现在方法后,不在B中调用,而是在B类中注册(就相当于登记回调函数)然后传回A类调用,这就是回调的机制,这个函数就是回调函数。

案例一(点击事件):

安卓中用的最多的函数就是按钮点击的回调事件,虽然天天用,但是都没有好好深入研究,今天就借助点击事件的回调函数来帮助我们更好的理解回调。
1、在A类中定义一个接口:需要我们在类中定义出一个接口,并且给这个接口定义出一个抽象方法,就像下面这样:

    /**
     * 定义一个接口,包涵一个函数,供B类调用
     */
    public interface CallBack {
        public abstract void testCallback();
    }

以下是View.java类中定义的响应点击事件的接口:

   /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

2、在A类中定义出该接口的一个成员变量:

 /**
     * 定义回调接口的成员变量
     */
    private CallBack mCallback;

以下是View.java类中获取点击事件接口成员变量的源码:

 /**
         * Listener used to dispatch click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        public OnClickListener mOnClickListener;

3、在A类中定义出一个公共方法,可以用来设置这个接口的对象,调用该方法可以给接口对象变量赋值:

 /**
     * 用来注册的函数
     * @param mCallBack 接口对象
     */
    public void setCallBack(CallBack mCallBack) {
        this.mCallback = mCallBack;
    }

这里看英文注释也看得出来是什么意思,是不是想到了我们平常使用setOnClickListener(OnClickListener l)的时候呢:

 /**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

4、在A类中调用接口对象中的方法:

  /**
     * 调用回调接口对象中的方法
     */
    public void doWork() {
        mCallback.testCallback();
    }

在View.java中的体现:

 /**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);//就是这里
            result = true;
        } else {
            result = false;
        }

这里附上整个项目的代码,这里A类映射到实际中使用Callback
这个类来代表:

/**
 * A类
 */
public class Callback {
    /**
     * 定义回调接口的成员变量
     */
    private CallBack mCallback;

    /**
     * 定义一个接口,包涵一个函数,供B类调用
     */
    public interface CallBack {
        public abstract void testCallback();
    }

    /**
     * 用来注册的函数,调用此方法就表示注册到你这来
     * @param mCallBack 接口对象
     */
    public void setCallBack(CallBack mCallBack) {
        this.mCallback = mCallBack;
    }

    /**
     * 调用回调接口对象中的方法
     */
    public void doWork() {
        mCallback.testCallback();
    }
}  

我们在定义出一个B类,就用TestCallback
类吧:

/**
 * B类
 */
public class TestCallback {
    private Callback callback;
    private void testButton(){
        /**
         * 为Callback类设置回调函数, 在这里定义具体的回调方法
         */
        callback.setCallBack(new Callback.CallBack() {
            @Override
            public void testCallback() {

            }
        });
    }  

如果第一眼看不明白,我们附上我们最常用的Button点击事件的处理的代码,这里Callback 类类比一下就是View类:

public class Test{
    private Button button;
    button.setOnClickListener(new OnClickListener() {
        
        @Override
        public void onClick(View v) {
           //做一些操作
            doWork();
        }
    });
    
}

总结如下:
为了实现一个回调方法,首先,要先定义一个包含了接口的类,并且该接口中要有一个抽象方法,这个抽象方法的具体实现由其他类来完成(比如响应 Button 的点击事件,onClick() 方法里做当点击事件产生,并且该方法被调用时需要做的操作,如显示文本信息等)。而后,该方法的回调是之前包含有抽象方法的那个接口所在类去调用的(比如 onClick() 方法是当点击事件产生之后,经过一系列的事件分发,在 View 类中被调用)。

案例二:模拟Activity生命周期

下面模拟一下Activity生命周期,基本都是回调函数在作用:
1. Activity接口

  //定义接口  
    public interface Activity{  
        //创建时调用的方法  
        public void onCreate();  
        //启动时调用的方法  
        public void onStart();  
        //销毁时调用的方法  
        public void onDestory();  
    } 

2. Activity接口的实现类MyActivity

   //定义一个类实现Activity接口  
    public void MyActivity implements Activity{  
        //实现创建方法,简单输出提示信息  
        @Override  
        public void onCreate(){  
            System.out.println("onCreate....");  
        }  

        //实现启动方法,简单输出提示信息  
        @Override  
        public void onStart(){  
            System.out.println("onStart....");  
        }  

        //实现销毁方法,简单输出提示信息  
        @Override  
        public void onDestory(){  
            System.out.println("onDestory....");  
        }  
    } 

3. 系统运行环境类AndroidSystem

    //系统运行环境类  
    public class AndroidSystem{  
        //定义创建常量  
        public static final int CREATE=1;  
        //定义启动常量  
        public static final int START=2;  
        //定义销毁常量  
        public static final int DESTORY=3;  

        //运行方法  
        public void run(Activity a,int state){  
            switch(state){  
                //创建  
                case CREATE:  
                    a.onCreate();  
                    break;  
                //启动  
                case START:  
                    a.onStart();  
                    break;  
                //销毁  
                case DESTORY:  
                    a.onDestory();  
                    break;  
            }  
        }  
    }

测试类:

//测试类  
    public class Test{  
        //主方法  
        public static void main(String[] args){  
            //实例化AndroidSystem  
            AndroidSystem system = new AndroidSystem();  

            //实例化MyActivity  
            Activity a = new MyActivity();  

            //创建  
            system.run(a,AndroidSystem.CREATE);  
            //启动  
            system.run(a,AndroidSystem.START);  
            //销毁  
            system.run(a,AndroidSystem.DESTORY);  
        }  
    }  

通过上述代码我们可以看出,接口(系统框架)是系统提供的,接口的实现是用户实现的。这样可以达到接口统一,实现不同。系统通过在不同的状态“回调”我们的实现类,来达到接口和实现的分离。
除了以上几个案例,在activity里每点击一次adpter中item获取item的ID诸如此场景也可以用回调

回调的作用

一般来说,类的成员变量都是数据对象,主要是用来传递数据的。回调是将一段程序作为成员变量,在特定的场合使用该段程序,此为回调的核心。

而在程序代码中,则可以抽象成以下这张图的形式(摘自此博客,感谢前辈):
Android中的回调机制_第1张图片
C不会自己调用b,C提供b的目的就是让S来调用它,而且C不得不提供。S并不知道C提供的b是什么,因此S会约定b的接口规范(函数原型),然后由C提前通过S的一个函数r告诉S自己将要使用b函数(即注册),比如注册监听器就是其中一种典型。其中r为注册函数。
对上图的一个完善是这样的:
Android中的回调机制_第2张图片

Java 是一门面向对象语言,“万事万物皆为对象”,将普通事物的共性抽取出来,而这些共性中又充斥着特性,每个不同的特性就需要交给特定的情况去处理,暴露接口可以减少很多重复,使得代码更加优雅。比如,View 具有被点击的通性,但是每个点击事件会产生不一样的事件处理,因此,Android 对外暴露一个接口中有个 onClick() 方法,需要处理什么写什么,View 不管如何实现,其只负责调用该回调方法。
至此,关于 Android 中的回调机制研究已经综述完毕。

2019.11.03补充:看到一篇讲的很好的例子
使用Volley来访问百度,并且把放回的网页代码显示出来。
上代码:

  mButton = findViewById(R.id.btn_kotlin);
        mTextView = findViewById(R.id.tv_content);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            // 第一步,利用Volleynew出来一个请求队列
                RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
                         // 第二步,创建一个请求
                // 在这里,我们需要关心的就是请求到底是成功还是失败。就这两点。
                // 可以利用接口封装起来。
                StringRequest stringRequest = new StringRequest("http://www.baidu.com",
                        new Response.Listener<String>() {
                            @Override
                            public void onResponse(String response) {
                                Toast.makeText(MainActivity.this, "请求成功", Toast.LENGTH_SHORT).show();
                                mTextView.setText(response);
                            }
                        }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();

                    }
                });
                   // 第三步,把请求放进队列
                requestQueue.add(stringRequest);
            }
        });

根据上面的代码,我们使用了Volley做了一个请求网络的简单示例。在上面的代码中,我们看得出来,我们需要关心的只是请求的成功或者失败的结果,就这两点,那么,这个东西完全可以封装起来。写一个接口,进行回调。

我们来写一个工具类,叫HttpUtils,然后提供一个方法request,这个request方法除了传入必要的url和context之外,还有一个参数是我们自己定义的接口。

这样外界一旦调用了这个方法就必须实现接口,实现接口之后外界想做什么操作外界自己去决定,最后,在request方法里面真正去执行外界交待给我们的任务

HttpUtils代码:

public class HttpUtils {
     // 外界除了传入必要的url和上下文之外,最关键的就是要实现我们的接口,这样外界就能方便地交待工作给我们
    // 我们不能把东西写死,需要让调用者有一定的自由,他具体想干嘛让他自己去具体发挥,然后真正工作的还是我们自己
    public static void request(Context context, String url, final RequestListener requestListener) {
        RequestQueue requestQueue = Volley.newRequestQueue(context);
        StringRequest stringRequest = new StringRequest(url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
              // 外界实现接口交待的关于请求的成功的代码在此处被真正执行
                requestListener.responseSucceed(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
            // 外界实现接口交待的关于请求的失败的代码在此处被真正执行
                requestListener.responseOnError(error);
            }
        });
        requestQueue.add(stringRequest);

    }


    interface RequestListener {
        void responseSucceed(String response);

        void responseOnError(VolleyError error);
    }

}

在activity中调用:

   String url = "https://www.baidu.com";
          // request方法需要传入3个参数,这里关键是实现接口,然后用户就可以只关心成功和失败的部分
                // 最后把工作扔给request自己去做
                HttpUtils.request(MainActivity.this, url, new HttpUtils.RequestListener() {
                    @Override
                    public void responseSucceed(String response) {
                        Toast.makeText(MainActivity.this, "请求成功", Toast.LENGTH_SHORT).show();
                        mTextView.setText(response);
                    }

                    @Override
                    public void responseOnError(VolleyError error) {
                        Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();

                    }
                });

主要参考:
Android回调函数机制那点事
Android 回调接口是啥,回调机制详解
Android接口回调,最简单的理解方式
说说安卓回调——CallBack

你可能感兴趣的:(Android学习)