腾讯X5内核 WebView 实践篇

基于腾讯X5内核的WebView开发:
1.onPageFinished
2.onProgressChanged()
3.合适的时机获取页面元素

onPageFinished() 踩坑

业务需求是在 html 中注入 js脚本实现阅读模式切换功能。但是基于开源项目 mozilla/readability 开发,项目脚本过长,如果不能选定合适的时机注入,那么势必会影响用户交互。

在实现需求阶段,实现方式是在 onPageFinished() 注入 js。注入代码完成之后,在页面 ready() 状态中主动去通知客户端当前页面内容加载完成,并且是否可以切换阅读模式。根据前端的状态返回来确定客户端的表现。当然,这个过程是缓慢的,所以不建议直接加载阅读模式的html

注入代码留存:

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
private void injectScriptFile(X5WebView webView, String scriptFile) {
InputStream input;
try {
input = getActivity().getAssets().open(scriptFile);
byte[] buffer = new byte[input.available()];
input.read(buffer);
input.close();

// String-ify the script byte-array using BASE64 encoding !!!
String encoded = Base64.encodeToString(buffer, Base64.NO_WRAP);
webView.loadUrl("javascript:(function() {" +
"var scriptElement = document.getElementById('readability-script');" +
//"alert(scriptElement);" +
"if(!scriptElement) {" +
"var parent = document.getElementsByTagName('head').item(0);" +
"var script = document.createElement('script');" +
"script.type = 'text/javascript';" +
// "script.async = 'true';" +
"script.id = 'readability-script';" +
// Tell the browser to BASE64-decode the string into your script !!!
"script.innerHTML = window.atob('" + encoded + "');" +
"parent.appendChild(script);}" +
"})()");
} catch (IOException e) {
e.printStackTrace();
}
}

在功能完成之后,我们发现,有的网页出现解析代码运行之后通知客户端是可读的,但是我们并不能获取到展示的文本信息。导致切换阅读模式出现空白页面的情况。

这个Bug目前还没有解决,规避方案是当前页面的检测结果为可切换阅读模式页面并且可阅读文本不为空时,我们才会通知客户端该页面支持阅读模式的状态。我们目前选择了规避。

在页面调试时发现,上述的script标签内容导入了两次,虽然能够正常运行,但是无疑是增加了WebView消耗。

经过排查发现,在新网页开启时,会立即调用onPageStarted()onPageFinished()方法,这个时候会第一次注入js,当页面完全加载完,WebView还是会回调一次onPageFinished()方法。那么这就是第二次的注入时机。
上面的分析合情合理,但是真实原因并不是这个,因为我们发现在第一次onPageFinished()调用的时候,我们在注入js之后会去主动调用注入的一个方法,通过 chrome://inspect 调试发现,这个时候调用的方法是Undefined状态,所以说我们的注入是失败的。那么也就说明一个猜测,第一次回调onPageFinished()时,网页并没有完成html基本的格式加载。为什么在没有完成加载最基础的标签时,我们会收到onPageFinished()回调呢,这简直就是一个大坑,这个问题还没有深入研究,待后续再说吧。

排除了上述这个原因,那么为什么会出现js导入多次的情况呢?

由于本人并不了解前端开发,所以出现这个问题才知晓了单页面应用的概念,具体的可以自行了解单页应用程序

在单页面应用中,页面跳转中 <header/> 标签内容是一直存在的。在主页注入<script/>标签之后,在后续的onPageFinished()方法中重复导入。就会造成注入冗余的情况。

终于找到原因所在,那么上述的注入代码中,修改了js部分的代码,先去确定标签不存在再去注入。

总结:
单页面应用在跳转时只会刷新<body/>内的信息,当然其他标签如果有更新也会变化,暂不考虑。而<header/>中注>入js的动作执行了两次,所以导致了<script/>标签冗余。

这里还有个坑,在单页应用跳转时除了第一次加载外框页面会回调onPageStarted(),之后的内部页面跳转是没有这个回调的。

onProgressChanged()

WebView 可以通过设置 WebChromeClient 来监听页面资源的加载情况,本文主要用onProgressChanged()方法。

在上面已经说过,需要注入的js过长,如果放在页面加载完成再去注入,是否很不合理。所以我们希望可以在页面加载过程中,去异步的注入。

最理想的状态是页面基本html加载完成之后,给客户端一个状态通知,然后这个时机去异步注入(想想罢了,不知道前端技术人员是否能够感知到这个状态)。

采取了大众方案,在onProgressChanged(WebView webView, int newProgress)回调的30%去注入。当然具体的进度值还需要自己去测试。

通过logcat你会发现,这里的进度显示也很诡异。

逼不得已,添加了一个flag来控制,在第一次导入开始时关闭,在onPageStarted()中打开。

时机

既然WebView的回调方法这么不靠谱,那么我们就不能将核心功能依赖这两个不靠谱的方法。

由于阅读模式的检测是需要解析页面元素的,那么前端小伙伴是否能够在能够解析并解析完成之后将结果主动地下发到客户端呢?

前端我只是了解了一个ready()的方法,是可以监听到页面加载完成的,前端发送一个跳转给客户端,客户端需要通过shouldOverrideUrlLoading(WebView webView, String url)方法主动去拦截前端的跳转,然后根据需求,可以去完成对应的具体功能。

这部分还在研究,看看前端是否有更加精准的方式来通知客户端。