前言:
回调函数在开发中是很实用的一块知识点。
本文从原理及应用两个角度深入理解回调函数。
希望在交流中得到进步,也本着分享精神把知识传播出去,希望后来人少走我走过的弯路。
所以开始写博客,路漫漫其修远兮,吾将上下而求索。
回调函数的原理描述
要理解回调函数,首先要明确什么时候使用回调函数?通俗的讲,一般给某个类的对象在某个触发时机,添加一个可触发的事件函数,并使此事件函数能调用一个函数。这个被调用的函数就是回调函数。这个机制就是回调机制。
如在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出
来。
补充:
今天看了一点架构知识。发现回调函数是实现主动型API架构的一种方式。至于主动型API架构和被动型API架构,后续学习完设计模式和Android框架层会慢慢整理到博客。此处先稍做记录。
来看主动型API架构的三个特点:
1)定义:自己给出定义接口和基类
2)实现:使用者实现接口或基类
3)呼叫:呼叫我的理解其实就是控制权,控制权一定要在自己手里
做到以上三点即是主动型API架构。
来看Google的一个子吧:
简短的做下补充,等把设计模式和架构融会贯通,再整理到博客。希望大家多多指点。