介面回撥目的和用法解析
一、回撥的含義和用途
1. 什麼是回撥
一般來說,模組之間都存在一定的呼叫關係,從呼叫方式上來看,可分為三類:
- 同步呼叫:同步呼叫是一種阻塞式呼叫,即在函式A的函式體裡通過書寫函式B的函式名來呼叫之,使記憶體中對應函式B的程式碼得以執行。
- 非同步呼叫:非同步呼叫是一種類似訊息或事件的機制解決了同步阻塞的問題,例如A通知B後,他們各走各的路,互不影響,不用像同步呼叫那樣,A通知B後,非得等到B走完後,A才繼續走。
- 回撥:回撥是一種雙向的呼叫模式,也就是說,被呼叫的介面被呼叫時也會呼叫對方的介面,例如A要呼叫B,B在執行完又要呼叫A。
2. 回撥的用途
回撥一般用於層間協作,上層將本層函式安裝在下層,這個函式就是回撥,而下層在一定條件下觸發回撥。例如作為一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回撥,將這個資料交給上層應用層來做進一步處理,這在分層的資料通訊中很普遍。
二、為什麼會存在回撥機制
舉例1:
有一位老闆(上層模組)很忙,他沒有時間盯著員工(下層模組)幹活,然後他告訴自己的僱員,幹完當前這些事情後,告訴他幹活的結果。這個例子其實是一個回撥+非同步的例子,再舉一個例子,A程式設計師寫了一段程式a,其中預留了回撥函式介面,並封裝好了該程式,程式設計師B讓a呼叫自己的程式b中的一個方法,於是,他通過a中的介面回撥自己b中的方法。下面把上面的例子變成程式碼
1.首先建立一個回撥介面,讓老闆得告知幹完活如何找到他的方式:留下老闆辦公室地址:
/**
* 此介面為聯絡的方式,不論是電話號碼還是聯絡地址,作為
* 老闆都必須要實現此介面
*/
public interface CallBackInterface {
public void execute();
}
2.建立回撥物件,就是老闆本人,因為員工幹完活後要給他打電話,因此老闆必須實現回撥介面,不然員工去哪裡找老闆?
/**
* 老闆是作為上層應用身份出現的,下層應用(員工)是不知道
* 有哪些方法,因此他想被下層應用(員工)呼叫必須實現此介面
*/
public class Boss implements CallBackInterface {
@Override
public void execute () {
System.out.println("收到了!!" + System.currentTimeMillis());
}
}
3.建立控制類,也就是員工物件,他必須持有老闆的地址(回撥介面),即使老闆換了一茬又一茬,辦公室不變,總能找到對應的老闆。
/**
* 員工類,必須要記住,這是一個底層類,底層是不瞭解上層服務的
*/
public class Employee {
private CallBackInterface callBack = null;
//告訴老闆的聯絡方式,也就是註冊
public void setCallBack(CallBackInterface callBack){
this.callBack = callBack;
}
//工人幹活
public void doSome(){
//1.開始幹活了
for(int i=0;i<10;i++){
System.out.println("第【" + i + "】事情幹完了!");
}
//2.告訴老闆幹完了
callBack.execute();
}
}
4.測試類程式碼:
public class Client {
public static void main(String[] args) {
Employee emp = new Employee();
//將回調物件(上層物件)傳入,註冊
emp.setCallBack(new Boss());
//開啟控制器物件執行
emp.doSome();
}
}
舉例2:
對於回撥函式的理解可以參照C或C++中對回撥函式的定義:
程式在呼叫一個函式時,將自己的函式的地址作為引數傳遞給程式呼叫的函式時(那麼這個自己的函式稱回撥函式)
然而Java中沒有指標,不能傳遞方法的地址,一般採用介面回撥實現:把實現某一介面的類建立的物件的引用賦給該介面宣告的介面變數,那麼該介面變數就可以呼叫被類實現的介面的方法。
例如:
一讀者想借《軟體技術學習與實踐》這本書,但這本書已被其他讀者借走了。於是,讀者與圖書館管理員間發生了以下對話:
讀者:“我把我的電話號碼告訴你,等書一到就馬上通知我。”
管理員:“好的。另一讀者把書還回來後,馬上給您打電話,書我先幫您留著。”
在上述這個場景中,讀者就是“回撥物件”,管理員就是“控制器物件”,讀者的電話號碼就是“回撥物件的方法”。
在控制器類中引用了回撥物件,因此就能呼叫回撥方法,當控制器進行某些判斷之後(如:監聽滑鼠單擊操作)就會自動呼叫回撥方法!簡易流程圖如下:
舉例3:
假如有兩個類
- classA
- classB
如果A要呼叫B的方法,則:在classA中建立B的物件
如果B要呼叫A的方法,則:在classB中建立A的物件
兩個類構成一個迴圈的關係,“你中有我,我中有你”。這樣操作在實際中最不可取,因為耦合度最高。所以要想辦法打斷其中的一條線,打破迴圈關係。
怎麼打斷呢?——介面回撥
介面回撥三部曲:
一、 在B類中建立事件介面interface
//B類是自定義Video類。在B類中建立,供邏輯層來實現具體邏輯
public interface ADVideoPlayerListener {
public void onBufferUpdate(int time);
public void onClickFullScreenBtn();
public void onClickVideo();
public void onClickBackBtn();
public void onClickPlay();
public void onAdVideoLoadSuccess();
public void onAdVideoLoadFailed();
public void onAdVideoLoadComplete();
}
二、在A類中implement此介面
在邏輯層中實現此介面,並且實現相應方法
public class VideoAdSlot implements CustomVideo.ADVideoPlayerListener{
}
三、在A類中建立B類例項時,同時為B類setListener
在建立B類(自定義類CustomVideo)的時候,同時為它繫結一個Listener。
private void initVideoView() {
//建立B類(自定義Video類)
mVideoView = new CustomVideoView(mContext, mParentView);
if (mXAdInstance != null) {
mVideoView.setDataSource(mXAdInstance.resource);
//注入一個listener
mVideoView.setListener(this);
}
mParentView.addView(mVideoView);
}
這樣當,在B類(CustomVideo)中觸發對應事件時,就會通知到A類(邏輯層)實現此方法。
private ADVideoPlayerListener listener;
@Override
public void onClick(View v) {
if (v == this.mMiniPlayBtn) {
if (this.playerState == STATE_PAUSING) {
if (Utils.getVisiblePercent(mParentContainer)
> SDKConstant.VIDEO_SCREEN_PERCENT) {
resume();
this.listener.onClickPlay();
}
} else {
load();
}
} else if (v == this.mFullBtn) {
this.listener.onClickFullScreenBtn();
} else if (v == mVideoView) {
/**
*當點選視訊區的時候,就會呼叫listener的onClickVideo
*當事件真正產生的時候,就會呼叫A類(邏輯層)的onClickVideo()
*這樣也就完成了相互的通訊。
*A類既可以呼叫B類的的方法,B類也可以呼叫A類中的方法。
*/
this.listener.onClickVideo();
}
}
總結:
在三層中,當業務層呼叫資料層時,是不需要把業務層自身傳遞到資料層的,並且這是一種上層呼叫下層的關係,比如我們在用框架的時候,一般直接呼叫框架提供的API就可以了,但回撥不同,當框架不能滿足需求,我們想讓框架來呼叫自己的類方法,怎麼做呢?總不至於去修改框架吧。許多優秀的框架提幾乎都供了相關的介面,我們只需要實現相關介面,即可完成了註冊,然後在合適的時候讓框架來呼叫我們自己的類。
最後,再舉一例,為了使我們寫的函式接近完美,就把一部分功能外包給別人,讓別人個性化定製,至於別人怎麼實現不管,我唯一要做的就是定義好相關介面,這一設計允許了底層程式碼呼叫高層定義的子程式,增強程式靈活性,和反射有著異曲同工之妙,這才是回撥的真正原因!
用一段話來總結下回調:上層模組封裝時,很難預料下層模組會如何實現,因此,上層模組只需定義好自己需要但不能預料的介面(也就是回撥介面),當下層模組呼叫上層模組時,根據當前需要的實現回撥介面,並通過註冊或引數方式傳入上層模組即可,這樣就實現下層呼叫上層,並且上層還能根據傳入的引用來呼叫下層的具體實現,將程式的靈活性大大的增加了。