接上文:(閱讀本文前,建議閱讀前三篇文章先)html
以前設計Hybrid整塊交互的時候,受衆都是本身的團隊,沒有想往「公司化」和「平臺化」方向發展,而近期業務的發展逐漸超出預期了,慢慢會有第三方網站接入咱們的APP,並且第三方網站還會用一些Native的能力,這個時候以前的使用彷佛就不太合適了,所謂JS-SDK就須要存在了。ajax
相似這種需求,作的最完善的當屬微信的native容器了,微信這種屬於海量容器,對全部的接入方基本一視同仁,就算內部團隊會有一些「特權」能力,使用方式與第三方接入都是一套體系,可能只是文檔有所不一樣罷了。微信容器中,最經常使用的端能力要屬:後端
① 統一登陸,獲取微信的登陸態&使用微信登陸,微信方給予第三方應用有限的用戶信息(非JS-SDK)api
② 分享接口服務器
③ 微信支付微信
咱們今天以微信(中間可能參考其餘APP平臺)爲範本,思考下咱們本身的容器如何處理第三方的狀況。網絡
咱們作這塊設計以前,首先須要明確一個定位:
咱們的Native容器,應該給第三方網站提供哪些能力?
若是不加限制的提供能力,就屬於「內部」項目了,如微信容器通常,對外提供的能力屈指可數,這裏是咱們一個比較常見的第三方容器:
這個當history過深(不爲1時)後也會產生一個關閉按鈕,直接回到上一個native頁面。
咱們這裏約定,對於第三方網站,這些Native UI咱們都不可定製化(可是能經過釋放接口配置增添菜單項目),甚至風格色彩都是不可定製的,若是哪天咱們的APP風格變了,那麼第三方網站只能適配咱們的APP(雖然APP便總體風格這種事情有點操蛋,可是在中小公司仍是比較常見)
這裏可定製的是:
① 標題展現
② 分享出去的文字、圖片等(這塊參考微信)
③ 不一樣的APP這塊可能會有定製化需求,這塊也最好標準化,不讓產品有過多的瞎想
除了登陸以外,另外一個比較經常使用的服務就是喚起支付(APP中的支付),而後APP公共頁面再引導用戶選擇微信或者支付寶支付。
其實喚起支付屬於H5跳Native(或者Hybrid)的一類,與之相似的有:
① 打開某一個native頁面,比較典型的是在網頁貼吧中打開一個網址若是安裝了貼吧APP,會直接打開那個頁面
② 這裏也有多是打開第三方APP,而打開第三方APP這種功能,對於比較大的APP平臺會作嚴格的限制
由於圖片上傳什麼對H5來講是一個比較麻煩的功能,徹底依賴H5可能體驗很差,因而native方可能會釋放一些圖片操做的接口如:
① H5調用native的選取相冊&圖片接口
② 圖片預覽接口(查看大圖)
③ 上傳圖片
④ 下載圖片
有些比較特別的網站也許還會有獲取當前網絡狀態的接口或者地理位置接口或者調用掃一掃功能(場景較少),而對於UI層面的操做又會包含關閉當前窗口(同點擊關閉按鈕)的接口。
全部的這些功能,咱們最初就應該設想清楚,而且清晰的知道每個接口適用於什麼場景,在能力列表出來後,咱們就要作另外一個事情了:權限限制。
微信使用了一個appid,去限制每個接入方的能力,甚至能夠以收費的形式釋放某些接口,能夠預見,若是微信開放打開第三方App的收費功能,會有不少公司爭相埋單。這種appid的行爲,至關於一種統一收口的動做,雖然接入方千奇百怪,可是全部的接入方若是要使用容器的能力,甚至想在容器中展現,就必須有一個appid。
另外一方面,appid的使用其實成本比較高,前端須要額外的接口訪問不說,平臺方還須要提供一個第三方網站讓第三方網站管理本身的應用,這個對於一些小平臺甚至僞平臺有一些得不償失,咱們來簡單看看一個demo:
1 //第三方網站要用什麼接口,必須先聲明 2 wx.config({ 3 debug: false, 4 appId: 'wxf8b4f85f3a794e77',//服務器端讀出 5 timestamp: 1485169627,//服務器端讀出 6 nonceStr: 'jhGQ4jiN4CQpaGPC',//服務器端讀出 7 signature: 'a19573b7f65427a33a96f2a57a4f40075135a5b4',//服務器端讀出 8 jsApiList: [ 9 'onMenuShareTimeline', 10 'onMenuShareAppMessage', 11 'onMenuShareQQ', 12 'onMenuShareWeibo', 13 'onMenuShareQZone' 14 ] 15 });
1 // 2. 分享接口 2 // 2.1 監聽「分享給朋友」,按鈕點擊、自定義分享內容及分享結果接口 3 document.querySelector('#onMenuShareAppMessage').onclick = function () { 4 wx.onMenuShareAppMessage({ 5 title: '分享標題', 6 desc: '分享描述', 7 link: 'http://movie.douban.com/subject/25785114/', 8 imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg', 9 trigger: function (res) { 10 // 不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,由於客戶端分享操做是一個同步操做,這時候使用ajax的回包會尚未返回 11 alert('用戶點擊發送給朋友'); 12 }, 13 success: function (res) { 14 alert('已分享'); 15 }, 16 cancel: function (res) { 17 alert('已取消'); 18 }, 19 fail: function (res) { 20 alert(JSON.stringify(res)); 21 } 22 }); 23 alert('已註冊獲取「發送給朋友」狀態事件'); 24 };
若是沒有第一段代碼的聲明,第二段代碼就沒用;若是appid沒有相關接口權限,這裏就註冊不了,也不能調用接口,好比咱們就能夠爲某一個appid賦予打開第三方app的權限,或者咱們定義某個appid具備app,其餘應用能夠根據打開對應app,沒有的話就引導下載:
wx.openApp(appid);
這裏的實現方案能夠是這樣,通常來講,咱們對外釋放的接口都是比較通用的,像一些私密的接口才會有白名單維護,好比咱們本身的app對應的幾個圖片操做接口:
1 //選取圖片接口 2 wx.chooseImage({ 3 count: 1, // 默認9 4 sizeType: ['original', 'compressed'], // 能夠指定是原圖仍是壓縮圖,默認兩者都有 5 sourceType: ['album', 'camera'], // 能夠指定來源是相冊仍是相機,默認兩者都有 6 success: function (res) { 7 var localIds = res.localIds; // 返回選定照片的本地ID列表,localId能夠做爲img標籤的src屬性顯示圖片 8 } 9 }); 10 //上傳圖片接口 11 wx.uploadImage({ 12 localId: '', // 須要上傳的圖片的本地ID,由chooseImage接口得到 13 isShowProgressTips: 1, // 默認爲1,顯示進度提示 14 success: function (res) { 15 var serverId = res.serverId; // 返回圖片的服務器端ID 16 } 17 }); 18 //預覽圖片接口 19 wx.previewImage({ 20 current: '', // 當前顯示圖片的http連接 21 urls: [] // 須要預覽的圖片http連接列表 22 });
其中預覽我把他做爲私密接口不予釋放,須要特定的appid才能使用,就能夠這樣:
1 //1表明公共接口,0表明私密接口 2 var apilist = { 3 'chooseImage': 1, 4 'uploadImage': 1, 5 'previewImage': 0 6 }; 7 //全部的應用id 8 var appids = [1, 2, 3, 4]; 9 //白名單 10 var whitList = [{1: ['previewImage', '其餘私密能力']}];
實話實說維護一個appid,這樣作的成本比較高,單單作appid和祕鑰對於調用者來講也挺麻煩,對於有些比較小的平臺來講,能夠採起域名白名單的方法,後端維護一個列表:
1 //域名白名單 2 var whitList = [ 3 {'domain.com': ['previewImage', '其餘私密能力']}, 4 {'domain2.com': ['previewImage', 'uploadImage', '其餘私密能力']} 5 ];
這種方法比較作起來成本較低,一些小一點的平臺能夠這樣作,而這樣作的話,須要考慮每一個域名對應的App打開協議,可能會有打開需求,咱們這裏採用的比較簡單的方案,域名白名單,表設計大概這樣:
1 var whitList = [ 2 {id: 'domain.com', apis: ['previewImage', '其餘私密能力'], schema: 'xxxx://'}, 3 {id: 'domain2.com', apis: ['previewImage', 'uploadImage', '其餘私密能力'], schema: 'xxxx://'} 4 ];
咱們明確知道某個域名具備哪些能力,若是不具備這些能力就不予理睬,咱們也知道某個域名具備打開某個app的權限。
明確了能力,以及能力限制,接下來咱們便來整理一下幾個核心的對外接口。
咱們這裏作的第一件事情,依舊是header的定義,而且對於第三方,咱們要求header只能是這個樣子:
針對header咱們有如下約定:
① 進入一個頁面默認包含,返回+title+功能菜單三個按鈕
② 返回按鈕默認執行history.back()的操做,若是history.length爲1,則退到native上一步操做
③ title默認讀取html中的title標籤,可以使用接口更改
④ 關閉按鈕默認不存在,在history比較深而且點擊過一次返回按鈕後展現出來,防止頁面死循環假死
⑤ 功能菜單默認彈出如下菜單項,其中分享文案默認讀取當前tdk(title+description)標籤,和第一張圖片,也可讀取頁面標籤訂製(也可使用接口定製,事實上容器不會作這種業務工做,是業務框架層bridge作的工做),好比:
1 <meta name="med-title" content="分享標題"> 2 <meta name="med-description" content="分享內容"> 3 <meta name="med-link" content="http://...."> 4 <meta name="med-img" content="http://....">
咱們業務層代碼,或者bridge代碼會將之翻譯爲:
1 wx.onMenuShareTimeline({ 2 title: medTitle, 3 link: medLink, 4 imgUrl: medImg, 5 trigger: function (res) { 6 }, 7 success: function (res) { 8 }, 9 cancel: function (res) { 10 }, 11 fail: function (res) { 12 } 13 });
分享到朋友圈前端代碼爲:
1 MED.origin = MED.origin || {}; 2 //shareTimeline分享到朋友圈;shareAppMessage分享給朋友;shareQQ分享給qq好友;shareQZone分享到空間,設置方面稍做更改便可 3 MED.origin.medShareXXX = MED.medShareXXX = function (o) { 4 _.requestHybrid({ 5 tagname: 'shareTimeline', 6 param: { 7 title: o.title, 8 desc: o.desc, 9 image: o.img, 10 url: o.link 11 }, 12 callback: function(data) { 13 if(data.code === 0) { 14 o.success && o.success(data.data); 15 } else { 16 o.cancel && o.cancel(data.data); 17 } 18 } 19 }); 20 };
H5上傳圖片方面的體驗不好,這塊咱們在H5狀況下依舊使用file上傳,可是在容器裏面釋放幾個圖片操做接口
1 //選取圖片,最初想把選取和上傳合併的,後面想一想仍是分開合適 2 _.requestHybrid({ 3 tagname: 'chooseImage', 4 param: { 5 //1-9張限制 6 count: 1, 7 sizeType: ['original', 'compressed'], // 能夠指定是原圖仍是壓縮圖,默認兩者都有 8 sourceType: ['album', 'camera'], // 能夠指定來源是相冊仍是相機,默認兩者都有 9 }, 10 callback: function(data) { 11 if(data.code === 0) { 12 //這塊有一些疑問,選擇和上傳仍是連着一塊兒算了 13 o.success && o.success(data.data.localIds); // 返回選定照片的本地ID列表,localId能夠做爲img標籤的src屬性顯示圖片; 14 } else { 15 o.error && o.error(data.data); 16 } 17 } 18 }); 19 20 //上傳圖片 21 _.requestHybrid({ 22 tagname: 'uploadImage', 23 param: { 24 //由chooseImage獲取 25 localId: 1, 26 isShowProgressTips: 1 // 默認爲1,顯示進度提示 27 }, 28 callback: function(data) { 29 if(data.code === 0) { 30 o.success && o.success(data.data.url); // 返回src; 31 } else { 32 o.error && o.error(data.data); 33 } 34 } 35 }); 36 37 //圖片預覽,預覽的地方作個圖片下載的功能 38 _.requestHybrid({ 39 tagname: 'previewImage', 40 param: { 41 current: '', // 當前顯示圖片的http連接 42 urls: [] // 須要預覽的圖片http連接列表 43 } 44 });
1 //獲取網絡狀態 2 _.requestHybrid({ 3 tagname: 'getNetworkType', 4 callback: function(data) { 5 //data.networkType 2g 3g 4g wifi 6 } 7 });
Native的地理操做一塊相對H5體驗要好一些,特別是地圖展現一塊的體驗要好得多,因此這兩塊也須要釋放API:
1 //獲取經緯度信息 2 _.requestHybrid({ 3 tagname: 'getLocation', 4 callback: function(data) { 5 if(data.code !== 0) return; 6 var res = data.res; 7 var latitude = res.latitude; // 緯度,浮點數,範圍爲90 ~ -90 8 var longitude = res.longitude; // 經度,浮點數,範圍爲180 ~ -180。 9 var speed = res.speed; // 速度,以米/每秒計 10 var accuracy = res.accuracy; // 位置精度 11 } 12 }); 13 14 //根據經緯度等信息打開native地圖 15 _.requestHybrid({ 16 tagname: 'openLocation', 17 params: { 18 latitude: 0, // 緯度,浮點數,範圍爲90 ~ -90 19 longitude: 0, // 經度,浮點數,範圍爲180 ~ -180。 20 name: '', // 位置名 21 address: '', // 地址詳情說明 22 scale: 1, // 地圖縮放級別,整形值,範圍從1~28。默認爲最大 23 infoUrl: '' // 在查看位置界面底部顯示的超連接,可點擊跳轉 24 } 25 });
所謂的name和地址是指下面信息框這一坨:
關閉當前webview,回到native上一次操做:
1 _.requestHybrid({ 2 tagname: 'closeWindow' 3 });
H5在文字輸入一塊能夠說是弱爆了,比Native體驗差遠了,因此咱們在native鍵盤這塊也作了一個nativeUI,若是需求容許可使用:
1 //喚起輸入文字的軟鍵盤 2 //這塊代碼有一些業務耦合,須要如何處理下???? 3 _.requestHybrid({ 4 tagname: 'showKeyboard', 5 param: { 6 hasImg: 1, //是否須要上傳圖片區域,若是須要則爲1,不須要爲0 7 count: 1, //若是須要圖片上傳,這裏限制圖片選擇的數量,1-9 8 sizeType: ['original', 'compressed'], // 能夠指定是原圖仍是壓縮圖,默認兩者都有 9 sourceType: ['album', 'camera'], // 能夠指定來源是相冊仍是相機,默認兩者都有 10 textMin: 20, //文字要求最少輸入字符數 11 textMax: 500 //文字要求最多輸入字符數 12 }, 13 //輸入結束的回調或者說點擊發送時候的回調 14 callback: function (data) { 15 var content = data.content;//文字內容 16 var urls = data.urls;//圖片地址 17 } 18 });
今天分析了一下第三方webview須要釋放的接口,接下來我這邊開始落地,咱們真實工做中可能還要考慮新老容器過渡等問題,本文含金量相對較小,各位謹慎閱讀吧。