Hybrid應(yīng)用開發(fā)初探

Hybrid開發(fā)定義和使用范圍

為什么要采用hybrid:

現(xiàn)階段的應(yīng)用開發(fā),會遇到如下問題和挑戰(zhàn):
1 一些頁面或業(yè)務(wù),和運營強相關(guān),無法native固定(例如電子商務(wù) 詳情展示)
2 客戶端發(fā)版周期長,一些需求想要很快上線,或變化非常頻繁

現(xiàn)有3類主流APP,分別為:Web App、Hybrid App(混合模式應(yīng)用,Hybrid有“混合的”意思)、 Native App;

Native App 和 Web App不作解釋了,主要解釋Hybrid App。
Hybrid App按網(wǎng)頁語言與程序語言的混合,通常分為三種類型:多View混合型,單View混合型,Web主體型。

單頁混合型

即在同一個頁面內(nèi),同時包括Native View和Web View?;ハ嘀g是覆蓋(層疊)的關(guān)系。這種Hybrid App的開發(fā)成本較高,開發(fā)難度較大,但是體驗較好。如百度搜索為代表的單頁混合型移動應(yīng)用,既可以實現(xiàn)充分的靈活性,又能實現(xiàn)較好的用戶體驗。一般如無特殊需求,不會采用此種方式。

Web主體型

這種常見于市面上第三方hybrid框架實現(xiàn)。例如Wex5,AppCan和Rexsee都屬于Web主體型移動應(yīng)用中間件。基本可以實現(xiàn)跨平臺,主要以網(wǎng)頁語言編寫,利用框架生成native的殼子。但是一般用戶體驗存在缺陷。常見于一些小型或功能單一app。

多主體混合型

即Native View和Web View獨立展示,交替出現(xiàn)。這種應(yīng)用混合邏輯相對簡單。這種移動應(yīng)用主體通常是Native App,Web技術(shù)只是起到補充作用。開發(fā)難度和Native App基本相當(dāng)。常見的Hybrid App是Native View與WebView交替的場景出現(xiàn)。

與App內(nèi)接入H5的區(qū)別:

hybrid的開發(fā)模式與我們之前一些運營頁面采用h5的根本區(qū)別在于,后者只是在一些不重要的功能上實現(xiàn)可運營和便于分享,并不接入到應(yīng)用的主要流程中,與native的交互較少,對應(yīng)用的影響小,作為開發(fā)的一個小模塊獨立存在。hybrid開發(fā)則是將web頁面作為native的重要補充,應(yīng)用功能的重要組成部分,需要考慮上線節(jié)奏,web與native的通訊,優(yōu)化web體驗等問題,對于應(yīng)用來講,web與native的地位,被大大拉平了。

如何區(qū)分Hybrid APP中的原生頁面和H5頁面

很多人從頁面的設(shè)計上來區(qū)分的。如:(1)頂部顯示網(wǎng)頁鏈接;(2)有加載的進度條;(3)沒有底部tab導(dǎo)航欄;(4)頂部顯示兩個導(dǎo)航條;
但是現(xiàn)在app的h5頁面做的可以以假亂真了,這些統(tǒng)統(tǒng)不管用。
以淘寶為例:


設(shè)置-開發(fā)者選項-顯示布局邊界

H5中使用了webview控件,其作為一個控件,只有一個邊界框,所以通過這一點,就比較容易區(qū)分出一個界面是webview實現(xiàn)的還是原生布局控件實現(xiàn)的
這次再來看看:

幾個主流HybridApp:淘寶、京東、大眾點評等

Hybrid中Native和H5的使用范圍:

Native
1 應(yīng)用核心邏輯:例如 下單、支付等
2 對手機native功能(如照相、定位)重度依賴的頁面
3 用戶體驗要求強,運營要求弱的頁面
H5:
1.功能開發(fā)不完善,試運營階段(試錯成本低)
2.強運營需求,在功能調(diào)整或內(nèi)容的運營上很靈活
3.階段性的營銷活動,希望被分享出去

Hybrid開發(fā)中要解決的幾個問題

一、H5 和 Native 上線時間不一致,如何銜接?
二、H5 和 Native 之間如何進行通信?
三、H5 頁面如何接近 Native 的體驗?
針對幾個問題,參考了美團團隊技術(shù)分享的解決方案,同時根據(jù)自己的理解做了適當(dāng)?shù)臄U展:

1. H5 和 Native 上線時間不一致,如何銜接?

比如一個功能以H5形式作出,但H5的發(fā)布滯后于native,當(dāng)H5上線之后,客戶端需要給H5提供一些跳轉(zhuǎn)的入口,這個跳轉(zhuǎn)的入口提供的應(yīng)該是在不發(fā)版的情況下去給出的。
這就需要對路由的跳轉(zhuǎn)做到后臺的可配置。
現(xiàn)階段的跳轉(zhuǎn):(Native 到 Native)
這種組件化的全局統(tǒng)跳協(xié)議,利用ARouter、天貓統(tǒng)跳協(xié)議等其他路由機制,都可以實現(xiàn)。



對這個跳轉(zhuǎn)去做一些擴展:對路由協(xié)議擴展后,讓他能支持跳轉(zhuǎn)到H5里。如下圖:



通過后臺動態(tài)決定一個頁面,究竟是native還是h5的展現(xiàn)形式。
舉個例子:
在APP里一個購物下單的流程,用戶需要訪問列表頁,商家的詳情頁,創(chuàng)建訂單,最后購買成功。對一些新的產(chǎn)品,有新的產(chǎn)品詳情和創(chuàng)建訂單樣式。可以通過h5上線的方式:

可以看到流程的兩端都是native,中間環(huán)節(jié)從native到h5可以動態(tài)切換
備注:這些路由配置,是實際需求的少數(shù),作為主體方案的有效補充存在。

2. H5 和 Native 如何進行通信?
傳統(tǒng)的JSInterface(兼容性)

看一段html代碼

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" dir="ltr">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

    <script type="text/javascript">
        function showToast(toast) {
            javascript:control.showToast(toast);
        }
        function log(msg){
            console.log(msg);
        }
    </script>

</head>

<body>
<input type="button" value="toast"
       onClick="showToast('Hello world')" />
</body>
</html>

對應(yīng)的java代碼:

public class MainActivity extends AppCompatActivity {

    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        webView = (WebView)findViewById(R.id.webView);

        WebSettings webSettings = webView.getSettings();

        webSettings.setJavaScriptEnabled(true);

        webView.addJavascriptInterface(new JsInterface(), "control");

        webView.loadUrl("file:///android_asset/interact.html");
    }

    public class JsInterface {

        @JavascriptInterface
        public void showToast(String toast) {
            Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
            log("show toast success");
        }

        public void log(final String msg){
            webView.post(new Runnable() {
                @Override
                public void run() {
                    webView.loadUrl("javascript: log(" + "'" + msg + "'" + ")");
                }
            });
        }
    }
}

通過webView.addJavascriptInterface(new JsInterface(), "control"),將js的control與native的JsInterface聯(lián)系起來,實現(xiàn)了js向native的調(diào)用。反過來,webView.loadUrl("javascript: log(" + "'" + msg + "'" + ")"),loadUrl調(diào)用到j(luò)s中定義的log方法,實現(xiàn)了native到j(luò)s的回調(diào)。

但是,,,
4.2版本之前的addjavascriptInterface接口引起的漏洞,可能導(dǎo)致惡意網(wǎng)頁通過Js方法遍歷剛剛通過addjavascriptInterface注入進來的類的所有方法從中獲取到getClass方法,然后通過反射獲取到Runtime對象,進而調(diào)用Runtime對象的exec方法執(zhí)行一些操作,惡意的Js代碼如下:

function execute(cmdArgs) {
    for (var obj in window) {
        if ("getClass" in window[obj]) {
            alert(obj);
            return  window[obj].getClass().forName("java.lang.Runtime")  
                 .getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
        }
    }
}

4.2以后通過為可以被Js調(diào)用的方法添加@JavascriptInterface注解來解決,但是4.2之前的版本兼容性存在問題。而且這種類似于函數(shù)式的調(diào)用方式,擴展性和兩端的兼容性都受限,所以他也就沒法廣泛采用了。

UrlRouter

嚴(yán)格的說,UrlRouter不算是js和java的通信,它只是一個通過url來讓前端喚起native頁面的框架。不過千萬不要小看它的作用,如果協(xié)議定義的合理,它可以讓前端,Android和iOS三端有一個高度的統(tǒng)一,十分方便。

public class NavWebViewClient extends WebViewClient {

    private Context context;

    public NavWebViewClient(Context context){
        this.context = context;
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if( Nav.from(context).toUri(url)){
            return true;
        }

        view.loadUrl(url);
        return true;
    }
}

在方法shouldOverrideUrlLoading中,攔截后交給Nav處理,如果返回true則成功攔截,返回false則交給webview去load url。Nav中的解析處理,可以根據(jù)業(yè)務(wù)特點,根據(jù)scheme host url地址解析出跳轉(zhuǎn)路徑和攜帶的參數(shù)。
關(guān)于攜帶參數(shù),再多說兩句:h5與native要約定傳參的格式,比如json格式,那么在json字串里約定好字段的含義,就可以傳參,比如要實現(xiàn)跳轉(zhuǎn)到指定頁面,并攜帶參數(shù):

{"p": "orderlist","pa": {"tp": "per"}}

字段p代碼代碼頁面,字段pa代表參數(shù),pa字段后面的json表示此頁面需要的具體傳參。要注意傳參部分要進行加密處理。

JSBridge

這種方式不算新,一些大公司都有自己的jsBridge封裝方式,這里簡要說明一下基本原理。
WebView中有一個WebChromeClient類,有三個監(jiān)聽函數(shù):

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
    return super.onJsPrompt(view, url, message, defaultValue, result);
}

@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
    return super.onJsAlert(view, url, message, result);
}

@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
    return super.onJsConfirm(view, url, message, result);
}

在js中,alert和confirm本身的使用概率還是很高的,不建議使用這兩個通道,onJsPrompt方法則可以用來js與java通信。通過在回調(diào)函數(shù)中message參數(shù)傳遞通訊協(xié)議,native根據(jù)協(xié)議解析決定自己的操作。

onJsPrompt方法中message參數(shù):hybrid://JSBridge:1538351/method?{“message”:”msg”}

sheme是hybrid://,host是JSBridge,方法名字是toast,傳遞的參數(shù)是以json格式傳遞的
java層的處理:

public class InjectedChromeClient extends WebChromeClient {
    private final String TAG = "InjectedChromeClient";

    private JsCallJava mJsCallJava;

    public InjectedChromeClient() {
        mJsCallJava = new JsCallJava();
    }

    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        result.confirm(mJsCallJava.call(view, message));
        return true;
    }
}

核心的call方法做了哪些?

public String call(WebView webView, String jsonStr) {
    String methodName = "";
    String name = BRIDGE_NAME;
    String param = "{}";
    String result = "";
    String sid="";
    if (!TextUtils.isEmpty(jsonStr) && jsonStr.startsWith(SCHEME)) {
        Uri uri = Uri.parse(jsonStr);
        name = uri.getHost();
        param = uri.getQuery();
        sid = getPort(jsonStr);
        String path = uri.getPath();
        if (!TextUtils.isEmpty(path)) {
            methodName = path.replace("/", "");
        }
    }

    if (!TextUtils.isEmpty(jsonStr)) {
        try {
            ArrayMap<String, Method> methodMap = mInjectNameMethods.get(name);

            Object[] values = new Object[3];
            values[0] = webView;
            values[1] = new JSONObject(param);
            values[2]=new JsCallback(webView,sid);
            Method currMethod = null;
            if (null != methodMap && !TextUtils.isEmpty(methodName)) {
                currMethod = methodMap.get(methodName);
            }
            // 方法匹配失敗
            if (currMethod == null) {
                result = getReturn(jsonStr, RESULT_FAIL, "not found method(" + methodName + ") with valid parameters");
            }else{
                result = getReturn(jsonStr, RESULT_SUCCESS, currMethod.invoke(null, values));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    } else {
        result = getReturn(jsonStr, RESULT_FAIL, "call data empty");
    }

    return result;
}

代碼的思路如下:
(1) 在js腳本中把對應(yīng)的方法名,參數(shù)等寫成一個符合協(xié)議的uri,并且通過window.prompt方法發(fā)送給java層。

(2) 在java層的onJsPrompt方法中接受到對應(yīng)的message之后,通過JsCallJava類進行具體的解析。

(3) 在JsCallJava類中,我們解析得到對應(yīng)的方法名,參數(shù)等信息,并且在map中查找出對應(yīng)的類的方法。

思考:為什么不對message中的字段進行switch case的邏輯判斷,而是要經(jīng)過mInjectNameMethods的遍歷呢?

在業(yè)務(wù)復(fù)雜,應(yīng)用已經(jīng)組件化的情況下,JSBridge一定是作為整體架構(gòu)的一部分存在的,那么其定義和使用可能是分離的,通過mInjectNameMethods遍歷的方法,JSBridge中定義方法的權(quán)利交給了業(yè)務(wù)部門,有效實現(xiàn)了解耦。

可以這么說UrlRouter在頁面跳轉(zhuǎn)方面,JSBridge在方法調(diào)用方面,都具備各自的特點和優(yōu)勢,可以有效的結(jié)合起來。

3 . H5 頁面如何接近 Native 的體驗?
資源加載緩慢

1.模塊化你的 H5 頁面/應(yīng)用,引入模塊加載器
2.資源預(yù)加載
第一種方式是說使用 WebView 自身的緩存機制
這種緩存,系統(tǒng)會自動把它清掉,我們沒法進行控制
第二種方案是說,我們自己去構(gòu)建,自己管理緩存
把這些需要預(yù)加載的資源放在 APP 里面,他可能是預(yù)制放進去的,也可能是后續(xù)下載的。
每當(dāng)這個 WebView 發(fā)起資源請求的時候,我們會攔截到這些資源的請求,去本地檢查一下我們的這些靜態(tài)資源本地離線包有沒有。針對本地的緩存文件我們有些策略能夠及時的去更新它


資源預(yù)加載效果:

每個頁面在預(yù)加載后都有明顯提升(4G下明顯),同時橫向比較,也可看出,在一系列的web加載過程中,平均時間再降低。也說明了webview自身的緩存機制。
騰訊開源的hybrid框架(實際只是webview首屏優(yōu)化),實踐了webview的優(yōu)化,具體原理可以去github:
https://github.com/Tencent/VasSonic

VasSonic有如下特點(缺點):
1.VasSonic的技術(shù)實現(xiàn)上,需要服務(wù)端、客戶端 同時修改配合;
2.目前sonic后臺僅支持node.js和php版本,暫時還不支持其他后臺;
3.iOS 只支持UIWebView,不支持WKWebView,主要是因為在WKWebView目前不支持NSURLProtocol攔截;
vassonic這套方案,對于現(xiàn)有項目還是有一定侵入性的,而且需要服務(wù)端配合??梢詤⒖计渌悸?,完全照搬對于大項目有風(fēng)險。

最后放一張hybrid客戶端架構(gòu)圖

Zjiyyu.png

H5Container是架構(gòu)設(shè)計的重點和難點,其中nativeApi,HandwareApi都是對于手機對web提供功能的封裝。Data Channel負(fù)責(zé)埋點;JSBridge是處于底層的通訊接口,JSBridges為各個模塊的定制和擴展。
Synchronize Service 模塊表示和服務(wù)器的長連接通信模塊,用于接受服務(wù)器端各種推送,包括離線包等。 Source Merge Service 模塊表示對解壓后的H5資源進行更新,包括增加文件、以舊換新以及刪除過期文件等。

總結(jié):

一般來說Hybrid的項目一般是用在一些快速迭代試錯的地方。另外包括有一些非主流產(chǎn)品的頁面,我們傾向于用 Hybrid 的形式做.
但是像前端購買一些交易環(huán)節(jié),特別核心的流程的話,我們一般情況下會用 Native 的形式去寫這些頁面,去提升,達到一個極致的用戶體驗。不要為了hybrid而hybrid,一切都是根據(jù)需求的實際情況出發(fā),同時hybrid的框架在設(shè)計時,協(xié)議方面要注意ios android兩端的統(tǒng)一,android端自身盡量考慮擴展性和解耦,有利于后續(xù)開發(fā)迭代的穩(wěn)定和迅速。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內(nèi)容