webview
本篇文主要面向前端同学食用,分别从简介,内核&组件,基本配置,加载,缓存机制,通信等几个方向列举安卓和iOS有关 webview 的知识点,希望前端在和客户端同学对接时不会太懵逼..
🌝 简介
webview 是一个可以加载网页的可视组件,在原生应用中经常使用 webview 来嵌入一个网页,以此来开发混合APP(可以简单理解为就是一个浏览器)
webview 可以借助原生APP在 web 中调用系统功能,同时,webview 也可以限制 web 的一些功能权限。一般开发阶段都可以直接面向浏览器调试,除了需要调用到原生系统功能才需要在 webivew 里面联调
🌝 内核&组件
安卓
安卓 weiview 内核主要有两个分类:Android系统webview 和 第三方webview内核
Android系统webview
在安卓4.4之前,Android WebView 基于WebKit内核的实现。
而在安卓4.4及之后,Android WebView 就换成基于 Google 的 Chromium 实现,Chromium 在性能,H5支持等方面都有很大的提升。
在安卓5.0开始,WebView 移植成了一个独立的apk,可以不依赖系统而独立存在和更新。
第三方webview内核
- 腾讯x5内核(官方介绍,QQ浏览器,微信等都是使用这个)
- Crosswallk (体积偏大)
iOS
iOS 只要有两种 webview 组件: UIWebView 和 WKWebView
UIWebView 是 iOS 2 中推出的网页容器
直到 iOS 8 以后,苹果推出了 WKWebView ,具有占用内存小,性能好、稳定性高、H5支持度高及功能丰富等优点
iOS 12 中,苹果正式弃用 UIWebView,要求开发者用 WKWebView 全面替换 UIWebView
apple 文档
🌝 webview基本配置
列举一些可能对前端有用的配置信息
安卓
- 允许js代码
- 允许SessionStorage/LocalStorage存储
- 允许访问文件
- 禁用放缩
- 禁用文字缩放
- 允许缓存,设置缓存位置
- 不保存密码
- 设置UA
- 移除部分系统JavaScript接口
- 自动加载图片
- 关于前进 / 后退网页
在不做任何处理前提下,浏览网页时点击系统的“Back”键,整个 Browser 会调用finish()而结束自身,安卓可以拦截到该事件,并做返回上一条历史记录的处理 - 从 Android 5.0 开始,webview 默认不支持同时加载 Https 和 Http 资源,可以设置更改为允许
- ...
iOS
- 最小字体大小
- 是否支持JavaScript
- 播放视频
- allowsInlineMediaPlayback 设置HTML5视频是否允许网页内联播放(同时前端的 video 还需要设置 playinline 属性才有效)
- HTML5视频是否可以播放画中画
- 默认情况下,Web 视图会自动将出现在 Web 内容中的电话号码,链接,日历转换为链接,当链接被点击时,程序就会拨打该号码或打开日历或链接,可关闭这个默认的行为
- Web 视图是否应始终允许缩放网页(会覆盖前端设置的值)
- JavaScript 是否可以在没有用户交互的情况下打开窗口
- 禁止用户选择或者长按操作
- ...
PS:以前遇到过页面在 APP Demo 包内打开测试时,发现在 web 上点击上传文件调不起系统文件选择器,是由于 webview 没有配置允许访问文件;还有发现 web 中代码执行不了报错,是由于 webview 没有配置允许 JavaScript 等等。
🌝 加载
安卓
加载方式
//方式1. 加载一个网页:
webView.loadUrl("http://www.google.com/");
//方式2:加载apk包中的html页面
webView.loadUrl("file:///android_asset/test.html");
//方式3:加载手机本地的html页面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
// 方式4: 加载 HTML 页面的一小段内容
webView.loadData(String data, String mimeType, String encoding)
加载相关事件
-
onPageStarted()
开始载入页面时调用可以利用这个做一个loading的页面。 -
onPageFinished()
在页面加载结束时调用,配合1.可以关闭loading(但听说不同的内核里调用的时机都不一样,没验证过谨慎使用) -
onLoadResource()
在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次 -
onReceivedError()
加载出错时调用,如404,客户端可以在这时选择加载一个错误的页面显示 -
onProgressChanged()获取网页的加载进度(0-100)
iOS
加载方式
-
加载网络url
-
加载本地html
加载相关事件
-
同样,iOS也可以监听到页面的加载开始,进度,完成等事件
-
前端使用
window.open()打开新页面时,默认是无效的,需要客户端额外配置处理一下
🌝 缓存机制
webview 可以将访问到的文件缓存在本地,视情况去本地加载
安卓
webview 可以设置不同的缓存模式来缓存我们的页面文件,分别有
- LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
- LOAD_DEFAULT: (默认)根据 http 响应头的
cache-control决定是否从网络上取数据 - LOAD_NO_CACHE: 不使用缓存,只从网络获取数据
- LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据
// 设置缓存模式:
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
// 不使用缓存:
WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
PS:一般推荐设置为默认的LOAD_DEFAULT(根据cache-control决定是否从网络上取数据)
iOS
iOS默认缓存配置遵循 http 缓存策略,类似安卓的 LOAD_DEFAULT
对于单页项目,推荐做法是对 index.html 文件响应头配置Cache-Control: no-cache,让请求每次都会去服务器校验文件是否有更新;而对于依赖资源文件(如js,图片等),推荐在文件名做版本标识,打包工具一般都会有这个功能
🌝 与H5通信
通信有两个方向,一个 WebView 主动与 H5 通信,另一个是 H5 主动发起与 WebView 的通信,下面分别列出具体的通信姿势
安卓
安卓调H5
- 通过 WebView 的
loadUrl() - 前端在全局环境下定义一个函数给 Webview 调用
通过 WebView 的
evaluateJavascript()可以调用前端定义的函数
效率更高、使用更简洁
注意:Android 4.4 后才可使用
前端只需要在window下定义好一个等被调用的函数即可,WebView会使用evaluateJavascript()去执行我们的方法,同时可以传入参数,并拿到我们在方法里面 return 的值
// 前端代码
window.callJs = function() {
// ...
}
// 安卓代码
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// 此处能拿到 js 返回的结果
}
});
这里需要注意,安卓执行调用时,前端是否已经定义好对应的函数了
H5调安卓
- 对象映射
通过 WebView 的
addJavascriptInterface()进行对象映射,有点类似往 H5 加了一个对象,对象里面有 WebView定义的方法,可以直接调用。但该方法可能存在严重的漏洞问题,且容易与 javascript 的变量冲突,不推荐
- 利用 ULR 拦截来通信
Android 可以通过 WebViewClient 的回调方法
shouldOverrideUrlLoading ()来拦截H5的 url,所以只要我们事先与客户端约定好具体的协议即可通信
如:我们与客户端约定了jssdk://hello来打招呼
前端触发协议只需要改变location.href即可
// 前端代码
window.location.href = 'jssdk://hello'
window.location.href = 'jssdk://hello?name=s1' // 也可以带上参数给客户端
// 安卓代码
// 复写WebViewClient类的shouldOverrideUrlLoading方法
new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// url就是前端触发的协议,先判断是否是约定好的协议,然后按需拿对应的数据即可
// ...
return super.shouldOverrideUrlLoading(view, url);
}
}
- 通过 prompt() 等对话框通信(需要交换参数的时候推荐)
前端调起
alert()、confirm()、prompt()对话框时,默认情况下是不会弹出系统的对话框的(iOS也类似),而是会触发 webview 的onJsAlert()、onJsConfirm()、onJsPrompt()方法,然后 webview 在方法内处理弹出对话框才有。期间就可以在方法内处理通信逻辑,拿到前端传过来的东西并可以返回内容给前端
以prompt()为例子(prompt较常用,可以提交参数也可以拿到返回值)
// 前端代码
let result = prompt("jssdk://hello?arg1=111")
// 安卓代码
@Override
// 拦截到 prompt
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 参数message: 代表 prompt 的内容
// 参数result: 代表输入框的返回值
// ...
// 判断是不是我们约定的协议,做对应的操作
// 然后可以用 result.confirm 返回有一些东西给前端
result.confirm("一些东西");
return super.onJsPrompt(view, url, message, defaultValue, result);
}
这样前端的result就能拿到客户端返回的东西了
iOS
iOS调H5
- 全局定义一个函数供 webview 调用
同安卓的办法类似,前端只需要在window下定义约定好的函数等待被调用即可
// 前端代码
window.callJs = function() {
// ...
}
客户端调用时可以选择传递参数
// iOS代码
[self.webView evaluateJavaScript:@"callJs()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
// ...
}];
H5调iOS
- 利用 ULR 拦截来通信
跟安卓一样,iOS也可以拦截前端的 URL 变化,来处理通信,这里不具体列举了
// 前端代码
window.location.href = 'jssdk://hello'
- WKScriptMessageHandler
WKScriptMessageHandler 是 WKWebView 的一种协议,可以让 js 与客户端通信
客户端添加了 WKScriptMessageHandler 协议之后,前端就可以通过 window.webkit访问到该通信的代理对象。与客户端约定好通信的名称后,前端就可以通过window.webkit提供的 api 主动与客户端通信
如与客户端约定hello来通信
// 前端代码
window.webkit.messageHandlers.hello.postMessage(/**传递的参数 */)
// iOS代码
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"Share"]) {
// ...
}
}
message 参数包含了前端传递的信息,message.body就是JS传过来的参数,可以是字符串,可以是数组,也可以是字典。通过message.name判断可以知道监听的是JS的哪个方法
🌝 参考链接
Carson带你学Android:最全面、易懂的Webview使用教程