前言
如果你還是第一次與app合作開發webview的頁面,那麼對於如何除錯,可能有哪些問題可能是不夠了解的。本文嘗試性的根據自己的經驗給大家一個入門級別的瞭解,如果是大佬級別的,可以繞路了。
webview協議約定
為了更好的在app中除錯開發我們的移動頁面(h5),我們需要與app開發人員約定一些基本的原則,來保證我們的頁面可以很好的進行除錯,包括除錯工具、靈活的模擬上線時的app環境、測試互動過程中的問題、方便自定義的修改為自己的h5地址等。
以下的方案僅供參考,每一條都是有實際用途的,如果公司裡的webview需要進行準確的除錯和後續開發,必要性的需要考慮以下的問題。
主題 | 方案 | 備註 |
---|---|---|
統一確定的ua標識 | 比如ua結尾加入【xxx】 | 無 |
h5公用的app頭 | app端提供統一的app頭,參考支付寶以及微信的ua互動,提供顯示頁面標題,返回,關閉的簡單操作,預設頁面可滾動 | 之後其他的h5預設在這個類瀏覽器外殼中,針對前端一些固定佈局的方案,需要優化完善這個外殼,前端技改時間允許的話,最好給出完整確定的方案可以在webview中無縫對接和展示 |
h5與app定製頭 | 針對產品以及互動特殊需求,提供的特殊頁面,比如單頁,強互動邏輯頁定製專門的頭 | 需要產品明確說明特殊性,不是瀏覽器的返回,比如返回需要加確認框,就需要定製 |
h5與app功能性互動 | 約定常規互動方法的格式,並給出相互通訊的一些固定的可用的方法,比如獲取使用者資訊,獲取app網路狀態 | 這個是雙向的功能性互動,h5的一些方法也可以設定app的狀態,頁面跳轉,資料儲存等 |
h5與app純互動性方法 | 調取相應app的載入框,載入失敗,相簿控制元件,掃碼控制元件 | 需要與產品,互動統一確定是否使用app原生控制元件還是h5效果。 |
h5與app不同場景的分享互通 | 比如:使用者在不同app中:app分享到h5詳情頁,h5詳情頁也可以對應到app中開啟 | 需要約定規則 |
app提供webview的外殼 | 可以通過app外殼掃碼進入webview場景,模擬互動,開發階段暴露解決一些app中問題 | 以上的app解決方案整合在這個app外殼中 |
關於 app內webiew與h5通訊情況
作為常識我們知道,一般情況下webview的頁面是包括兩種情況的,一種是本來就可能限定於只有app會嵌入的h5頁面,這部分在與app進行通訊的時候,我們更多的是通過約定jsBridge的方式。一種是普通的h5頁面。
jsBridge說的更直白一點,就是網頁在載入時,向頁面內注入一個指定的js檔案,然後頁面內就會有一個前端和app都知道的方法,通過這個方法前端可以喚起app的互動控制元件,甚至是跳轉到其他的app頁面,也可以知道app此時的一些裝置狀態、網路狀態、使用者資訊等。而app也可以通過h5知道此時頁面的狀態,進而根據需要做可以在必要時喚起想要的操作。
而另一方面,webview也可看做一個普通的瀏覽器,可以載入任何的頁面,所以我們非app的內嵌頁的h5也可以在app內通過webview進行開啟; 而app外的h5可以通過app自定義的協議碼來喚起app。
相關的通訊技術點可見下面的簡陋的圖說明。
jsBridge參考文件
iOS與H5互動說明(ios)
iOS與H5互動,採用是JavaScriptCore方式。原理是iOS端在WebView載入完一個URL連結的時候,手動向H5頁面繫結一個JSContext
物件。利用這個JSContext物件,可以實現OC與JS間的雙向互動。注意:JSContext物件是在iOS的webViewDidFinishLoad:
回撥裡完成繫結的,在完成繫結前無法使用JS與OC的任何互動。
JS呼叫OC介面
JS開始呼叫OC介面前,有幾個前提條件:
-
js的window.isReady方法已經觸發過了,JS才能呼叫OC提供的方法。原因是iOS會在JSContext物件繫結成功後,才會向js端呼叫isReady方法,所以js只有等isReady觸發了,才能通過JSContext呼叫OC方法。
-
iOS在繫結JSContext物件的時候,要約定好一個欄位,然後OC會將原生方法註冊到網頁window物件的這個欄位上。比如
window.app
。 -
js端如果要非同步接收原生方法的返回結果,需要在全域性作用域內定義好回撥方法
JS示例程式碼:
js呼叫OC原生方法,同步獲取使用者基本資訊
// 約定好獲取使用者資訊介面註冊到window的app屬性上
// getUserInfo方法是一個同步方法,可以js端可以直接獲取到返回值
// 返回的物件可以是json字串
var info = window.app.getUserInfo()
複製程式碼
js呼叫OC原生方法,拍照上傳作業圖片,並非同步獲取上傳結果
// 假設約定好作業相關的OC介面都註冊到window的homework屬性上
window.homework.uploadHomeworkPicture(questionID)
// 在全域性作用域內定義好回撥方法,用於接收返回值
// 原生方法會在上傳完作業圖片的時候,間接呼叫該回撥方法
function homeworkPictureDidUploaded(questionID, picUrl) {
// do something...
}
複製程式碼
JS裡呼叫通用原生介面:
/**
跳轉到課程詳情
@param productId 商品ID(string型別)
*/
app.gotoCourseDetail(productId);
/**
關閉當前頁面
*/
app.finish();
/**
獲取使用者資訊,已json字串形式返回。主要欄位如下:
memberId: 使用者id
token: 使用者登入唯一標識
memberType: 使用者型別
*/
app.getUserInfo();
/**
toast提示
@param msg 提示語(string型別)
*/
app.toast(msg);
/**
顯示對話方塊
@param title 標題(string字串)
@param msg 訊息(string字串)
@param actions 點選事件(一個json陣列字串),每個陣列元素欄位如下:
title: 事件標題(string字串,比如“取消”)
callback: 事件的js回撥方法(string字串)
示例:
var actions = "[{`title`: `取消`, `callback`: `cancelPay`},
{`title`: `確定`, `callback`: `confirmPay`}]";
app.confirm("溫馨提示", "是否支付訂單?", actions);
*/
app.confirm(title, msg, actions);
複製程式碼
OC呼叫JS介面
OC在呼叫JS方法時的注意事項:
-
js方法應該申明到全域性作用域內,否則OC獲取不到該方法
-
如果在
webViewDidFinishLoad:
直接用過JSContext呼叫js方法,可能會出現呼叫無效的請求。為了避免此類問題,推薦以setTimeout
方式呼叫js方法
示例程式碼:
OC在webViewDidFinishLoad:中呼叫js的isReady
方法
// setTimeout是JS的自帶方法
// 這裡使用setTimeout的目的是為了將isReady方法放到js呼叫佇列的最後
JSValue *isReadyFunc = self.jsContext[@"isReady"];
if (isReadyFunc) {
[self.jsContext[@"setTimeout"] callWithArguments:@[isReadyFunc, @100]];
}
複製程式碼
OC在JS發起的原生方法中呼叫js的setUserInfo方法
// 注意,JavaScriptCore支援NSDictionary、NSArray型別作引數傳給js方法
NSDictionary *userInfo = ...;
[self.jsContext[@"setUserInfo"] callWithArguments:@[userInfo]];
複製程式碼
JS提供給原生呼叫的通用介面定義:
/**
iOS原生初始化完成後呼叫本方法,告訴js已經準備好了
*/
function isReady();
/**
* return boolean 型別返回值:
true h5已經處理了返回,native不處理;
false h5沒有處理返回,native直接返回上級原生頁面
*/
function gobackIfNeeded();
複製程式碼
APP喚醒
定義scheme: com.xxx.app
UserAgent
WebView的預設UserAgent為:”xxxx XXX/1.3.0″, 其中xxxx為系統預設UserAgent。“/“後為app版本號
內嵌H5頁面的載入(安卓)
1.原生提供一個框架頁面給H5頁面。框架只提供一個叫InnerWeb
的類(這點js不需要知曉).如何需要在本地載入一個純H5的內嵌頁面,請使用IntentHelper.startWeb(Context context, String url)
方法去載入一個內嵌H5頁面。具體內部只是載入這個url。之後的邏輯都交給H5處理。
Android本地通過Java呼叫HTML頁面中的JavaScript方法
原生呼叫js方法分一下兩種型別的方法:
- 無返回值方法
- 有返回值方法
呼叫js中無返回值方法
很簡單,我們直接呼叫即可具體程式碼示例如下:
/**
* f1 為js中宣告的函式
*/
mWebView.loadUrl("javascript:f1()");
複製程式碼
這樣就可以呼叫js的方法了。
呼叫js中有返回值的方法
稍微複雜一點,如下:
/**
* sum 為js中定義的函式
*/
mWebView.evaluateJavascript("sum(1,2)", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.e(TAG, "onReceiveValue value=" + value);
}
});
複製程式碼
js呼叫Android本地Java方法
本地提供給js呼叫的對映物件,這需要注入,我們同一使用一個叫app
的物件。js如要呼叫本地方法。都以此開頭來代表我們原生方法。
具體如下:
<script type="text/javascript">
function s(){
// 注意下面的‘app’
var result = window.app.gotoLogin();
document.getElementById("p").innerHTML = result;
}
</script>
複製程式碼
原生程式碼如下:
public calss AppJavascriptInterface {
@JavascriptInterface
public void gotoLogin() {
if (mContext.get() == null) {
Log.w("web", "頁面已關閉");
return;
}
LoginActivity.start(mContext.get());
}
}
複製程式碼
產品協議
協議的主要以原生提供給H5的為主, 下面是我詳細羅列的:
/**
* 跳轉登入
*/
@JavascriptInterface
public void gotoLogin();
/**
* 跳轉課程詳情
*/
@JavascriptInterface
public void gotoCourseDetail();
/**
* 關閉當前頁面
*/
@JavascriptInterface
public void finish();
/**
* 關閉當前頁面獲取當前使用者資訊,如果為空,說明使用者未登入
*
* 目前有如下資訊(以json格式返回給H5):
* memberId: 使用者id
* token: 使用者登入唯一標識
* memberType: 使用者型別
*/
@JavascriptInterface
public String getUserInfo();
/**
* 返回
*
* 目前有如下資訊(以json格式返回給H5):
* memberId: 使用者id
* token: 使用者登入唯一標識
* memberType: 使用者型別
*/
public void back();
/**
*
*/
public void toast(String msg);
public void confirm(String title, String msg, String positiveFunctionName, String negativeFunctionName);
複製程式碼
/**
* return boolean 型別返回值: true h5已經處理了返回,native不處理; false h5沒有處理返回,native返回上一個非H5頁面
*/
function gobackIfNeeded();
複製程式碼
app的喚醒方式方案:
1.定義scheme: com.xxx.app
2.另外具體頁面的開啟待定
約定ua: “xxxx XXX/1.3.0”, 其中xxxx為系統預設ua。iOS與Android不一樣。”/”後為app版本號
h5喚起app
h5喚起app已經變成了目前不可或缺的功能之一,作為前端技術棧的必備技術棧之一,我們需要知道如何在非app環境內喚起app,以及正確識別是系統中是否安裝了app.
系統相關
應用名稱 | URL Scheme |
---|---|
簡訊 | sms:// |
app store | itms-apps:// |
電話 | tel:// |
無線區域網 | App-Prefs:root=WIFI |
藍芽 | App-Prefs:root=Bluetooth |
蜂窩行動網路 | App-Prefs:root=MOBILE_DATA_SETTINGS_ID |
個人熱點 | App-Prefs:root=INTERNET_TETHERING |
運營商 | App-Prefs:root=Carrier |
通知 | App-Prefs:root=NOTIFICATIONS_ID |
通用 | App-Prefs:root=General |
通用-關於本機 | App-Prefs:root=General&path=About |
通用-鍵盤 | App-Prefs:root=General&path=Keyboard |
通用-輔助功能 | App-Prefs:root=General&path=ACCESSIBILITY |
通用-語言與地區 | App-Prefs:root=General&path=INTERNATIONAL |
通用-還原 | App-Prefs:root=Reset |
牆紙 | App-Prefs:root=Wallpaper |
Siri | App-Prefs:root=SIRI |
隱私 | App-Prefs:root=Privacy |
Safari | App-Prefs:root=SAFARI |
音樂 | App-Prefs:root=MUSIC |
音樂-均衡器 | App-Prefs:root=MUSIC&path=com.apple.Music:EQ |
照片與相機 | App-Prefs:root=Photos |
FaceTime | App-Prefs:root=FACETIME |
應用
應用名稱 | URL Scheme |
---|---|
微博 | weibo:// |
mqq:// | |
QQ群組 | mqqapi://card/show_pslcard?src_type=internal&version=1&card_type=group&uin={QQ群號} |
QQ聯絡人 | mqqapi://card/show_pslcard?src_type=internal&version=1&uin={QQ號碼} |
支付寶 | alipay:// |
微信 | weixin:// |
微信 | wechat:// |
微信-掃一掃 | weixin://dl/scan |
微信-反饋 | weixin://dl/feedback |
微信-朋友圈 | weixin://dl/moments |
微信-設定 | weixin://dl/settings |
微信-訊息通知設定 | weixin://dl/notifications |
微信-聊天設定 | weixin://dl/chat |
微信-通用設定 | weixin://dl/general |
微信-公眾號 | weixin://dl/officialaccounts |
微信-遊戲 | weixin://dl/games |
微信-幫助 | weixin://dl/help |
微信-個人資訊 | weixin://dl/profile |
微信-功能外掛 | weixin://dl/features |
蝦米音樂 | xiami:// |
chrome | googlechrome:// |
微博國際版 | weibointernational:// |
摩拜單車 | mobike:// |
ofo | ofoapp:// |
有道雲筆記 | youdaonote:// |
印象筆記 | evernote:// |
今日頭條 | snssdk141:// |
網易新聞 | newsapp:// |
網易雲音樂 | orpheuswidget:// |
QQ音樂 | qqmusic:// |
QQ音樂最近播放 | qqmusic://today?mid=31&k1=2&k4=0 |
美團外賣 | meituanwaimai:// |
美團 | imeituan:// |
Gmail | googlegmail:// |
網易郵箱 | neteasemail:// |
QQ郵箱 | qqmail:// |
騰訊視訊 | tenvideo:// |
愛奇藝 | iqiyi:// |
12306 | cn.12306:// |
有道詞典 | yddict:// |
釘釘 | dingtalk:// |