Evil Mouth's Blog

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

August 21, 2018

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

0x00 文章图片懒加载

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

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

0x01 分析

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

<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,可以去掉

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进行监听

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

@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 的做法,还支持回调,推荐

— Evil Mouth