首頁 > 軟體

​​​​​​​Android H5通用容器架構設計詳解

2022-09-05 14:01:45

背景

大家如果經歷過Hybrid專案的開發,即專案中涉及到H5與Native之間的互動,那麼很有可能會遇到各種各樣的H5容器。為什麼會有那麼多各種各樣的容器呢...這也是輪子多的通病了,輪子多到業務方不知道選哪個。當然,也有可能大家壓根就不會使用到H5容器,直接用系統WebView就完事兒了,比如我的前東家就是這樣做的。那這篇文章的主題就是與前者相關的:當專案中擁有很多個H5容器時,怎樣封裝才能讓業務側用得爽呢? 

下面按慣例,丟擲這篇文章要解決的三個問題:

  • 如何優雅地提供介面呼叫?
  • 怎樣封裝多個不同型別的H5容器?
  • 這樣的架構能帶來什麼樣的好處?

術語對齊

術語描述
H5框架容器特指目前專案中正在使用的二方/三方H5容器。
H5通用容器特指將專案中所有的框架容器抽象到一個容器中,讓業務側不需要感知到具體的框架細節。
A頁面url query引數中帶a=true的頁面,專案中用A容器承載。
B頁面url query引數中帶b=true的頁面,專案中用B容器承載。

探索

如何優雅地提供介面呼叫?

業務側呼叫的介面其實很有限,可以說90%的業務都只是開啟一個網頁而已。

我們根據業務需要新建一個介面類:

/**
 * WebPage服務介面類
 */
public interface IWebPageService {
    /**
     * 開啟url
     * @param url
     */
    void openUrl(String url);

    /**
     * 建立Fragment內建到某頁面中使用
     * @return
     */
    Fragment buildFragment(String url, Context context);

    /**
     * 獲取當前Url
     * @return
     */
    String getCurrentUrl();

    /**
     * 給h5發通知
     * @param eventData
     * @param eventName
     */
    void postNotificationToJS(String eventName, String eventData);
}

以上列舉了4個非常常見的介面。如果有需要,還可以擴充套件其它的,比如設定容器生命週期相關的監聽、設定H5發通知來時的監聽等等。拿openUrl的實現舉例,實現類通過url中的引數判斷需要開啟A容器還是B容器

@Override
public void openUrl(String url) {
     // 若url中含有A引數,則用A容器開啟
     // if (urlParamHasA) {
        // startAContainer();
     // } else {
        // 否則用B容器開啟
        // startBContainer();
     //}
}

這就從介面層這一層面遮蔽了內部框架容器。其它介面也是類似,原則就是不讓業務側感知到具體的實現細節,而不是跳個頁面還需要知道這個url是要用A容器開啟還是B容器開啟。在應用啟動時,可以將實現類注入到公共依賴中去,也可以通過ARouter等框架實現依賴注入,方便業務側的呼叫。

怎樣封裝多個不同型別的H5容器容器?

整體架構

先上一張圖:

介面層上面已經講過了,這裡我們直接看容器層。

通用容器

第一層就是我們抽出來的通用容器,WebPageActivity與WebPageFragement。專案中,不管是哪個H5框架容器,勢必都是用Activity或者Fragment來承載的,所以我們相應的也需要有這兩者作為父類別去進行封裝。而通用容器中,最重要的職責就是去執行通用邏輯,即每個框架容器都需要執行的邏輯。

通用職責可以有以下幾點:

  • 設定頁面屬性,包括window flag、activity的主題等。
  • 解析url,將url中的關鍵引數記錄下來,從而決定以哪種策略渲染UI。比如可以通過disableNav引數決定是否顯示導航欄,通過statusBarStyle引數決定狀態列的風格等。
  • 控制通用UI的渲染,包括何時改變TitleBar、何時載入/隱藏Loading,何時渲染異常態UI等。
  • 決定框架容器載入時的邏輯順序,如setContentView()->loadUrl()。
  • 接收生命週期事件與系統回撥事件,並且分發回撥。生命週期就不用說了,系統回撥這裡特指onActivityResultonRequestPermissionResult這類事件,然後將結果分發給各listener。

WebPageActivity#onCreate舉例:

@Override
protected void onCreate(Bundle savedInstanceState) {
    // setWindowFlags(); 設定window屬性
    // setTheme(); 設定主題
    // setTransition(); 設定轉場動畫
    
    super.onCreate(savedInstanceState);

    // parseUrl(); 解析url
    // initCommonUI(); 渲染通用UI 比如TitleBar、底部導航欄等
    
    setContentView();
    loadUrl();
}

protected abstract void setContentView();

protected abstract void loadUrl();

我們可以將設定window屬性、設定主題這些通用行為放在通用容器中執行,而setContentViewloadUrl交由框架容器去執行。setContentView是因為不同的容器,layout xml可能是不一樣的。loadUrl是因為每個容器用WebView去載入url的方式也可能不同,無法統一。類似的行為,若執行時機可統一,則由通用容器統一執行時機,比如setContentViewloadUrl。若時機也沒法確定,甚至邏輯只會存在於某個框架容器中,那麼就要寫到子類中去了。

框架容器

圖中第二層的容器A、容器B、容器C指的就是框架容器。有了通用容器承載共同的邏輯後,框架容器需要做的事情就比較少了。只需要載入各自的佈局,初始化WebView,然後將url載入到WebView中就可以了(除非有某個框架容器需要特殊適配,那就得寫些額外的程式碼)。具體的實現需要根據專案中框架容器的使用方式來確定。

基礎元件

第三層表示基礎元件。這裡只列一些常見的基礎元件。

  • Components:各類UI元件,比如TitleBar與異常態控制元件。
  • JS Bridge:JS Bridge可以單獨放到一個類中做收口,然後分發給各個listener。listenr通過介面層進行注入。
  • WebViewClient:WebViewClient在日常開發中算是維護的非常頻繁的一個類,職責主要是在shouldOverrideUrlLoading重定向時對url進行攔截,以及根據相應的生命週期回撥(onPageStarted、onPageFinished、onReceviedError等)進行loading或異常態UI的渲染時。若每個框架容器可以設定同一個WebViewClient,那就非常方便了,只要改一處地方,所有容器就都生效了。
  • Interceptors:即用於WebView重定向時的攔截器。像我的專案中會有很多攔截相關的邏輯,比如將A頁面攔截跳轉到原生頁面,就需要在重定向時進行攔截。但邏輯堆疊太多會提高維護的成本,因此建議用責任鏈模式(沒用過的同學可以百度一下,很簡單滴~)去處理攔截的這塊邏輯。
  • Managers、Listeners:容器中總是需要很多輔助類去來幫助我們去管理一些東西,比如WebView的管理,url的管理。這裡就不延伸了,根據實際需要去加。Listener也一樣,關於容器生命週期的Listener,與JS Bridge互動相關的Listener,都可以通過介面層去實現注入與銷燬。

當然,除此之外,根據每個專案的完整度與負責度,還會衍生出很多基礎元件。比如H5的監控體系,H5的預載入體系,以及WebView預建立相關的快取體系等。他們作為框架容器共同的底座承擔相應的職責。

這樣的架構能帶來什麼樣的好處?

  • 對業務側而言,介面統一收口到IWebPageService中去,呼叫方式更加簡單明瞭,不需要再關注容器的具體型別。
  • 對容器側而言,程式碼邏輯性清晰的同時,有效降低了日後的維護成本與開發成本。Loading、異常態不需要再在各類容器中重複寫N遍,各種url params都統一收口到通用容器中去,重定向攔截邏輯也不再需要重複寫N遍...總而言之,通用邏輯統一給通用容器與基礎元件去承擔,框架容器的程式碼,都是各框架容器所“獨有”的。

到此這篇關於Android H5通用容器架構設計詳解的文章就介紹到這了,更多相關Android H5容器架構內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com