app中的webview通識篇(上)

RobinsonZhang發表於2019-03-04

前言

如果你還是第一次與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。

相關的通訊技術點可見下面的簡陋的圖說明。

app中的webview通識篇(上)

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方法分一下兩種型別的方法:

  1. 無返回值方法
  2. 有返回值方法

呼叫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://
QQ 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://

參考文章

相關文章