1. 程式人生 > >介面回撥目的和用法解析

介面回撥目的和用法解析

一、回撥的含義和用途

1. 什麼是回撥

一般來說,模組之間都存在一定的呼叫關係,從呼叫方式上來看,可分為三類:

  1. 同步呼叫:同步呼叫是一種阻塞式呼叫,即在函式A的函式體裡通過書寫函式B的函式名來呼叫之,使記憶體中對應函式B的程式碼得以執行。
  2. 非同步呼叫:非同步呼叫是一種類似訊息或事件的機制解決了同步阻塞的問題,例如A通知B後,他們各走各的路,互不影響,不用像同步呼叫那樣,A通知B後,非得等到B走完後,A才繼續走。
  3. 回撥:回撥是一種雙向的呼叫模式,也就是說,被呼叫的介面被呼叫時也會呼叫對方的介面,例如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就可以了,但回撥不同,當框架不能滿足需求,我們想讓框架來呼叫自己的類方法,怎麼做呢?總不至於去修改框架吧。許多優秀的框架提幾乎都供了相關的介面,我們只需要實現相關介面,即可完成了註冊,然後在合適的時候讓框架來呼叫我們自己的類。

最後,再舉一例,為了使我們寫的函式接近完美,就把一部分功能外包給別人,讓別人個性化定製,至於別人怎麼實現不管,我唯一要做的就是定義好相關介面,這一設計允許了底層程式碼呼叫高層定義的子程式,增強程式靈活性,和反射有著異曲同工之妙,這才是回撥的真正原因!

用一段話來總結下回調:上層模組封裝時,很難預料下層模組會如何實現,因此,上層模組只需定義好自己需要但不能預料的介面(也就是回撥介面),當下層模組呼叫上層模組時,根據當前需要的實現回撥介面,並通過註冊或引數方式傳入上層模組即可,這樣就實現下層呼叫上層,並且上層還能根據傳入的引用來呼叫下層的具體實現,將程式的靈活性大大的增加了。

參考: