WebViewClient shouldInterceptRequest Freezes the W

2019-06-28 09:15发布

问题:

I want to cache images displayed in the WebView for a specific time, for e.g. 7 days, so I needed to both save image caches to disk and load them from disk and provide them to the WebView.

Also I needed to resize the images to avoid the WebView; Crash on High Memory Usage, so I implemented the caching and resizing logic in a Blocking Function named "fetchBitmap".

As the Android documentation states, the "shouldInterceptRequest" runs on a Thread Other than the UI Thread, so I can do networking in this function, but as I can see, a blocking call causes the WebView to Freeze.

The way the function behaves forces me to use a blocking call, and I can't pass a Runnable for e.g. for a future completion.

Any workarounds?

webView.setWebViewClient(new WebViewClient() {

    private boolean isImage(String url) {
        if (url.contains(".jpg") || url.contains(".jpeg")
                    || url.contains(".png")) {
            return true;
        }
        return false;
    }

    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        if (isImage(url)) {
            Bitmap bitmap = ImageUtil.fetchBitmap(url);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            bitmap.compress(CompressFormat.JPEG, 100, bos);
            byte[] bitmapdata = bos.toByteArray();
            ByteArrayInputStream bs = new ByteArrayInputStream(bitmapdata);
            return new WebResourceResponse("image/*", "base64", bs);
        }
        return null;
    }
});

--UPDATE--

OK, I changed the logic, now I just Block Read from the disk, and handle the network load (caching) separately in a runnable; "fetchBitmapForWebView"; it doubles the network load when the cache is not available, but the UI is now more responsive.

public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
    if (isImage(url)) {
        Bitmap bitmap = ImageUtil.fetchBitmapForWebView(url);
        if (bitmap == null) {
            return null;
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        bitmap.compress(CompressFormat.JPEG, 100, bos);
        byte[] bitmapdata = bos.toByteArray();
        ByteArrayInputStream bs = new ByteArrayInputStream(bitmapdata);
        return new WebResourceResponse("image/*", "base64", bs);
    }
    return null;
}

回答1:

The general principle is that you shouldn't do any heavy processing inside shouldInterceptRequest, as it is a synchronous call (albeit on another thread). Calls for all network requests are processed on the same thread so blocking for one request will cause all of the other requests to be blocked as well.

For your use case, I would consider running a small web server inside your app, which will do all the required caching and processing. Surely, you will also need to rewrite all the links inside displayed web pages in order to point back to the local server instead of the original web server, so it may be cumbersome in some situations. But as a general approach this will help greatly with off-loading heavy work and maintaining a local cache.

There are numerous simple Java web servers around, for example:

  • https://github.com/NanoHttpd/nanohttpd
  • https://github.com/nikkiii/embedhttp
  • https://github.com/google/webview-local-server