WebViewClient.shouldInterceptRequest是在UI线程中执行的,如果资源加载出现堵塞,展示时就会堵塞,所以需要异步加载外部资源。
在网上搜的全是用PipeInputStream/PipeOutputStream实现,测试后,发现这种方法在out输出内容时,PipeInputStream已经关闭。所以改成重载InputStream实现,测试通过。
下面例子是用http访问远程的资源,其中的http使用OkHttp实现的,并不难,所以没有贴这部分代码:
public static class UrlInputStream extends InputStream {
private final String url;
private final Map<String, String> headers;
private byte[] stream = null;
private int i = 0;
private int len = 0;
public UrlInputStream(String url, Map<String, String> headers) {
this.url = url;
this.headers = headers;
}
@Override
public int read() throws IOException {
if (stream == null) { //stream初始化必须放在read中,放在构造函数中,就会阻塞调用线程
Request req = http.buildGet(url, headers,null, null);
try (Response resp = http.syncSend(req);
ResponseBody body = resp.body()){
stream = body.bytes();
len = stream.length;
LOG.debug("read {}, len:{}", url, len);
} catch (IOException e) {
LOG.error("Fail to get {}", url, e);
}
}
if (i >= len) {
return -1;
}
return (((int)stream[i++]) & 0xff); //此处小心,必须强转后去除高位,否则可能会返回负数
}
}
在WebViewClient.shouldInterceptRequest中使用UrlInputStream:
InputStream in = new HttpClient.UrlInputStream(url, null);
return new WebResourceResponse(mimeType, DEFAULT_CHARSET_NAME, in);