我想创建一个Intranet-Application。此应用程序将显示内容,通常只能在我们的内部环境中访问。 例如http://intranet.ourfirm.com
现在我们可以从外部访问此内容 例如https://ourproxy.com/ourIntranetApplicationID/(这将转至http://intranet.ourfirm.com)
我将http://intranet.ourfirm.com/whatever/index.html之类的每个原始网址都更改为 https://ourproxy.com/ourIntranetApplicationID/whatever/index.html
在index.htm中,以绝对或相对方式定义了几个资源。 我将它们全部变为绝对并将它们转换为我们的代理URL(参见* 1)(可从我们公司外的任何地方访问)
这一切都很完美,但有一个大问题。它像地狱一样缓慢! 转换过程在我的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()方法永远不会异步获取startet。 如果异步调用该方法,则会在前一个数字的LEAVE出现之前出现更大的数字@ ENTER- Comment。 不幸的是,这从未发生过。
对myRewritingHelper.getConnection()的调用是非锁定的。
现在我的问题: 是否有可能激发WebviewClient异步调用其shouldInterceptRequest()方法? 如果可以异步加载Web视图的多个资源,我很确定这会大大改善性能! Web视图按顺序在资源之后加载资源。
一个有趣的子问题是,为什么我必须在创建Web资源中将mime-type定义为0(参见* 2)。 像......这样的电话 response = new WebResourceResponse(mime,charset,isContents); ......不起作用。
感谢您提供任何有用的答案
编辑:
myRewritingHelper.getConnection(..)的方法很快,只需打开附加的http标头的连接:
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 -type而不是null(参见* 2)创建WebResourceResponse,则无法加载内容。 html /文本将在WebView中显示为文本。
已编辑2: 米哈伊尔建议的解决方案似乎是正确的解决方案。 但不幸的是,它不是:
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始终为null。 我开始使用预定义的编码定义一个变通方法(取决于url)。但这远非一般的解决方案!并且在每种情况下都不起作用 有人知道另一种解决方案吗?对不起米哈伊尔取消了接受的答案。
答案 0 :(得分:8)
资源加载过程包括两个阶段:创建请求作业,然后运行它们以获取数据。在第一阶段调用shouldInterceptRequest
,这些调用确实按顺序在单个线程上运行。但是当WebView的资源加载器收到请求作业时,它会开始并行地从提供的流中加载资源内容。
创建请求作业应该很快,并且它不应该成为瓶颈。您是否实际测量了shouldInterceptRequest
完成所需的时间?
下一步是检查输入流是否实际上没有相互阻塞。此外,RewritingHelper是否预先获取内容,还是仅在读取流时按需加载它们?预取可以帮助提高加载速度。
至于mime类型 - 通常浏览器从响应标头中获取它,这就是为什么需要通过WebResourceResponse
构造函数提供它。我实际上不确定你是什么意思&#34; WebResourceResponse直接从流中获取类型&#34;在您的评论中 - 流只包含回复的数据,但不包含响应标题。
<强>更新强>
因此,从您更新的问题看来,似乎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
的{{1}}实例后立即查询响应标头。这意味着如果应用程序想要从网络本身加载资源(例如,用于修改它们),那么加载将永远不会像WebView本身加载这些资源那样快。
应用程序可以做的最好的方法就是这样(代码缺少正确的异常和错误处理,否则它会大3倍):
shouldInterceptRequest