title: How to get WebViewClient.shouldInterceptRequest invoked asynchronously
body: <p>I want to create an Intranet-Application. This app is going to show content, normally only reachable in our internal environment.
  e.g. <a href="http://intranet.ourfirm.com" rel="noreferrer">http://intranet.ourfirm.com</a></p>
  
  <p>Now we are having the possibility to access this content from external 
  e.g. <a href="https://ourproxy.com/ourIntranetApplicationID/" rel="noreferrer">https://ourproxy.com/ourIntranetApplicationID/</a>   (this will be directed to <a href="http://intranet.ourfirm.com" rel="noreferrer">http://intranet.ourfirm.com</a>)</p>
  
  <p>I change every original url like <a href="http://intranet.ourfirm.com/whatever/index.html" rel="noreferrer">http://intranet.ourfirm.com/whatever/index.html</a> to 
  <a href="https://ourproxy.com/ourIntranetApplicationID/whatever/index.html" rel="noreferrer">https://ourproxy.com/ourIntranetApplicationID/whatever/index.html</a>.</p>
  
  <p>In the index.htm several resources are defined either in an absolute or relative way.
  I make them all absolute and convert them to our proxy url(see *1 ) (reachable from everywhere outside our firm)</p>
  
  <p>This all is working perfectly, but with one big issue. It is slow like hell!
  the process of conversion is initiated in my MyWebViewClient.shouldInterceptRequest method.</p>
  
  <p>my html has 80 Resources to be loaded and shouldInterceptRequest is called sequentially for each resource:</p>
  
  <pre><code>public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
          LOGGER.debug("ENTER shouldInterceptRequest: " + String.format("%012d", interceptCounter.incrementAndGet()));
          WebResourceResponse response;
  
  
      HttpURLConnection conn;
  
          try {
              conn = myRewritingHelper.getConnection(request.getUrl(), method); // *1 this internally converts the url and gets a connection adds the header for Basic Auth etc.
  
              // add request headers
              for (Map.Entry&lt;String, String&gt; entry : request.getRequestHeaders().entrySet()) {
                  conn.setRequestProperty(entry.getKey(), entry.getValue());
              }
  
              // Read input
              String charset = conn.getContentEncoding() != null ? conn.getContentEncoding() : Charset.defaultCharset().displayName();
              String mime = conn.getContentType();
              InputStream is = conn.getInputStream();
  
  
  
              long interceptStopTimestamp = System.currentTimeMillis();
              long durationIntercepting = interceptStopTimestamp - interceptStartTimestamp;
              LOGGER.info("InterceptionDuration : " + durationIntercepting);
  
              // *2 we have to define null for the mime-type , so the WebResourceResponse gets the type directly from the stream
              response = new WebResourceResponse(null, charset, isContents);
          } catch (IllegalStateException e) {
              LOGGER.warn("IllegalStateException", e);
  
          } catch (IOException e) {
              LOGGER.warn("IOException: Could not load resource: " + url, e);
          }
  
  
          LOGGER.debug("LEAVE shouldInterceptRequest: " + String.format("%012d", interceptCounter.get()));
          return response;
      }
  </code></pre>
  
  <p>As you can see, I use an AtomicInteger incrementing and logging at beginning of the interception method,
  and log the value at the end of the method.</p>
  
  <p>It always logs:</p>
  
  <pre><code>ENTER shouldInterceptRequest:  000000000001
  LEAVE shouldInterceptRequest:  000000000001
  ENTER shouldInterceptRequest:  000000000002
  LEAVE shouldInterceptRequest:  000000000002
  ENTER shouldInterceptRequest:  000000000003
  LEAVE shouldInterceptRequest:  000000000003
  ENTER shouldInterceptRequest:  000000000004
  LEAVE shouldInterceptRequest:  000000000004
  :
  :
  ENTER shouldInterceptRequest:  000000000080
  LEAVE shouldInterceptRequest:  000000000080
  </code></pre>
  
  <p>With this I was able to check that the shouldInterceptRequest() method never get startet asynchonously.
  If the method would get called asynchronously a bigger number @ ENTER- Comment would appear before a LEAVE of the prior number would occure. 
  This unfortunately never happened. </p>
  
  <p>The call to myRewritingHelper.getConnection() is non-locking.</p>
  
  <p>Now my Question:
  <strong>Is there a possibility to provoke the WebviewClient to call its shouldInterceptRequest() method asynchonously?
  I'm quite sure this would massively improove the performance, if several resources of the Web view could be loaded asynchonously!
  The Web view loads resource after resource sequentially.</strong></p>
  
  <p>An interesting sub-question is, why i have to define the mime-type in the Creation of the Web Resource to 0 (see *2).
  A call like...
  response = new WebResourceResponse(mime, charset, isContents);
  ... doesn't work.</p>
  
  <p>Thanks for any helpful answers</p>
  
  <p><strong>Edited:</strong></p>
  
  <p>The method of myRewritingHelper.getConnection(..) is fast, it simply opens the connection with appended http headers:</p>
  
  <pre><code>private HttpURLConnection getConnection(String url, String httpMethod) throws MalformedURLException, IOException {
  
          String absoluteRewrittenUrl = urlConfigurationManager.getRewritedUrl(url); // this gets a rewritten url
  
          final HttpURLConnection connection = (HttpURLConnection) new URL(absoluteRewrittenUrl).openConnection();
          connection.setRequestMethod(httpMethod);
          connection.setConnectTimeout(CONNECTION_TIMEOUT_MS);
          connection.setReadTimeout(SOCKET_TIMEOUT_MS);
          connection.setRequestProperty("AUTHORIZATION",getBasicAuthentication());
  
          return connection;
      }
  </code></pre>
  
  <p>The getConnection(..) method only consumes a couple of milliseconds.</p>
  
  <p>The great "bottleneck" in the shouldInterceptRequest method are the 3 calls after the comment // Read input</p>
  
  <pre><code>String charset = conn.getContentEncoding() != null
  conn.getContentEncoding():Charset.defaultCharset().displayName();
  String mime = conn.getContentType();
  InputStream is = conn.getInputStream();
  </code></pre>
  
  <p>Those 3 calls consume up to 2Seconds each time. So the shouldInterceptRequestMethod() consumes more than 2 seconds each call.(That was the reason I asked to invoke this method asynchronously)</p>
  
  <p><strong>Mikhail Naganov suggested to do a pre-fetch. Can anybody show an example of how to prefetching and give the data properly to the WebResourceResponse?</strong></p>
  
  <p>if I create the WebResourceResponse with the real mime -type instead of null (see *2) then the content cant be loaded. An html/text will be displayed as text in the WebView.</p>
  
  <p><strong>Edited 2:</strong>
  The suggested solution from Mikhail seemed to be the right one. 
  But unfortunately it is not:</p>
  
  <pre><code>public class MyWebResourceResponse extends WebResourceResponse {
      private String url;
      private Context context;
      private MyResourceDownloader myResourceDownloader;
      private String method;
      private Map&lt;String, String&gt; requestHeaders;
      private MyWebViewListener myWebViewListener;
      private String predefinedEncoding;
  
  public MyWebResourceResponse(Context context, String url, MyResourceDownloader myResourceDownloader, String method, Map&lt;String, String&gt; requestHeaders, MyWebViewListener myWebViewListener,String predefinedEncoding) {
          super("", "", null);
          this.url = url;
          this.context = context;
          this.myResourceDownloader = myResourceDownloader;
          this.method = method;
          this.requestHeaders = requestHeaders;
          this.myWebViewListener = myWebViewListener;
          this.predefinedEncoding = predefinedEncoding;
      }
  
      @Override
      public InputStream getData() {
          return new MyWebResourceInputStream(context, url, myResourceDownloader, method, requestHeaders, myWebViewListener);
      }
  
      @Override
      public String getEncoding() {
          if(predefinedEncoding!=null){
              return predefinedEncoding;
          }
          return super.getEncoding();
      }
  
      @Override
      public String getMimeType() {
          return super.getMimeType();
      }
  }
  </code></pre>
  
  <p>The MyWebResourceInputStream is like this:</p>
  
  <pre><code>public class MyWebResourceInputStream extends InputStream {
      private static final Logger LOGGER = LoggerFactory.getLogger(MyWebResourceInputStream.class);
      public static final int NO_MORE_DATA = -1;
      private String url;
      private boolean initialized;
      private InputStream inputStream;
      private MyResourceDownloader myResourceDownloader;
      private String method;
      private Map&lt;String, String&gt; requestHeaders;
      private Context context;
      private MyWebViewListener myWebViewListener;
  
  public MyWebResourceInputStream(Context context, String url, MyResourceDownloader myResourceDownloader,
              String method, Map&lt;String, String&gt; requestHeaders, MyWebViewListener myWebViewListener) {
          this.url = url;
          this.initialized = false;
          this.myResourceDownloader = myResourceDownloader;
          this.method = method;
          this.requestHeaders = requestHeaders;
          this.context = context;
          this.myWebViewListener = myWebViewListener;
      }
  @Override
      public int read() throws IOException {
          if (!initialized &amp;&amp; !MyWebViewClient.getReceived401()) {
              LOGGER.debug("- -&gt; read ENTER *****");
              try {
                  InterceptingHelper.InterceptingHelperResult result = InterceptingHelper.getStream(context, myResourceDownloader, url, method, requestHeaders, false);
                  inputStream = result.getInputstream();
                  initialized = true;
              } catch (final UnexpectedStatusCodeException e) {
                  LOGGER.warn("UnexpectedStatusCodeException", e);
                  if (e.getStatusCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
                      MyWebViewClient.setReceived401(true);
                      if (myWebViewListener != null) {
                          myWebViewListener.onReceivedUnexpectedStatusCode(e.getStatusCode());
                      }
                      LOGGER.warn("UnexpectedStatusCodeException received 401", e);
                  }
              } catch (IllegalStateException e) {
                  LOGGER.warn("IllegalStateException", e);
              }
          }
          if (inputStream != null &amp;&amp; !MyWebViewClient.getReceived401()) {
              return inputStream.read();
          } else {
              return NO_MORE_DATA;
          }
  
      }
  @Override
      public void close() throws IOException {
          if (inputStream != null) {
              inputStream.close();
          }
      }
  @Override
      public long skip(long byteCount) throws IOException {
          long skipped = 0;
          if (inputStream != null) {
              skipped = inputStream.skip(byteCount);
          }
          return skipped;
      }
  @Override
      public synchronized void reset() throws IOException {
          if (inputStream != null) {
              inputStream.reset();
          }
      }
  @Override
      public int read(byte[] buffer) throws IOException {
          if (inputStream != null) {
              return inputStream.read(buffer);
          }
          return super.read(buffer);
      }
  @Override
      public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
          if (inputStream != null) {
              return inputStream.read(buffer, byteOffset, byteCount);
          }
          return super.read(buffer, byteOffset, byteCount);
      }
   public int available() throws IOException {
          if (inputStream != null) {
              return inputStream.available();
          }
          return super.available();
      }
  
  public synchronized void mark(int readlimit) {
          if (inputStream != null) {
              inputStream.mark(readlimit);
          }
          super.mark(readlimit);
      }
   @Override
      public boolean markSupported() {
          if (inputStream != null) {
              return inputStream.markSupported();
          }
          return super.markSupported();
      }
  </code></pre>
  
  <p>the call is initiated in </p>
  
  <pre><code>MyWebViewClient extends WebViewClient{
  
      public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request){
      // a lot of other code
      String predefinedEncoding = getPredefinedEncodingFromUrl(url);
              return new MyWebResourceResponse(context, url, myResourceDownloader, method, requestHeaders, webViewListener, predefinedEncoding);
    }
  }
  </code></pre>
  
  <p>it brought a performance boost, but it has the huge drawback that the encoding is not defined during creation of the MyWebResourceResponse class. Because the connection gets established not until MyWebResourceInputStream.read() is called. 
  I have discovered that the webkit calls getEncoding() prior to getData(), when the connection is not established, so it all the time getEncoding will be null.
  I started to define a Workaround with a predefined encoding (depending on the url ). But that's far away from a generic solution!And does not work in each case
  Does anybody known an alternative solution? Sorry Mikhail for taking away the accepted answer.</p>