app中內(nèi)嵌h5常見問題的一些思考

一.引子

隨著h5技術(shù)這幾年的蓬勃發(fā)展,大多數(shù)移動(dòng)端開發(fā)工程師沒少和它打過交道。h5的好處我們就不多提了,相信你一定知道它的優(yōu)點(diǎn)(開發(fā)快速,隨時(shí)發(fā)布更新),加載消耗內(nèi)存,體驗(yàn)比不上native的大眾問題我們這里也不去討論,技術(shù)優(yōu)化我們也不去詳解(單進(jìn)程,預(yù)加載,緩存...)。Stop!What?那我們?cè)谶@里要講什么?我相信你大抵是經(jīng)歷過這么一個(gè)過程:app不斷快速發(fā)展、迭代,內(nèi)嵌h5頁越來越多,承擔(dān)的業(yè)務(wù)越來越復(fù)雜,相應(yīng)的App內(nèi)h5頁面管理也越來越混亂。

二.目前的暴露的問題

下面我由我所經(jīng)手項(xiàng)目暴露的一些問題來概括上述敘說。

1. 布局顯示問題。

h5頁面中原生的布局通常只有一個(gè)Titlebar,所以布局顯示問題,大體上可以歸納為titlebar的問題。


正常顯示

錯(cuò)誤顯示

上面兩個(gè)頁面都是內(nèi)嵌h5訂單頁面,bug產(chǎn)生原因也不復(fù)雜。在最初版本app中,訂單頁入口處是課程詳情頁,最初協(xié)議的結(jié)果就是移動(dòng)端不提供titlebar,加載一個(gè)全屏的h5頁面,由h5自己實(shí)現(xiàn)titlebar的導(dǎo)航工作。后來隨著app業(yè)務(wù)發(fā)展,App內(nèi)有了運(yùn)營活動(dòng),在banner或者其他入口進(jìn)入的h5頁面是由原生提供導(dǎo)航條。而在用戶進(jìn)行下單操作時(shí),h5頁面進(jìn)行了頁面復(fù)用,Duang ~~,雙下巴問題就這樣產(chǎn)生了。(是不是濃濃山寨風(fēng)撲來?)

當(dāng)然,除了上述問題,還一個(gè)很突出的案例是在Titlebar上顯示一個(gè)菜單按鈕。根據(jù)當(dāng)前不同的業(yè)務(wù)場景,顯示不同的菜單,點(diǎn)擊菜單進(jìn)行相應(yīng)的操作。比如h5頁面A具有分享功能,在TitleBar顯示一個(gè)分享按鈕,點(diǎn)擊按鈕可以分享鏈接。h5頁面B具有一個(gè)意見反饋功能,在TitleBar顯示一個(gè)反饋按鈕,點(diǎn)擊按鈕可以提交意見反饋。這類功能算是App比較常用的功能。試問,當(dāng)你面臨這些需求的時(shí)候,你會(huì)怎么做呢?(PS:App的WebView頁面只是h5頁面的容器,不要去做任何業(yè)務(wù)相關(guān)的代碼)

2. 業(yè)務(wù)流程問題(業(yè)務(wù)支持能力)

在客戶端開發(fā)中,頁面都是以棧的方式進(jìn)行管理的,每新打開一個(gè)頁面,進(jìn)行一次入棧操作,退出頁面時(shí),進(jìn)行一次出棧操作。這是操作系統(tǒng)默認(rèn)維護(hù)的,我們?cè)谲浖_發(fā)過程中通常不需特殊去處理它。但是,產(chǎn)品的有些業(yè)務(wù)需求對(duì)于頁面跳轉(zhuǎn)路徑會(huì)有相關(guān)要求,比如某種情況直接返回首頁?;蛘唔撁鍭—>B—>C,C中要求直接返回頁面A,這些需求相信每一位客戶端開發(fā)者都有遇到,對(duì)于Android,通常是借助Activity的啟動(dòng)模式或者Intent_Flag加以實(shí)現(xiàn)。

同樣的,WebView同樣使用棧來維護(hù)加載過的路徑,每打開一個(gè)鏈接,會(huì)進(jìn)行一次入棧操作,我們通常在對(duì)WebView頁面返回時(shí),會(huì)判斷是否包含歷史路徑,存在歷史路徑返回歷史加載路徑操作(出棧操作)。

Android代碼如下:

@Override
public void onBackPressed() {
    if (mWebView.canGoBack()) {
        mWebView.goBack();
        return;
    }
    super.onBackPressed();
}

我們知道,產(chǎn)品需求是不區(qū)分移動(dòng)端和h5的,h5作為內(nèi)嵌app內(nèi)的一部分,也面臨著同樣業(yè)務(wù)路徑跳轉(zhuǎn)的要求。而對(duì)于內(nèi)嵌的h5來說,只對(duì)應(yīng)我們Android端的一個(gè)WebView頁面,更加難以實(shí)現(xiàn)產(chǎn)品的需求,在當(dāng)前條件下App內(nèi)不具有處理這類問題能力,產(chǎn)品不得不在需求上做了某些妥協(xié)。所以這同樣值得引發(fā)我們思考。

3. 編碼影響

這點(diǎn)在產(chǎn)品層面看可能無關(guān)痛癢,但是對(duì)開發(fā)人員來說卻不得不說是個(gè)災(zāi)難。災(zāi)難在哪里?我們都知道,做軟件開發(fā),對(duì)外暴露接口應(yīng)該盡量簡單、明了。要讓團(tuán)隊(duì)其他人員方便調(diào)用不會(huì)出現(xiàn)歧義,減少開發(fā)維護(hù)成本。但是如果titlebar使用原生和h5的不確定性,title是業(yè)務(wù)控制還是h5控制不確定性,app提供的api是這樣的:

   public class WebIntentHelper {
       
       private WebIntentHelper() {
           throw new IllegalStateException("不能初始化");
       }

       /**
        * 開啟一個(gè)內(nèi)嵌web頁面,默認(rèn)包含titlebar
        *
        * @param context 上下文
        * @param url     h5鏈接
        */
       public static void startWeb(Context context, String url) {
           startWeb(context, url, true);
       }

       /**
        * 開啟一個(gè)內(nèi)嵌web頁面
        *
        * @param context     上下文
        * @param url         h5鏈接
        * @param enableTitle 是否有標(biāo)題
        */
       public static void startWeb(Context context, String url, boolean enableTitle) {
           startWeb(context, url, enableTitle, BaseWebActivity.class);
       }

       /**
        * 開啟一個(gè)內(nèi)置web頁面,使用本地titlebar,傳入的title作為標(biāo)題
        *
        * @param context 上下文
        * @param url     h5鏈接
        * @param title   h5顯示的標(biāo)題
        */
       public static void startWeb(Context context, String url, String title) {
           startWeb(context, url, title, BaseWebActivity.class);
       }


       /**
        * 開啟一個(gè)內(nèi)嵌web頁面,默認(rèn)包含titlebar
        *
        * @param context 上下文
        * @param url     h5鏈接
        * @param clz     繼承BaseWebActivity的頁面 Class
        */
       public static void startWeb(Context context, String url, Class<? extends BaseWebActivity> clz) {
           startWeb(context, url, true, clz);
       }

       /**
        * 開啟一個(gè)內(nèi)嵌web頁面
        *
        * @param context     上下文
        * @param url         h5鏈接
        * @param enableTitle 是否有標(biāo)題
        * @param clz         繼承BaseWebActivity的頁面 Class
        */
       public static void startWeb(Context context, String url, boolean enableTitle, Class<? extends BaseWebActivity> clz) {
           Intent intent = new Intent(context, clz);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_URL, url);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_NEED_TITLE, enableTitle);
           context.startActivity(intent);
       }


       /**
        * 開啟一個(gè)內(nèi)置web頁面,使用本地titlebar,傳入的title作為標(biāo)題
        *
        * @param context 上下文
        * @param url     h5鏈接
        * @param title   h5顯示的標(biāo)題
        */
       public static void startWeb(Context context, String url, String title, Class<? extends BaseWebActivity> clz) {
           Intent intent = new Intent(context, clz);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_URL, url);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_NEED_TITLE, true);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_TITLE, title);
           context.startActivity(intent);
       }
}

是不是感覺還好?只有有一點(diǎn)小亂?好吧,那我可以告訴你,這還是在不考慮大數(shù)據(jù)埋點(diǎn)的情況下提供的Api,如果當(dāng)初再把大數(shù)據(jù)埋點(diǎn)添加進(jìn)來會(huì)多少呢?答案很簡單,當(dāng)前數(shù)量 * 2。如果再填入其他的東西呢?那么答案還是很簡單,再用當(dāng)前 數(shù)量 * 2。面對(duì)這樣的api,你還會(huì)去想調(diào)用它么?

三.What to do?

前面問題已經(jīng)拋出,現(xiàn)在我們應(yīng)該埋坑了。如何解決上面這些問題,其實(shí)很簡單,我們只需要理出h5和native的相互關(guān)系就可以想出有效的解決辦法。h5作為展現(xiàn)在用戶面前的頁面,承載著用戶的交互與業(yè)務(wù)需求,無疑在體系內(nèi)起著主導(dǎo)作用。而native客戶端呢?客戶端作為h5的一個(gè)載體,在體系內(nèi)起輔導(dǎo)作用??梢岳斫鉃榭蛻舳酥粸閔5提供一些公共功能,不做任何業(yè)務(wù)邏輯操作,而所有業(yè)務(wù)交互,邏輯控制需要h5頁面自己去處理。這樣才可以支持起更強(qiáng)大的業(yè)務(wù)功能。具體做法如下:

  1. 對(duì)于titlebar(或其他布局),無特殊需求建議全部使用客戶端的布局??蛻舳讼騢5提供設(shè)置標(biāo)題,設(shè)置標(biāo)題右側(cè)菜單等的功能。h5頁面根據(jù)當(dāng)前承擔(dān)的業(yè)務(wù)功能動(dòng)態(tài)的去跟原生通信,指定當(dāng)前titlebar(或其他布局)狀態(tài)。
  2. 當(dāng)原生titlebar(或其他布局)有任何事件觸發(fā),將事件消息轉(zhuǎn)發(fā)給h5頁面,h5頁面接收到消息根據(jù)業(yè)務(wù)做具體操作。包含titlebar右側(cè)菜單按鈕,titlebar左側(cè)的返回按鈕(或android設(shè)備的物理返回鍵)。
先看一下下面2個(gè)場景解決方式:
1. h5頁面控制狀態(tài)欄菜單圖示如下:
顯示菜單
2. h5頁面返回流程控制圖示如下:
客戶端返回邏輯
可以看出,解決問題的核心就在于h5和native的通信,客戶端作為h5的載體,不負(fù)責(zé)邏輯處理,將所有事交由h5去處理。而h5頁面作為業(yè)務(wù)承載方,除了負(fù)責(zé)頁面內(nèi)的業(yè)務(wù)邏輯,還要做的就是控制客戶端的視圖狀態(tài)。
四.關(guān)于jsbridge

如何進(jìn)行h5和native的通信呢?答案是使用jsbridge。關(guān)于jsbridge的實(shí)現(xiàn)在這里不做詳細(xì)闡述,網(wǎng)上有很多文章有詳細(xì)介紹。這里只提以下幾點(diǎn):1. jsbridge庫設(shè)計(jì)根本在于方便本地與h5進(jìn)行通信,所以具體協(xié)議要根據(jù)公司具體情況去制訂。

  1. 設(shè)計(jì)jsbridge時(shí)候要考慮解耦,在api盡量方便使用同時(shí),不要摻雜業(yè)務(wù)邏輯。
  2. 如果公司項(xiàng)目已經(jīng)組件化,設(shè)計(jì)jsbridge庫要考慮組件化,盡量使jsbridge具有更加強(qiáng)大的業(yè)務(wù)支撐能力。
五.總結(jié)

?好了,就到這里了,本篇文章并沒有向你闡述很牛逼的技術(shù),只是作者個(gè)人最近經(jīng)常跟app的h5頁面打交道所引發(fā)的一些小的思考,如果對(duì)你有所幫助,歡迎你的點(diǎn)贊,也希望得到更多的想法交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,765評(píng)論 25 709
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 13,913評(píng)論 2 59
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,621評(píng)論 1 32
  • 從小就喜歡武俠片,尤其是徐克的,一刀一劍、一景一物、一惡一善,江湖情仇盡顯,好不快哉。恨自己不是生在古代,不然就可...
    崇德先生閱讀 843評(píng)論 1 5
  • 2017.03.08 老婆你此身眼不濁, 萬綠叢中選中我。 共度一生時(shí)相伴, ...
    8b7535d983e2閱讀 496評(píng)論 1 1

友情鏈接更多精彩內(nèi)容