Android WebView注入Js预览图片 - 微信公众号文章

通常的做法是通过document.getElementsByTagName("img"),然后遍历元素拿到src为图片地址。但是针对微信公众号文章就不适用了,尤其是其图片元素采用懒加载方式,所以取到的src为空,但是在微信客户端能够正常取到所有图片,所以对文章进行分析

0x00 文章图片懒加载

Chrome打开一篇微信公众号文章,审查元素,控制台输入document.getElementsByTagName("img")。可以看到正文图片的img标签定义了img_loadingclass属性

此时再看向该标签的src属性,是一张base64loading图。这也就导致通常的做法是获取不到图片地址从而进行预览的

0x01 分析

然而在微信客户端可以正常预览,推测微信应该是在别的属性进行获取,定位到具体的标签看到文章中的img标签都定义了data-srcdata-type属性(这是自定义属性,区分src,方便js调用)

1
2
3
4
5
6
7
8
<img class="img_loading"
data-ratio="0.53375"
data-src="https://mmbiz.qpic.cn/mmbiz_jpg/h5tEWrMy7mrq9iclTia1O8M2C5Se7nr5TgN6IibURS7YYpCSTwT0U5KUhOmGrxusN8iaQKrFDjtTBaMox6Dgp2Hfbg/640?wx_fmt=jpeg"
data-type="jpeg"
data-w="800"
_width="677px"
src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="
style="width: 647px !important; height: 345.336px !important;">

0x02 解决

现在知道问题所在,针对data-x自定义属性可以通过dataset获得,之所以判断dataset.type !== "gif"是为了过滤掉gif,如果你想显示gif,可以去掉

1
2
3
4
5
6
7
8
9
10
11
12
13
function wxImgClick() {
let objs = document.getElementsByTagName("img");
let imgs = [];
for (let i = 0; i < objs.length; i++) {
let dataset = objs[i].dataset;
if (dataset.src && dataset.type !== "gif") {
let index = imgs.push(dataset.src) - 1;
objs[i].onclick = function () {
window.xxxxxx.openImage(imgs, index)
}
}
}
}

0x03 完整例子

可以在webView创建之时调用addJavascriptInterface进行监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
addJavascriptInterface(new JavascriptInterface(getContext().getApplicationContext()), "xxxxxx");

private class JavascriptInterface {
private Context context;

private JavascriptInterface(Context context) {
this.context = context;
}

@android.webkit.JavascriptInterface
public void openImage(String[] imgs, int index) {
// 预览图片操作
}
}

在合适的时候注入Js,我这里是在onPageFinished

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
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
addWXImgClickJs();
}

private void addWXImgClickJs() {
// 用"javascript:(%s)()"包住
String js = "javascript:(function wxImgClick() {\n" +
" let objs = document.getElementsByTagName(\"img\");\n" +
" let imgs = [];\n" +
" for (let i = 0; i < objs.length; i++) {\n" +
" let dataset = objs[i].dataset;\n" +
" if (dataset.src && dataset.type !== \"gif\") {\n" +
" let index = imgs.push(dataset.src) - 1;\n" +
" objs[i].onclick = function () {\n" +
" window.xxxxxx.openImage(imgs, index)\n" +
" }\n" +
" }\n" +
" }\n" +
"})()";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
evaluateJavascript(js, null);
} else {
loadUrl(js);
}
}

evaluateJavascript()是比loadUrl()更高效的注入Js的做法,还支持回调,推荐