WebView 加载链接跳转快应用问题

前言

最近在项目中遇到一个关于快应用的问题,就是在应用内某个页面使用 WebView 查看微博时,会发生一次跳转到快应用的行为,在一些机型(目前发现华为的一些机型)上会因为 scheme 协议的问题而无法正确跳转,加载不了页面,而在另一个类似的页面打开,又没有发生跳转,只在应用内打开了微博页面,后来通过排查,用不是太妥善的方法解决了问题,先做下记录,以后再完善

分析过程

在使用以下机型的时候会出现下面图中的问题,而华为荣耀8青春版却又没有这个问题

  • 华为畅享 6

    • EMUI 4.7
    • Android 6.0
  • 华为 p9

    • EMUI 8.0
    • Android 8.0
  • 华为畅享 7

    图片名称

图中的报错信息显示是加载到了一个未知的 scheme,导致无法加载页面,我 load 的明明就是一个 http 链接,怎么会变成 scheme 呢,从报错中的 hwfastapp 就可以看出端倪,fastapp 不就是之前发布的快应用吗?先去了解下什么是快应用

从官网中可以看到,这是努比亚、联想、一加、小米、vivo、华为、OPPO、金立、魅族、中兴这十家厂商联合起来的快应用联盟所推出的一种应用形态,旨在打造一种无需安装,即点即用的新的应用体验,我感觉其实类似之前的 Progressive Web App(PWA),看了下手头上的手机,就只有小米和华为,先拿这两家来测试下:

图片名称 图片名称

先是华为,可以在 WebView 的 setWebViewClient 中通过重写 shouldOverrideUrlLoading() 方法来看到 webView.loadUrl(url) 都干了什么,关于 shouldOverrideUrlLoading() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Give the host application a chance to take over the control when a new
* url is about to be loaded in the current WebView. If WebViewClient is not
* provided, by default WebView will ask Activity Manager to choose the
* proper handler for the url. If WebViewClient is provided, return true
* means the host application handles the url, while return false means the
* current WebView handles the url.
* This method is not called for requests using the POST "method".
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return True if the host application wants to leave the current WebView
* and handle the url itself, otherwise return false.
* @deprecated Use {@link #shouldOverrideUrlLoading(WebView, WebResourceRequest)
* shouldOverrideUrlLoading(WebView, WebResourceRequest)} instead.
*/
@Deprecated
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}

大致的意思是

  • 若没有设置 WebViewClient 则由系统(Activity Manager)处理该 url,通常是使用浏览器打开或弹出浏览器选择对话框。
  • 若设置 WebViewClient 且该方法返回 true ,则说明由应用的代码处理该 url,WebView 不处理,也就是程序员自己做处理。
  • 若设置 WebViewClient 且该方法返回 false,则说明由 WebView 处理该 url,即用 WebView 加载该 url

而 WebView 的前进、后退、刷新、以及 post 请求都不会调用 shouldOverrideUrlLoading 方法,除去以上行为,还得满足(!isLoadUrl || isRedirect) 即 (不是通过 webView.loadUrl 来加载的 或者 是重定向的)这个条件,才会调用 shouldOverrideUrlLoading 方法

所以通过重写 WebView的shouldOverrideUrlLoading() 方法,可以捕获到重定向的链接经历了几次变化

1
2
3
4
5
6
7
8
9
https://api.weibo.com/开头
⬇️
http://weibo.com/
⬇️
http://m.weibo.cn/XXXXXX/XXXXXXX?&jumpfrom=weibocom
⬇️
https://m.weibo.cn/status/XXXXXXXXX?jumpfrom=weibocom
⬇️
hwfastapp://xxxxxx

然后 WebView 在加载这条 scheme 的时候,无法识别,所以报错了,那么好办,直接过滤掉这个开头的,或者干脆不单只华为,连其他厂商的也不加载了,就是只加载 http 或者 https 开头的链接,那就不用报错了吧,试了一下

1
2
3
4
5
6
7
8
9
10
11
12
webSiteNavigation.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("http:") || url.startsWith("https:")) {
view.loadUrl(url);
return false;
} else {
//不做任何事直接返回true
return true;
}
}
}

果然可行,在页面内加载出了微博的页面,并没有跳转快应用

那再试试小米系统,同样的方法,却发现小米仍然可以跳转到快应用,但是 shouldOverrideUrlLoading() 捕获不到关于快应用的链接,都是正常的 http 或者 https 头的,那是不是还有其他方式跳转快应用,而没有华为那么明显呢,重写一下 shouldInterceptRequest() 方法,看看发生了什么,从返回中可以看到,都是一些 js、css 页面或者是 log 和页面内的图片资源 jpg、svg 图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
I/lvqx-test: https://api.weibo.com/2/statuses/go?uid=6360369679&id=4316516449593454
I/lvqx-test: http://weibo.com/6360369679/H6VFqEfH8
I/lvqx-test: https://weibo.com/6360369679/H6VFqEfH8
I/lvqx-test: http://m.weibo.cn/6360369679/H6VFqEfH8?&jumpfrom=weibocom
I/lvqx-test: https://m.weibo.cn/6360369679/H6VFqEfH8?&jumpfrom=weibocom
I/lvqx-test: https://m.weibo.cn/status/H6VFqEfH8?jumpfrom=weibocom

I/lvqx-test: https://h5.sinaimg.cn/m/weibo-lite/js/vendor.9e10b5e9.js
I/lvqx-test: https://h5.sinaimg.cn/m/weibo-lite/js/app.81d27428.js
I/lvqx-test: https://h5.sinaimg.cn/m/weibo-lite/css/app.c20922eb.css
I/lvqx-test: https://h5.sinaimg.cn/m/weibo-lite/js/manifest.65eb9185.js
I/lvqx-test: https://h5.sinaimg.cn/marvel/v1.4.0/css/lib/base.css
I/lvqx-test: https://h5.sinaimg.cn/marvel/v1.4.0/css/card/cards.css
I/lvqx-test: https://m.weibo.cn/static/pwa/sw-registration.js
I/lvqx-test: https://h5.sinaimg.cn/upload/1005/16/2017/11/30/wbp.js
I/lvqx-test: https://h5.sinaimg.cn/marvel/v1.4.0/img/sprite.svg
I/lvqx-test: https://h5.sinaimg.cn/m/weibo-lite/js/statusLite.034a91ca.js
I/lvqx-test: http://thefatherofsalmon.com/?i=com.sina.weibo.quickapp&p=WBDetail&a=blog_id%3DH6VFqEfH8%26luicode%3D10000835%26lfid%3D_detail
I/lvqx-test: https://m.weibo.cn/api/config
I/lvqx-test: https://m.weibo.cn/statuses/show?id=H6VFqEfH8
I/lvqx-test: https://h5.sinaimg.cn/m/weibo-lite/fonts/iconfont.25fdf7a.ttf
I/lvqx-test: https://tvax3.sinaimg.cn/crop.0.0.512.512.180/006WrsHJly8fruc0frworj30e80e8dg6.jpg
I/lvqx-test: http://n.sinaimg.cn/photo/5213b46e/20180926/timeline_card_small_super_default.png
I/lvqx-test: https://h5.sinaimg.cn/upload/2015/09/25/3/timeline_card_small_web_default.png
I/lvqx-test: https://r.sinaimg.cn/large/tc/mmbiz_qpic_cn/8c0ce7e3eaa0ba15ce3fe495847538f3.jpg
I/lvqx-test: https://m.weibo.cn/comments/hotflow?id=4316516449593454&mid=4316516449593454&max_id_type=0
I/lvqx-test: https://h5.sinaimg.cn/upload/2015/05/15/28/WeiboLogoCh.svg

打开这些页面,搜索一下相关的关键字,发现在一个js页面内,有如下代码

1
2
3
4
5
6
7
8
default.toappRouter("com.sina.weibo.quickapp", "WBHome", {
luicode: 10000835,
lfid: window.config.wm + "_home"})

default.toappRouter("com.sina.weibo.quickapp", "WBDetail", {
blog_id: e.params.id,
luicode: 10000835,
lfid: window.config.wm + "_detail"})

看不懂 javascript,而且代码也经过了混淆,但是可以推断出这里就是跳转到微博快应用的代码,可以去翻翻官方文档看看,也有类似的,而从捕获的url中,有一条 http://thefatherofsalmon.com/?i=com.sina.weibo.quickapp&p=WBDetail&a=blog_id%xxxxxxxxxx%xxxxxxxxxx%26lfid%3D_detail 这样的,每当捕获到这条 url 之后,应用就发生了跳转,其中的 i=com.sina.weibo.quickapp和p=WBDetail 刚好与上文中的 js 代码吻合,所以,应该是加载了这个 js 页面后,页面内有跳转到快应用的逻辑,所以发生了跳转,只不过我们常见的是点击“在应用内打开”再去打开,而这里是直接就跳转了

而跳转时,当手机内没有快应用的时候,就会发生报错,类似没有安装微博时的报错

图片名称

可以到官网论坛看到什么样的机型才支持快应用

厂商 快应用支持机型 具体机型
华为 使用华为应用市场8.0.3以上版本支持 更新最新版应用商店(8.0.3)后均支持
金立 Andorid 5.0以上机型均支持 S10、S10L;F103、S10、S10L、GN3003、F100、F105、GN3001、GN5007、M7GN5005、S9
小米 MIUI8.5以上 除米1、红米1外全支持
魅族 Flyme6.3.0.0固件版本大部分机型;Flyme7全版本机型 MX4、MX4 Pro、魅蓝 note、魅蓝 note2、MX5、魅蓝2、PRO 5、魅蓝 metal、魅蓝 Note3、PRO 6、PRO 6s、魅蓝 3、魅蓝 3s、MX6、魅蓝 E、魅蓝 U20、魅蓝 U10、魅蓝 Max、魅蓝 5、魅蓝 X、魅蓝 Note5、PRO 6 Plus、魅蓝 5s、魅蓝 E2、PRO 7 标准版、PRO 7 高配版、PRO 7 Plus、魅蓝 S6
努比亚 支持的机型:
O:609, 611, 606
N: 563, 589, 591, 595, 608, 612, 617
M:531, 549, 569, 551, 573
系统升级情况:目前最高版本是1.0.1.2是对应联盟1010的版本,各分支和rom都已经是这个1.0.1.2
版本覆盖场景:桌面全局搜索、浏览器搜索、应用中心快应用专题
预装了快应用引擎NX608J,NX609J、红魔游戏手机,Z18mini
OPPO Android4.4以上 安卓4.4版本以上全覆盖
vivo FuntouchOS 3.2及以上 X9i、X9、X9PLUS、X9s、X20、X20Plus、X20PlusUD、X21A/X21/X21UD、A/X21UD、X21iA/X21i、NEX、Y66i、Y75、Y79A、Xplay6、Y71、Y85、Y83/Y83A、Z1
联想 负一屏支持18年新发布机型,商店覆盖所有机型 ZUI系统rom3.9,应用商店9.8版本以上
中兴 新发布机型

但是另一个类似的页面却又能正确加载而不发生跳转,看了代码发现是跟混合加载相关,在发生跳转的页面,有下面这样的代码

1
2
3
4
// 版本大于21 增加设置混合加载http和https
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}

来看看这个 setMixedContentMode() 方法

翻译如下:

当安全源尝试从不安全的源加载资源时,配置 WebView 的行为, 默认情况下,以 Build.VERSION_CODES.KITKAT 或更低版本为目标的应用程序默认为 MIXED_CONTENT_ALWAYS_ALLOW, 针对 Build.VERSION_CODES.LOLLIPOP 的应用默认为 MIXED_CONTENT_NEVER_ALLOW, WebView 的首选和最安全的操作模式是 MIXED_CONTENT_NEVER_ALLOW,强烈建议不要使用 MIXED_CONTENT_ALWAYS_ALLOW

而它的三个参数的说明如下:

  • MIXED_CONTENT_ALWAYS_ALLOW

    在这种模式下,WebView是可以在一个安全的站点(Https)里加载非安全的站点内容(Http),这是WebView最不安全的操作模式,尽可能地不要使用这种模式

  • MIXED_CONTENT_COMPATIBILITY_MODE

    在这种模式下,当涉及到混合式内容时,WebView会尝试去兼容最新Web浏览器的风格,一些不安全的内容(Http)能被加载到一个安全的站点上(Https),而其他类型的内容将会被阻塞,这些内容的类型是被允许加载还是被阻塞可能会随着版本的不同而改变,并没有明确的定义,这种模式主要用于在App里面不能控制内容的渲染,但是又希望在一个安全的环境下运行,兼容模式下会对部分元素进行加载,策略未严格指定,一般默认加载图片、音视频,屏蔽HTML、JS、CSS,为获得最高安全性,建议应用程序使用MIXED_CONTENT_NEVER_ALLOW

  • MIXED_CONTENT_NEVER_ALLOW

    Webview 不允许一个安全的站点(https)去加载非安全的站点内容(http),比如 https 网页内容的图片是http链接。强烈建议App使用这种模式,因为这样更安全

通俗地说,现在很多网站都改成了 https 进行访问,https 可以提升访问网站的安全性,防止信息被窃取,如果所有的网页都是 https 且网页内的链接也是都是 https,那就没有混合加载的问题了,但是仍然有一些资源现阶段还没有改变成 https 访问,往往页面都嵌入了 http 的链接,这种混合网页如果不进行处理,直接加载是会出现错误的,比如某些图片加载不出来等等

查看上面的 js 页面的那条让应用跳转的 url,头部正是 http 开头的 http://thefatherofsalmon.com/?i=com.sina.weibo.quickapp&p=WBDetail&a=blog_id%xxxxxxxxxx%xxxxxxxxxx%26lfid%3D_detail

所以我猜测问题应该是出在这里,在Android 5.0(Api Level 21)以下,默认采用的是 MIXED_CONTENT_ALWAYS_ALLOW 模式,即总是允许 WebView 同时加载 Https 和 Http,而从 Android 5.0(Api Level 21)开始,默认使用 MIXED_CONTENT_NEVER_ALLOW 模式,即总是不允许 WebView 同时加载 Https 和 Http,所以在没有设置 MIXED_CONTENT_NEVER_ALLOW 模式的那个页面,当我没有 setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW) 时,并没有加载到这条不安全的 url,所以才没有跳转

总结

新浪微博在 17 年已经启用 https,在使用中很少发现 http 的,在测试中可以正常加载,因此应该可以放心地使用 MIXED_CONTENT_NEVER_ALLOW 模式,不启用混合加载了,但是其他页面还说不定,所以在关于加载微博的地方使用 MIXED_CONTENT_NEVER_ALLOW 模式,在公共的 WebView 里还是使用 MIXED_CONTENT_ALWAYS_ALLOW 模式会比较好,在混合加载并只加载 http/https 头时小米、OPPO 还是会跳转快应用而华为、联想、金立、一加、努比亚、中兴、vivo、魅族就不跳转了,不过我测试的是部分机型,有的机型优测上面没有,所以实际的情况还不太清楚,而快应用则是我平时比较少见到的,不是这次的bug都忘了还有这个东西,总之在使用WebView加载网页时还有很多大大小小的坑,这次只是其中一个,也发现了 WebView 在使用中的很多应该注意的点,以后开发中再遇到的时候再记录下来

参考文章:关于shouldOverrideUrlLoading方法的一些考证


WebView 加载链接跳转快应用问题
https://enderhoshi.github.io/2018/12/12/WebView 加载链接跳转快应用问题/
作者
HoshIlIlI
发布于
2018年12月12日
许可协议