面向前端的webview知识总结

867 阅读9分钟

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

  1. 通过 WebView 的loadUrl()
  2. 前端在全局环境下定义一个函数给 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调安卓

  1. 对象映射

通过 WebView 的addJavascriptInterface()进行对象映射,有点类似往 H5 加了一个对象,对象里面有 WebView定义的方法,可以直接调用。但该方法可能存在严重的漏洞问题,且容易与 javascript 的变量冲突,不推荐

  1. 利用 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);
  }
}

  1. 通过 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

  1. 全局定义一个函数供 webview 调用

同安卓的办法类似,前端只需要在window下定义约定好的函数等待被调用即可

// 前端代码
window.callJs = function() {
  // ...
}

客户端调用时可以选择传递参数

// iOS代码
[self.webView evaluateJavaScript:@"callJs()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
  // ...
}];

H5调iOS

  1. 利用 ULR 拦截来通信

跟安卓一样,iOS也可以拦截前端的 URL 变化,来处理通信,这里不具体列举了

// 前端代码
window.location.href = 'jssdk://hello'
  1. 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使用教程

Carson带你学Android:全面总结WebView与 JS 的交互方式

Android WebView缓存机制和性能优化

iOS UIWebView与WKWebView 那些事

WKWebView使用过程中遇到的坑

WKWebView处理js打开新标签(新窗口)

WKWebView学习笔记

WKWebView默认缓存策略与HTTP缓存协议

iOS 与 H5 的交互【WKWebView】