相信大家平时开发过程中交流听到的高频词汇,回调肯定少不了,平时开发中也经常会有回调的应用场景,但是都是对着别人写的然后模仿写了用,自己没有好好研究过,今天就来研究一下开发中的高频词汇:回调
在知乎看到个排第一的通俗解释,点赞数两千多,引用如下(有些许变动):
场景:你到一个商店买东西,刚好要的东西没有货,于是,你在店员那里留下了电话。过了几天,店里有货了,店员就打了你的电话,然后,你接到电话后就到店里取了货。
解析:你的电话号码叫做回调函数,你把电话留给店员叫做登记回调函数,店里后来有货了叫做触发回调关联的事件,店员给你打电话叫做调用回调函数,你到店里取货叫做响应回调事件。
回调是一种双向调用模式,调用方在接口被调用时,也会调用对方的接口,意即实现了抽象类或接口的实例,实现了父类提供的抽象方法后,将该方法交还给父类来处理。更简明的说,就是实现方法交还给提供接口的父类来处理。
所谓的回调函数就是:
你在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生命周期,基本都是回调函数在作用:
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诸如此场景也可以用回调
一般来说,类的成员变量都是数据对象,主要是用来传递数据的。回调是将一段程序作为成员变量,在特定的场合使用该段程序,此为回调的核心。
而在程序代码中,则可以抽象成以下这张图的形式(摘自此博客,感谢前辈):
C不会自己调用b,C提供b的目的就是让S来调用它,而且C不得不提供。S并不知道C提供的b是什么,因此S会约定b的接口规范(函数原型),然后由C提前通过S的一个函数r告诉S自己将要使用b函数(即注册),比如注册监听器就是其中一种典型。其中r为注册函数。
对上图的一个完善是这样的:
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