shouldInterceptRequest 是WebViewClient的一个方法,官方的说明:

* Notify the host application of a resource request and allow the * application to return the data. If the return value is null, the WebView * will continue to load the resource as usual. Otherwise, the return * response and data will be used. NOTE: This method is called on a thread * other than the UI thread so clients should exercise caution * when accessing private data or the view system. * @param view The {@link android.webkit.WebView} that is requesting the * resource. * @param request Object containing the details of the request. * @return A {@link android.webkit.WebResourceResponse} containing the * response information or null if the WebView should load the * resource itself. public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { return shouldInterceptRequest(view, request.getUrl().toString());

大致翻译:
当webview页面有资源请求的时候通知宿主应用,允许应用自己返回数据给webview。如果返回值是null,就正常加载返回的数据,否则就加载应用自己return的response给webview。注意,这个方法回调在子线程而不是UI线程所以在操作私有数据或者view视图的时候要小心。
通常这个方法是用来监控所有的页面请求的,可以用它来监控黑名单以防止页面劫持,当怀疑域名被劫持时,可以通过本地http请求代理,然后将结果返回给webview。下面是我的代码的简化

public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
                try {
                        //走本地代理
                        InputStream is;
                        if(isHocked) {
                            ResponseBody httpResponse = HttpProxyClient.execute(request);
                            is = httpResponse.byteStream();
                            retrun new WebResourceResponse(httpResponse.contentType().type() + "/" + httpResponse.contentType().subtype(),
                        responseBody.contentType().charset().displayName(), is)
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                	if(is!=null){
                		try {
		                   is.close();
		              } catch (IOException e) {
		                   e.printStackTrace();
                return null;

然而当我走到这段代码,网页无一例外,都显示了出错,走到了错误占位页。方的不行,以为是网络请求出了问题,debug跟到httpResponseBody,发现response code为200。但是再往下走,返回了WebResourceResponse之后,仍然走到了错误页。我百思不得其解。打了一堆日志也没有发现有什么问题。
折腾了一天,一筹莫展。偶然的我把日志换成Verbose并且去掉所有过滤,看到了这么一行:

E/InputStreamUtil: Got exception when calling available() on an InputStream returned from shouldInterceptRequest. This will cause the related request to fail.

说是调用从shouldInterceptRequest返回的inputStream 调用available()发生异常,会导致相关的请求失败。看了下这个方法的说明:

* Returns an estimate of the number of bytes that can be read (or * skipped over) from this input stream without blocking by the next * invocation of a method for this input stream. The next invocation * might be the same thread or another thread. A single read or skip of this * many bytes will not block, but may read or skip fewer bytes. * <p> Note that while some implementations of {@code InputStream} will return * the total number of bytes in the stream, many will not. It is * never correct to use the return value of this method to allocate * a buffer intended to hold all data in this stream. * <p> A subclass' implementation of this method may choose to throw an * {@link IOException} if this input stream has been closed by * invoking the {@link #close()} method. * <p> The {@code available} method for class {@code InputStream} always * returns {@code 0}. * <p> This method should be overridden by subclasses. * @return an estimate of the number of bytes that can be read (or skipped * over) from this input stream without blocking or {@code 0} when * it reaches the end of the input stream. * @exception IOException if an I/O error occurs. public int available() throws IOException { return 0;

这个方法是被子类重写的,官方的建议是如果inputstream的close被调用,子类重写时可以在里面抛IO异常。到这里就有些眉目了,有可能是httpResponse.byteStream()返回的inputStream类型,它的available()实现是如果调用了close就抛出异常。而我的代码里在finally中确实调用了close导致的。那么httpResponse.byteStream()返回的inputStream类型是不是真的就是这么实现的呢?byteStream()返回的是BufferSource的inputStream(),
我用的是Okhttp3,跟踪代码发现它使用的是RealBufferSource,继承了BufferSource。它的inputStream 方法的实现:

public InputStream inputStream() {
        return new InputStream() {
            public int read() throws IOException {
                if(RealBufferedSource.this.closed) {
                    throw new IOException("closed");
                } else {
                    if(RealBufferedSource.this.buffer.size == 0L) {
                        long count = RealBufferedSource.this.source.read(RealBufferedSource.this.buffer, 8192L);
                        if(count == -1L) {
                            return -1;
                    return RealBufferedSource.this.buffer.readByte() & 255;
            public int read(byte[] data, int offset, int byteCount) throws IOException {
                if(RealBufferedSource.this.closed) {
                    throw new IOException("closed");
                } else {
                    Util.checkOffsetAndCount((long)data.length, (long)offset, (long)byteCount);
                    if(RealBufferedSource.this.buffer.size == 0L) {
                        long count = RealBufferedSource.this.source.read(RealBufferedSource.this.buffer, 8192L);
                        if(count == -1L) {
                            return -1;
                    return RealBufferedSource.this.buffer.read(data, offset, byteCount);
            public int available() throws IOException {
                if(RealBufferedSource.this.closed) {
                    throw new IOException("closed");
                } else {
                    return (int)Math.min(RealBufferedSource.this.buffer.size, 2147483647L);
            public void close() throws IOException {
                RealBufferedSource.this.close();
            public String toString() {
                return RealBufferedSource.this + ".inputStream()";

可以看到确实available()里面如果调用过close,会抛出IO异常。到此,这个谜题就都解开了。
我把finally中的close操作去掉,果然就能正常打开页面了。

这个问题查了好久没找到原因,是因为对这个机制不熟悉,理解不够充分。从写法上看,在finally里面关闭流是最正常不过的事情了,但是关了就会导致所有走代理的请求web view全都不认。仔细想想,为什么WebResourceResponse的构造入参要有这个么个东西?因为app代理的response要交给webview处理,webview要拿到response里的内容,要么直接丢给它一个Response类型,但是不同的http工具定义的response不是一个东西,webview应该对客户端自己用什么http方式不关心的,所以它只拿了一个inputstream的流。

shouldInterceptRequest 是WebViewClient的一个方法,官方的说明:/** * Notify the host application of a resource request and allow the * application to return the data. If the return value is null, the We...
webp格式图片 webp格式图片是google推出的,相比jpg png有着巨大的优势,同样质量的图片webp格式的图片占用空间更小,在像电商这样图片比较多的App使用webp格式图片会很有优势。 很早之前,我们的项目就已经采用了webp格式,但是由于webView本身并不能解析webp格式,所以我们基于webView的文章详情页就无法使用到这项优化。  那么有没有什么办法能实现呢?当然是有的。 在开始技术讲解之前需要先说明,本文的技术方案,是基于本项目的情况:文章的正文大部分通过接口直接获取到,通过在客户端本地进行html正文组装,最后通过webView的loadHTMLSt
webView.setWebViewClient(new WebViewClient() { @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { try { URL url =
WebView 的 shouldInterceptRequest 可以让我们监听WebView发出的请求并做相应的处理,但有个严重的问题:浏览器的渲染以及资源加载都是在一个线程,如果在shouldInterceptRequest 处理时间过长,WebView界面就会阻塞,这是非常非常不好的。     本人最近遇到个问题,用WebView做显示,里面有个别特定url的图片需要做特殊的加载处理
接到这样一个需求,需要在 WebView 的所有网络请求,在请求的url,加上一个sign=xxxx 的标志位,同时添加手机本地的数据比如 sessionToken=sd54f5sd4ffsdf45454564  、deviceId=863970025919887 例如 http://www.baidu.com 加上标志位就变成了 http://www.baidu.c
WebView 的 shouldInterceptRequest 可以让我们监听WebView发出的请求并做相应的处理,但有个严重的问题:浏览器的渲染以及资源加载都是在一个线程,如果在shouldInterceptRequest 处理时间过长,WebView界面就会阻塞,这是非常非常不好的。     本人最近遇到个问题,用WebView做显示,里面有个别特定url的图片需要做特殊的加载处理。所...
在资源检查,可以通过覆盖shouldOverrideUrlLoading和shouldInterceptRequest方法来检查对内容和/或资源的请求。 但上述两种方法用于不同的目的,例如 1.当新页面即将被打开时调用shouldOverrideUrlLoading,而每次资源被加载的时候都会调用shouldI
HttpServletRequest: HttpServletRequest对象代表用户端浏览器发来的请求,当用户通过HTTP协议访问服务器时,HTTP请求头的所有信息都封装在这个对象,通过这个对象提供的方法,可以获得客户端请求的所有信息。