前言:
回調(diào)函數(shù)在開發(fā)中是很實(shí)用的一塊知識點(diǎn)帅掘。
本文從原理及應(yīng)用兩個角度深入理解回調(diào)函數(shù)。
希望在交流中得到進(jìn)步,也本著分享精神把知識傳播出去副砍,希望后來人少走我走過的彎路。
所以開始寫博客颠通,路漫漫其修遠(yuǎn)兮址晕,吾將上下而求索。
回調(diào)函數(shù)的原理描述
要理解回調(diào)函數(shù)顿锰,首先要明確什么時候使用回調(diào)函數(shù)谨垃?通俗的講,一般給某個類的對象在某個觸發(fā)時機(jī)硼控,添加一個可觸發(fā)的事件函數(shù)刘陶,并使此事件函數(shù)能調(diào)用一個函數(shù)。這個被調(diào)用的函數(shù)就是回調(diào)函數(shù)牢撼。這個機(jī)制就是回調(diào)機(jī)制匙隔。
如在Android中Button摁扭的點(diǎn)擊事件,是給Button對象在觸發(fā)時機(jī)為點(diǎn)擊時熏版,執(zhí)行觸發(fā)的事件函數(shù)纷责,并在事件函數(shù)內(nèi)調(diào)用設(shè)置的一個點(diǎn)擊回調(diào)函數(shù)。
(一)實(shí)現(xiàn)回調(diào)函數(shù)的原理其實(shí)很簡單
1.定義一個回調(diào)接口撼短,并定義一個回調(diào)方法
2.定義出要設(shè)置回調(diào)機(jī)制的類再膳,并使此類持有回調(diào)接口的指針
3.在要設(shè)置回調(diào)機(jī)制的類中,初始化回調(diào)接口指針曲横,并使用指針調(diào)用回調(diào)函數(shù)
4.在要設(shè)置回調(diào)機(jī)制的類中設(shè)置觸發(fā)時機(jī)及執(zhí)行的觸發(fā)事件函數(shù)
(二)按照上面思路實(shí)現(xiàn)回調(diào)機(jī)制方式有三種
1.在構(gòu)造器中初始化回調(diào)接口指針
2.在自定義方法中實(shí)現(xiàn)初始化回調(diào)接口指針
3.將要設(shè)置回調(diào)機(jī)制的類喂柒,設(shè)置為抽象類并實(shí)現(xiàn)回調(diào)接口
回調(diào)機(jī)制的代碼實(shí)現(xiàn)
下面分別使用上面提到的實(shí)現(xiàn)回調(diào)機(jī)制的三種方式,使用代碼實(shí)現(xiàn)禾嫉。
方式一(在構(gòu)造器中初始化回調(diào)接口指針)
/**
*
* @author 趙默陽
* @date 2017年5月10日 上午10:18:20
*
* 第一步定義一個回調(diào)接口及回調(diào)方法
*
*/
public interface CallBackInterface {
void callback();
}
/**
* @author 趙默陽
* @date 2017年5月10日 上午10:18:25
* 定義要設(shè)置回調(diào)機(jī)制的類
*
* 1)設(shè)置此類持有 回調(diào)接口指針對象
* 2)設(shè)置構(gòu)造器初始化回調(diào)接口對象
* 3)設(shè)置觸發(fā)事件函數(shù)灾杰,調(diào)用回調(diào)函數(shù);設(shè)置觸發(fā)機(jī)制及觸發(fā)事件函數(shù)
*/
public class CallObject {
//第二步持有回調(diào)接口指針對象
private CallBackInterface callBackInterface=null;
//第三步在構(gòu)造方法里初始化回調(diào)接口
public CallObject(CallBackInterface callBackInterface){
this.callBackInterface=callBackInterface;
}
/*
* 第四步 設(shè)置觸發(fā)事件函數(shù),并在內(nèi)部使用回調(diào)函數(shù)接口指針調(diào)用回調(diào)函數(shù)
*/
public void goCallMethord(){
callBackInterface.callback();
}
/*
* 第四步 設(shè)置執(zhí)行觸發(fā)事件函數(shù)的觸發(fā)時機(jī)
* 舉例:這里是 CallObject執(zhí)行的一個方法熙参,執(zhí)行完畢即觸發(fā)事件函數(shù)
*/
public void doSomething(){
for(int i=0;i<10;i++){
System.out.println("CallObject操作方法 ···");
}
goCallMethord(); //執(zhí)行觸發(fā)事件函數(shù)
}
}
通過上面的代碼艳吠,我們已經(jīng)給類設(shè)置了回調(diào)機(jī)制。其執(zhí)行時機(jī)是當(dāng)執(zhí)行完doSomething方法內(nèi)的for循環(huán)操作后尊惰,執(zhí)行觸發(fā)事件函數(shù)讲竿,在觸發(fā)事件函數(shù)內(nèi)執(zhí)行回調(diào)函數(shù)。下面弄屡,來看下運(yùn)行結(jié)果吧。
/**
*
* @author 趙默陽
* @date 2017年5月10日 上午10:18:28
* 查看測試結(jié)果
*/
public class Test{
/**
* @param args
*/
public static void main(String[] args) {
//方式一實(shí)現(xiàn)回調(diào)機(jī)制鞋诗,需要在new對象的時候傳入回調(diào)接口的實(shí)現(xiàn)對象
CallObject callObject=new CallObject(new CallBackInterface() {
@Override
public void callback() {
// TODO Auto-generated method stub
System.out.println("do something···");
}
});
callObject.doSomething(); //執(zhí)行觸發(fā)時機(jī)方法
}
}
查看console中結(jié)果膀捷,回調(diào)方法在執(zhí)行doSomething方法內(nèi)操作完成后,觸發(fā)了回調(diào)機(jī)制削彬。
方式二(在自定義方法中實(shí)現(xiàn)初始化回調(diào)接口指針)
在andoid中全庸,這是非常常見的回調(diào)機(jī)制實(shí)現(xiàn)方式秀仲。如使用serOnClickListener()等方法實(shí)現(xiàn)初始化回調(diào)接口指針。
/**
*
* @author 趙默陽
* @date 2017年5月10日 上午10:18:20
*
* 第一步定義一個回調(diào)接口及回調(diào)方法
*
*/
public interface CallBackInterface {
void callback();
}
/**
* @author 趙默陽
* @date 2017年5月10日 上午10:18:25
*
* 需求: 給一個類的對象設(shè)置觸發(fā)事件給出回調(diào)方法
* 定義要設(shè)置回調(diào)機(jī)制的類
*
* 1) 設(shè)置此類持有 回調(diào)接口指針對象
* 2) 設(shè)置自定義初始化回調(diào)接口指針對象的方法壶笼,
* 一般以回調(diào)時機(jī)命名神僵,如setDoSomethingDownListener 即當(dāng)
* doSomething方法操作執(zhí)行完的監(jiān)聽
* 3) 設(shè)置觸發(fā)事件函數(shù),調(diào)用回調(diào)函數(shù);設(shè)置觸發(fā)機(jī)制及觸發(fā)事件函數(shù)
*/
public class CallObject {
//第二步持有的回調(diào)接口指針對象
private CallBackInterface callBackInterface=null;
//第三步聲明 空構(gòu)造
public CallObject(){}
/*
* 第三步設(shè)置自定義初始化回調(diào)接口指針對象的方法覆劈,并把回調(diào)接口對象當(dāng)參數(shù)
* 比如執(zhí)行完某段代碼保礼,調(diào)用這個方法
*/
public void setDoSomethingDownListener (CallBackInterface callBackInterface1){
this.callBackInterface=callBackInterface1;
};
/*
* 第四步 設(shè)置觸發(fā)事件函數(shù),并在內(nèi)部使用回調(diào)函數(shù)接口指針調(diào)用回調(diào)函數(shù)
*/
public void goCallMethord(){
callBackInterface.callback();
}
/*
* 第四步 設(shè)置執(zhí)行觸發(fā)事件函數(shù)的觸發(fā)時機(jī)
* 舉例:這里是 CallObject執(zhí)行的一個方法责语,執(zhí)行完畢即觸發(fā)事件函數(shù)
*/
public void doSomething(){
for(int i=0;i<10;i++){
System.out.println("CallObject操作方法 ···");
}
goCallMethord(); //執(zhí)行觸發(fā)事件函數(shù)
}
}
同樣炮障,我們來看看方式二的使用結(jié)果吧。
先看使用匿名內(nèi)部類坤候,做Android開發(fā)的同學(xué)是不是特別熟悉呢胁赢?
/**
*
* @author 趙默陽
*
* @date 2017年5月10日 上午10:18:28
*
*/
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
CallObject callObject2=new CallObject();
//使用匿名內(nèi)部類
callObject2.setDoSomethingDownListener(new CallBackInterface() {
@Override
public void callback() {
// TODO Auto-generated method stub
System.out.println("我是設(shè)置的匿名回調(diào)···");
}
});
//執(zhí)行觸發(fā)時機(jī)的函數(shù),其內(nèi)部執(zhí)行完for循環(huán) 操作會調(diào)用觸發(fā)事件函數(shù)
callObject2.doSomething();
}
}
再看看使用實(shí)現(xiàn)接口效果白筹,做Android開發(fā)的同學(xué)是不是還是特別熟悉呢智末?
/**
*
* @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("我是設(shè)置的set監(jiān)聽的回調(diào)···");
}
public void test(){
//設(shè)置監(jiān)聽
CallObject callObject1=new CallObject();
callObject1.setDoSomethingDownListener(this);
//執(zhí)行觸發(fā)時機(jī)的函數(shù),其內(nèi)部執(zhí)行完for循環(huán) 操作會調(diào)用觸發(fā)事件函數(shù)
callObject1.doSomething();
}
}
方式三(將要設(shè)置回調(diào)機(jī)制的類徒河,設(shè)置為抽象類并實(shí)現(xiàn)回調(diào)接口)
/**
*
* @author 趙默陽
*
* @date 2017年5月10日 上午10:18:20
*
* 定義一個回調(diào)接口及回調(diào)方法
*
*/
public interface CallBackInterface {
void callback();
}
/**
* @author 趙默陽
* @date 2017年5月11日 上午1:07:48
* 定義一個實(shí)現(xiàn)回調(diào)機(jī)制的類
*
* 步驟:
* 1) 將此類定義為一個抽象方法系馆,并實(shí)現(xiàn)回調(diào)接口
* 2) 定義觸發(fā)時機(jī),及觸發(fā)時機(jī)時執(zhí)行的函數(shù)
*/
public abstract class CallObject implements CallBackInterface{
/*
* 設(shè)置執(zhí)行觸發(fā)事件函數(shù)的觸發(fā)時機(jī)
* 舉例:這里是 CallObject執(zhí)行的一個方法虚青,執(zhí)行完畢即觸發(fā)事件函數(shù)
*/
public void doSomething(){
for(int i=0;i<10;i++){
System.out.println("CallObject操作方法 ···");
}
goCallMethord(); //執(zhí)行觸發(fā)事件函數(shù)
}
//觸發(fā)時機(jī)時執(zhí)行的觸發(fā)事件函數(shù)
public void goCallMethord(){
callback(); //調(diào)用回調(diào)函數(shù)它呀,將實(shí)現(xiàn)交給實(shí)現(xiàn)類
}
}
方式三實(shí)現(xiàn)比較簡單,但是效果是一樣的棒厘。我們來看下執(zhí)行結(jié)果吧纵穿。
/**
* @author 趙默陽
*
* @date 2017年5月11日 上午1:14:40
*
*/
public class Test {
public static void main(String[] args) {
//聲明并初始化 定義回調(diào)方法的類 的時候會實(shí)現(xiàn)回調(diào)方法
// 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 ···");
}
};
//觸發(fā)時機(jī)即執(zhí)行完doSomething方法內(nèi)的操作后執(zhí)行觸發(fā)事件函數(shù)
callObject.doSomething();
}
}
回調(diào)機(jī)制的應(yīng)用
回調(diào)函數(shù)的應(yīng)用非常常見和方便。通過上面我們了解了回調(diào)機(jī)制的原理
和基本實(shí)現(xiàn)方式奢人。下面我們把回調(diào)機(jī)制使用在我們的開發(fā)工作中吧谓媒。
一)回調(diào)機(jī)制在系統(tǒng)代碼里的應(yīng)用
最近在看Dialog的源碼,下面我們看下Window類的回調(diào)機(jī)制何乎,怎么在Doalog類下使用吧句惯。
/**
* API from a Window back to its caller. This allows the client to
* intercept key dispatching, panels and menus, etc.
* 首先,在Window有個內(nèi)部接口支救,里面定義了很多回調(diào)方法抢野,我們只取一個舉例
*/
public interface Callback {
/**
* This is called whenever the current window attributes change.
* window屬性改變回調(diào)方法
*/
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
}
/*
* Window類,只截取有用代碼
* 1. 持有回調(diào)接口指針
* 2. 使用方法初始化回調(diào)接口指針
* 3. 觸發(fā)時機(jī)時 執(zhí)行的觸發(fā)事件函數(shù)
*/
public abstract class Window {
private Callback mCallback; //持有回調(diào)接口指針
/**
* Set the Callback interface for this window, used to intercept key
* events and other dynamic operations in the window.
* 初始化回調(diào)函數(shù)指針的方法
* @param callback The desired Callback interface.
*/
public void setCallback(Callback callback) {
mCallback = callback;
}
/**
* {@hide}
* 觸發(fā)時機(jī)時 執(zhí)行的觸發(fā)事件函數(shù)
*/
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);
/*
* 這是其中一個觸發(fā)時機(jī) 執(zhí)行的函數(shù)
* 在這里執(zhí)行了 觸發(fā)事件函數(shù)
*/
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);
//來看重點(diǎn)各墨,這里new出來window實(shí)現(xiàn)類對象
final Window w = new PhoneWindow(mContext);
mWindow = w;
/*
*這一句初始化了window持有的回調(diào)函數(shù)接口指針
*傳入了 this指孤,說明dialog實(shí)現(xiàn)了回調(diào)函數(shù)接口
*/
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
/*
* 回調(diào)實(shí)現(xiàn)方法
*/
@Override
public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
if (mDecor != null) {
mWindowManager.updateViewLayout(mDecor, params);
}
}
}
當(dāng)然以上代碼只是實(shí)現(xiàn)了回調(diào)機(jī)制,但是并沒有觸發(fā),如果要觸發(fā)這個回調(diào)機(jī)制恃轩,需要window類對象調(diào)用 觸發(fā)時機(jī)的函數(shù)即setAttributes函數(shù)结洼。這時,就會觸發(fā)回調(diào)機(jī)制叉跛,在Dialog類中執(zhí)行回調(diào)方法松忍。這時也實(shí)現(xiàn)了,window類中的改變及時通知Dialog筷厘。
二)回調(diào)機(jī)制在自己代碼里的應(yīng)用
對于回調(diào)機(jī)制在自己代碼的應(yīng)用鸣峭,就給大家展示一張我封裝的一個仿H5兩級聯(lián)動庫的效果圖吧。其使用方式就是
用了上面代碼實(shí)現(xiàn)的三種方式之一敞掘。大家先看圖吧叽掘,也算是個預(yù)告,下一篇博客我會介紹這個庫的使
用方法及實(shí)現(xiàn)原理玖雁,本著分享精神更扁,我會把這個仿H5兩級聯(lián)動庫共享出來。
這里主要是自定義了一個PopupWindow赫冬,實(shí)現(xiàn)了仿H5樣式的兩級聯(lián)動浓镜。當(dāng)選擇好數(shù)據(jù)后,點(diǎn)擊確定會
執(zhí)行一個回調(diào)方法onSure函數(shù)劲厌,其參數(shù)即選擇的數(shù)據(jù)膛薛,以便于處理選擇好的數(shù)據(jù),目前只是Toast出
來补鼻。
補(bǔ)充:
今天看了一點(diǎn)架構(gòu)知識哄啄。發(fā)現(xiàn)回調(diào)函數(shù)是實(shí)現(xiàn)主動型API架構(gòu)的一種方式。至于主動型API架構(gòu)和被動型API架構(gòu)风范,后續(xù)學(xué)習(xí)完設(shè)計模式和Android框架層會慢慢整理到博客咨跌。此處先稍做記錄。
來看主動型API架構(gòu)的三個特點(diǎn):
1)定義:自己給出定義接口和基類
2)實(shí)現(xiàn):使用者實(shí)現(xiàn)接口或基類
3)呼叫:呼叫我的理解其實(shí)就是控制權(quán)硼婿,控制權(quán)一定要在自己手里
做到以上三點(diǎn)即是主動型API架構(gòu)锌半。
來看Google的一個子吧:
簡短的做下補(bǔ)充,等把設(shè)計模式和架構(gòu)融會貫通寇漫,再整理到博客刊殉。希望大家多多指點(diǎn)。