gpt4 book ai didi

android - 如何异步调用 WebViewClient.shouldInterceptRequest

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:02:58 26 4
gpt4 key购买 nike

我想创建一个 Intranet 应用程序。这个应用程序将显示内容,通常只能在我们的内部环境中访问。例如http://intranet.ourfirm.com

现在我们可以从外部访问此内容例如https://ourproxy.com/ourIntranetApplicationID/ (这将被定向到 http://intranet.ourfirm.com )

我更改了每个原始网址,如 http://intranet.ourfirm.com/whatever/index.htmlhttps://ourproxy.com/ourIntranetApplicationID/whatever/index.html .

在 index.htm 中,多个资源以绝对或相对方式定义。我将它们全部设为绝对并将它们转换为我们的代理 url(请参阅 *1 )(可从我们公司以外的任何地方访问)

一切正常,但有一个大问题。它像 hell 一样缓慢!转换过程在我的 MyWebViewClient.shouldInterceptRequest 方法中启动。

我的 html 有 80 个资源要加载,并且为每个资源顺序调用 shouldInterceptRequest:

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<String, String> 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;
}

如您所见,我在拦截方法的开头使用 AtomicInteger 递增和记录,并在方法结束时记录值。

它总是记录:

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

有了这个,我能够检查 shouldInterceptRequest() 方法永远不会异步启动。如果该方法将被异步调用,则更大的数字@ENTER- Comment 将出现在先前数字的 LEAVE 发生之前。不幸的是,这从未发生过。

对 myRewritingHelper.getConnection() 的调用是非锁定的。

现在我的问题:是否有可能激发 WebviewClient 异步调用其 shouldInterceptRequest() 方法?如果可以异步加载 Web View 的多个资源,我敢肯定这会大大提高性能!Web View 按顺序加载资源。

一个有趣的子问题是,为什么我必须在创建 Web 资源时将 mime 类型定义为 0(参见 *2)。一个电话就像...response = new WebResourceResponse(mime, charset, isContents);... 不起作用。

感谢任何有帮助的回答

已编辑:

myRewritingHelper.getConnection(..) 的方法很快,它只是打开带有附加 http header 的连接:

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;
}

getConnection(..) 方法只消耗几毫秒。

shouldInterceptRequest 方法中最大的“瓶颈”是注释//读取输入之后的 3 次调用

String charset = conn.getContentEncoding() != null
conn.getContentEncoding():Charset.defaultCharset().displayName();
String mime = conn.getContentType();
InputStream is = conn.getInputStream();

这 3 次调用每次最多消耗 2 秒。所以 shouldInterceptRequestMethod() 每次调用消耗超过 2 秒。(这就是我要求异步调用此方法的原因)

Mikhail Naganov 建议进行预取。任何人都可以展示如何预取数据并将数据正确提供给 WebResourceResponse 的示例吗?

如果我使用真正的 mime 类型而不是 null(请参阅 *2)创建 WebResourceResponse,则无法加载内容。 html/文本将在 WebView 中显示为文本。

已编辑 2:Mikhail 建议的解决方案似乎是正确的。但不幸的是它不是:

public class MyWebResourceResponse extends WebResourceResponse {
private String url;
private Context context;
private MyResourceDownloader myResourceDownloader;
private String method;
private Map<String, String> requestHeaders;
private MyWebViewListener myWebViewListener;
private String predefinedEncoding;

public MyWebResourceResponse(Context context, String url, MyResourceDownloader myResourceDownloader, String method, Map<String, String> 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();
}
}

MyWebResourceInputStream 是这样的:

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<String, String> requestHeaders;
private Context context;
private MyWebViewListener myWebViewListener;

public MyWebResourceInputStream(Context context, String url, MyResourceDownloader myResourceDownloader,
String method, Map<String, String> 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 && !MyWebViewClient.getReceived401()) {
LOGGER.debug("- -> 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 && !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();
}

通话发起于

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);
}
}

它带来了性能提升,但它有一个巨大的缺点,即在创建 MyWebResourceResponse 类期间未定义编码。因为直到调用 MyWebResourceInputStream.read() 才建立连接。我发现 webkit 在未建立连接时会先于 getData() 调用 getEncoding(),因此 getEncoding 始终为空。我开始使用预定义编码(取决于 url )定义变通方法。但这离通用解决方案还很远!而且并不是在每种情况下都有效有人知道替代解决方案吗?抱歉 Mikhail 带走了已接受的答案。

最佳答案

资源加载过程包括两个阶段:创建请求作业,然后运行它们以获取数据。 shouldInterceptRequest在第一阶段被调用,这些调用确实按顺序在单个线程上运行。但是当 WebView 的资源加载器接收到请求作业时,它会开始从提供的流中并行加载资源内容。

创建请求作业应该很快,并且不应成为瓶颈。你真的测量过你的shouldInterceptRequest需要多长时间吗?完成?

下一步是检查输入流是否实际上没有相互阻塞。另外,RewritingHelper 是预取内容,还是仅在读取流时按需加载内容?预取有助于提高加载速度。

至于 mime 类型——通常浏览器从响应头中获取它,这就是为什么需要通过 WebResourceResponse 提供它的原因。构造函数。我实际上不确定您在评论中所说的“WebResourceResponse 直接从流中获取类型”是什么意思——流仅包含回复数据,但不包含响应 header 。

更新

因此,从您更新的问题看来,HttpURLConnection 实际上确实加载 shouldInterceptRequest 中的资源。 ,这就是为什么一切都这么慢的原因。你需要做的是定义你自己的类来包装 WebResourceResponse 并且不对构造做任何事情,所以 shouldInterceptRequest执行速度快。实际加载应在之后开始。

我找不到很多关于这项技术的好的代码示例,但是这个似乎或多或少地做了你需要的:https://github.com/mobilyzer/Mobilyzer/blob/master/Mobilyzer/src/com/mobilyzer/util/AndroidWebView.java#L252

我所说的预取是指您从 shouldInterceptRequest 返回后几乎可以立即开始加载数据。 ,不等到 WebView 调用 getData返回的方法 WebResourceResponse .这样,当 WebView 询问您时,您就已经加载了数据。

更新 2

实际上是WebView中的一个问题,它在收到WebResourceResponse的实例后立即查询响应头。来自 shouldInterceptRequest .这意味着如果应用程序想要从网络本身加载资源(例如为了修改它们),加载将永远不会像 WebView 自己加载这些资源时那样快。

应用程序可以做的最好的方法是这样的(代码缺乏适当的异常和错误处理,否则它会大 3 倍):

public WebResourceResponse shouldInterceptRequest(WebView view, final WebResourceRequest request) {
final CountDownLatch haveHeaders = new CountDownLatch(1);
final AtomicReference<Map<String, String>> headersRef = new AtomicReference<>();
final CountDownLatch haveData = new CountDownLatch(1);
final AtomicReference<InputStream> inputStreamRef = new AtomicReference<>();

new Thread() {
@Override
public void run() {
HttpURLConnection urlConnection =
(HttpURLConnection) new URL(request.getUrl().toString()).openConnection();
Map<String, List<String>> rawHeaders = urlConnection.getHeaderFields();
// Copy headers from rawHeaders to headersRef
haveHeaders.countDown();

inputStreamRef.set(new BufferedInputStream(urlConnection.getInputStream()));
haveData.countDown();
}
}.start();

return new WebResourceResponse(
null,
"UTF-8",
new InputStream() {
@Override
public int read() throws IOException {
haveInputStream.await(100, TimeUnit.SECONDS));
return inputStreamRef.get().read();
}) {

@Override
public Map<String, String> getResponseHeaders() {
haveHeaders.await(100, TimeUnit.SECONDS))
return headersRef.get();
}
}
);

关于android - 如何异步调用 WebViewClient.shouldInterceptRequest,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33370123/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com