1. 程式人生 > >Hybrid開發-----H5與原生App互動

Hybrid開發-----H5與原生App互動

http://www.cnblogs.com/yexiaochai/p/5813248.html

http://www.cnblogs.com/nildog/p/5536081.html

Hybrid開發效率高、跨平臺、低層本
Hybrid從業務開發上講,沒有版本問題,有BUG能及時修復

Hybrid是有缺點的,Hybrid體驗就肯定比不上Native,所以使用有其場景,但是對於需要快速試錯、快速佔領市場的團隊來說,Hybrid一定是不二的選擇,團隊生存下來後還是需要做體驗更好的原生APP


① Hybrid中Native與前端各自的工作是什麼

② Hybrid的互動介面如何設計

③ Hybrid的Header如何設計

④ Hybrid的如何設計目錄結構以及增量機制如何實現

 資源快取策略,白屏問題......

靜態資源打包至Native中,Native提供js呼叫原生應用的能力,從產品化和工程化來說做的很不錯,但是有兩個瑕疵:

① 資源全部打包至Naive中APP尺寸會增大,就算以增量機制也避免不了APP的膨脹,因為現在接入的頻道較少一個頻道500K沒有感覺,一旦平臺化後主APP尺寸會急劇增大

② 糯米前端框架團隊封裝了Native端的能力,但是沒有提供配套的前端框架,這個解決方案是不完整的。很多業務已經有H5站點了,為了接入還得單獨開發一套程式;而就算是新業務接入,又會面臨嵌入資源必須是靜態資源的限制,做出來的專案沒有SEO,如果關注SEO的話還是需要再開發,從工程角度來說是有問題的。

Native與前端分工

首先Native提供的是一宿主環境,要合理的利用Native提供的能力,要實現通用的Hybrid平臺架構,站在前端視角,我認為需要考慮以下核心設計問題。

互動設計

Hybrid架構設計第一個要考慮的問題是如何設計與前端的互動,如果這塊設計的不好會對後續開發、前端框架維護造成深遠的影響,並且這種影響往往是不可逆的,所以這裡需要前端與Native好好配合,提供通用的介面,比如:

① NativeUI元件,header元件、訊息類元件

② 通訊錄、系統、裝置資訊讀取介面

③ H5與Native的互相跳轉,比如H5如何跳到一個Native頁面,H5如何新開Webview做動畫跳到另一個H5頁面

資源訪問機制

Native首先需要考慮如何訪問H5資源,做到既能以file的方式訪問Native內部資源,又能使用url的方式訪問線上資源;需要提供前端資源增量替換機制,以擺脫APP迭代發版問題,避免使用者升級APP。這裡就會涉及到靜態資源在APP中的存放策略,更新策略的設計,複雜的話還會涉及到伺服器端的支援。

賬號資訊設計

賬號系統是重要並且無法避免的,Native需要設計良好安全的身份驗證機制,保證這塊對業務開發者足夠透明,打通賬戶資訊

Hybrid開發除錯

功能設計完並不是結束,Native與前端需要商量出一套可開發除錯的模型,不然很多業務開發的工作將難以繼續,這個很多文章已經接受過了,本文不贅述。

至於Native還會關注的一些通訊設計、併發設計、異常處理、日誌監控以及安全模組因為不是我涉及的領域便不予關注了(事實上是想關注不得其門),而前端要做的事情就是封裝Native提供的各種能力,整體架構是這樣的:

真實業務開發時,Native除了會關注登入模組之外還會封裝支付等重要模組,這裡視業務而定。

Hybrid互動設計

Hybrid的互動無非是Native呼叫前端頁面的JS方法,或者前端頁面通過JS呼叫Native提供的介面,兩者互動的橋樑皆Webview:

app自身可以自定義url schema,並且把自定義的url註冊在排程中心, 例如

  • ctrip://wireless 開啟攜程App
  • weixin:// 開啟微信

我們JS與Native通訊一般就是建立這類URL被Native捕獲處理,後續也出現了其它前端呼叫Native的方式,但可以做底層封裝使其透明化,所以重點以及是如何進行前端與Native的互動設計。

JS to Native

Native在每個版本會提供一些API,前端會有一個對應的框架團隊對其進行封裝,釋放業務介面。

前端框架定義了一個全域性變數BNJS作為Native與前端互動的物件,只要引入了糯米提供的這個JS庫,並且在糯米封裝的Webview容器中,前端便獲得了呼叫Native的能力,我揣測糯米這種設計是因為這樣便於第三方團隊的接入使用,手機百度有一款輕應用框架也走的這種路線:

clouda.mbaas.account //釋放了clouda全域性變數

API式互動

手白、糯米底層如何做我們無從得知,但我們發現呼叫Native API介面的方式和我們使用AJAX呼叫伺服器端提供的介面是及其相似的:

這裡類似的微薄開放平臺的介面是這樣定義的:

讀取介面接收訊息接收使用者私信、關注、取消關注、@等訊息介面 
寫入介面傳送訊息向用戶回覆私信訊息介面 

我們要做的就是通過一種方式建立ajax請求即可:

https://api.weibo.com/2/statuses/public_timeline.json

所以我在實際設計Hybrid互動模型時,是以介面為單位進行設計的,比如獲取通訊錄的總體互動是:

格式約定

所以我這邊與Native約定的請求模型是:

複製程式碼
requestHybrid({
  //建立一個新的webview對話方塊視窗
  tagname: 'hybridapi',
  //請求引數,會被Native使用
  param: {},
  //Native處理成功後回撥前端的方法
  callback: function (data) {
  }
});
複製程式碼

這個方法執行會形成一個URL,比如:

hybridschema://hybridapi?callback=hybrid_1446276509894&param=%7B%22data1%22%3A1%2C%22data2%22%3A2%7D

這裡提一點,APP安裝後會在手機上註冊一個schema,比如淘寶是taobao://,Native會有一個程序監控Webview發出的所有schema://請求,然後分發到“控制器”hybridapi處理程式,Native控制器處理時會需要param提供的引數(encode過),處理結束後將攜帶資料獲取Webview window物件中的callback(hybrid_1446276509894)呼叫之

資料返回的格式約定是:

{
  data: {},
  errno: 0,
  msg: "success"
}

真實的資料在data物件中,如果errno不為0的話,便需要提示msg,這裡舉個例子如果錯誤碼1代表該介面需要升級app才能使用的話:

{
  data: {},
  errno: 1,
  msg: "APP版本過低,請升級APP版本"
}

程式碼實現

這裡給一個簡單的程式碼實現,真實程式碼在APP中會有所變化:

複製程式碼
 1 window.Hybrid = window.Hybrid || {};
 2 var bridgePostMsg = function (url) {
 3     if ($.os.ios) {
 4         window.location = url;
 5     } else {
 6         var ifr = $('<iframe style="display: none;" src="' + url + '"/>');
 7         $('body').append(ifr);
 8         setTimeout(function () {
 9             ifr.remove();
10         }, 1000)
11     }
12 };
13 var _getHybridUrl = function (params) {
14     var k, paramStr = '', url = 'scheme://';
15     url += params.tagname + '?t=' + new Date().getTime(); //時間戳,防止url不起效
16     if (params.callback) {
17         url += '&callback=' + params.callback;
18         delete params.callback;
19     }
20     if (params.param) {
21         paramStr = typeof params.param == 'object' ? JSON.stringify(params.param) : params.param;
22         url += '&param=' + encodeURIComponent(paramStr);
23     }
24     return url;
25 };
26 var requestHybrid = function (params) {
27     //生成唯一執行函式,執行後銷燬
28     var tt = (new Date().getTime());
29     var t = 'hybrid_' + tt;
30     var tmpFn;
31 
32     //處理有回撥的情況
33     if (params.callback) {
34         tmpFn = params.callback;
35         params.callback = t;
36         window.Hybrid[t] = function (data) {
37             tmpFn(data);
38             delete window.Hybrid[t];
39         }
40     }
41     bridgePostMsg(_getHybridUrl(params));
42 };
43 //獲取版本資訊,約定APP的navigator.userAgent版本包含版本資訊:scheme/xx.xx.xx
44 var getHybridInfo = function () {
45     var platform_version = {};
46     var na = navigator.userAgent;
47     var info = na.match(/scheme\/\d\.\d\.\d/);
48 
49     if (info && info[0]) {
50         info = info[0].split('/');
51         if (info && info.length == 2) {
52             platform_version.platform = info[0];
53             platform_version.version = info[1];
54         }
55     }
56     return platform_version;
57 };
複製程式碼

因為Native對於H5來是底層,框架&底層一般來說是不會關注業務實現的,所以真實業務中Native呼叫H5場景較少,這裡不予關注了。

常用互動API

良好的互動設計是成功的第一步,在真實業務開發中有一些API一定會用到。

跳轉

跳轉是Hybrid必用API之一,對前端來說有以下跳轉:

① 頁面內跳轉,與Hybrid無關

② H5跳轉Native介面

③ H5新開Webview跳轉H5頁面,一般為做頁面動畫切換

如果要使用動畫,按業務來說有向前與向後兩種,forward&back,所以約定如下,首先是H5跳Native某一個頁面

複製程式碼
 1 //H5跳Native頁面
 2 //=>baidubus://forward?t=1446297487682&param=%7B%22topage%22%3A%22home%22%2C%22type%22%3A%22h2n%22%2C%22data2%22%3A2%7D
 3 requestHybrid({
 4     tagname: 'forward',
 5     param: {
 6         //要去到的頁面
 7         topage: 'home',
 8         //跳轉方式,H5跳Native
 9         type: 'native',
10         //其它引數
11         data2: 2
12     }
13 });
複製程式碼

比如攜程H5頁面要去到酒店Native某一個頁面可以這樣:

複製程式碼
 1 //=>schema://forward?t=1446297653344&param=%7B%22topage%22%3A%22hotel%2Fdetail%20%20%22%2C%22type%22%3A%22h2n%22%2C%22id%22%3A20151031%7D
 2 requestHybrid({
 3     tagname: 'forward',
 4     param: {
 5         //要去到的頁面
 6         topage: 'hotel/detail',
 7         //跳轉方式,H5跳Native
 8         type: 'native',
 9         //其它引數
10         id: 20151031
11     }
12 });
複製程式碼

比如H5新開Webview的方式跳轉H5頁面便可以這樣:

複製程式碼
 1 requestHybrid({
 2     tagname: 'forward',
 3     param: {
 4         //要去到的頁面,首先找到hotel頻道,然後定位到detail模組
 5         topage: 'hotel/detail  ',
 6         //跳轉方式,H5新開Webview跳轉,最後裝載H5頁面
 7         type: 'webview',
 8         //其它引數
 9         id: 20151031
10     }
11 });
複製程式碼

back與forward一致,我們甚至會有animattype引數決定切換頁面時的動畫效果,真實使用時可能會封裝全域性方法略去tagname的細節,這時就和糯米對外釋放的介面差不多了。

Header 元件的設計

最初我其實是抵制使用Native提供的UI元件的,尤其是Header,因為平臺化後,Native每次改動都很慎重並且響應很慢,但是出於兩點核心因素考慮,我基本放棄了抵抗:

① 其它主流容器都是這麼做的,比如微信、手機百度、攜程

② 沒有header一旦網路出錯出現白屏,APP將陷入假死狀態,這是不可接受的,而一般的解決方案都太業務了

PS:Native吊起Native時,如果300ms沒有響應需要出loading元件,避免白屏

因為H5站點本來就有Header元件,站在前端框架層來說,需要確保業務的程式碼是一致的,所有的差異需要在框架層做到透明化,簡單來說Header的設計需要遵循:

① H5 header元件與Native提供的header元件使用呼叫層介面一致

② 前端框架層根據環境判斷選擇應該使用H5的header元件抑或Native的header元件

一般來說header元件需要完成以下功能:

① header左側與右側可配置,顯示為文字或者圖示(這裡要求header實現主流圖示,並且也可由業務控制圖示),並需要控制其點選回撥

② header的title可設定為單標題或者主標題、子標題型別,並且可配置lefticon與righticon(icon居中)

③ 滿足一些特殊配置,比如標籤類header

所以,站在前端業務方來說,header的使用方式為(其中tagname是不允許重複的):

//Native以及前端框架會對特殊tagname的標識做預設回撥,如果未註冊callback,或者點選回撥callback無返回則執行預設方法
 2 // back前端預設執行History.back,如果不可後退則回到指定URL,Native如果檢測到不可後退則返回Naive大首頁
 3 // home前端預設返回指定URL,Native預設返回大首頁
 4 this.header.set({
 5     left: [
 6         {
 7             //如果出現value欄位,則預設不使用icon
 8             tagname: 'back',
 9             value: '回退',
10             //如果設定了lefticon或者righticon,則顯示icon
11             //native會提供常用圖示icon對映,如果找不到,便會去當前業務頻道專用目錄獲取圖示
12             lefticon: 'back',
13             callback: function () { }
14         }
15     ],
16     right: [
17         {
18             //預設icon為tagname,這裡為icon
19             tagname: 'search',
20             callback: function () { }
21         },
22     //自定義圖示
23         {
24         tagname: 'me',
25         //會去hotel頻道儲存靜態header圖示資源目錄搜尋該圖示,沒有便使用預設圖示
26         icon: 'hotel/me.png',
27         callback: function () { }
28     }
29     ],
30     title: 'title',
31     //顯示主標題,子標題的場景
32     title: ['title', 'subtitle'],
33 
34     //定製化title
35     title: {
36         value: 'title',
37         //標題右邊圖示
38         righticon: 'down', //也可以設定lefticon
39         //標題型別,預設為空,設定的話需要特殊處理
40         //type: 'tabs',
41         //點選標題時的回撥,預設為空
42         callback: function () { }
43     }
44 });

為完成Native端的實現,這裡會新增兩個介面,向Native註冊事件,以及登出事件:

複製程式碼
1 var registerHybridCallback = function (ns, name, callback) {
2   if(!window.Hybrid[ns]) window.Hybrid[ns] = {};
3   window.Hybrid[ns][name] = callback;
4 };
5 
6 var unRegisterHybridCallback = function (ns) {
7   if(!window.Hybrid[ns]) return;
8   delete window.Hybrid[ns];
9 };
複製程式碼

請求類

雖然get類請求可以用jsonp的方式繞過跨域問題,但是post請求卻是真正的攔路虎,為了安全性伺服器設定cors會僅僅針對幾個域名,Hybrid內嵌靜態資源是通過file的方式讀取,這種場景使用cors就不好使了,所以每個請求需要經過Native做一層代理髮出去。

這個使用場景與Header元件一致,前端框架層必須做到對業務透明化,業務事實上不必關心這個請求是由瀏覽器發出還是由Native發出:

1 HybridGet = function (url, param, callback) {
2 };
3 HybridPost = function (url, param, callback) {
4 };

真實的業務場景,會將之封裝到資料請求模組,在底層做適配,在H5站點下使用ajax請求,在Native內嵌時使用代理髮出,與Native的約定為

複製程式碼
 1 requestHybrid({
 2     tagname: 'ajax',
 3     param: {
 4         url: 'hotel/detail',
 5         param: {},
 6         //預設為get
 7         type: 'post'
 8     },
 9     //響應後的回撥
10     callback: function (data) { }
11 });
複製程式碼

常用NativeUI元件

最後,Native會提供幾個常用的Native級別的UI,比如loading載入層,比如toast訊息框:

複製程式碼
 1 var HybridUI = {};
 2 HybridUI.showLoading();
 3 //=>
 4 requestHybrid({
 5     tagname: 'showLoading'
 6 });
 7 
 8 HybridUI.showToast({
 9     title: '111',
10     //幾秒後自動關閉提示框,-1需要點選才會關閉
11     hidesec: 3,
12     //彈出層關閉時的回撥
13     callback: function () { }
14 });
15 //=>
16 requestHybrid({
17     tagname: 'showToast',
18     param: {
19         title: '111',
20         hidesec: 3,
21         callback: function () { }
22     }
23 });
複製程式碼

Native UI與前端UI不容易打通,所以在真實業務開發過程中,一般只會使用幾個關鍵的Native UI。

賬號系統的設計

根據上面的設計,我們約定在Hybrid中請求有兩種發出方式:

① 如果是webview訪問線上站點的話,直接使用傳統ajax發出

② 如果是file的形式讀取Native本地資源的話,請求由Native代理髮出

因為靜態html資源沒有鑑權的問題,真正的許可權驗證需要請求伺服器api響