我想创建一个 Intranet 应用程序。该应用程序将显示通常只能在我们的内部环境中访问的内容。
例如http://intranet.ourfirm.com
现在我们可以从外部访问此内容
例如https://ourproxy.com/ourIntranetApplicationID/(这将被定向到http://intranet.ourfirm.com)
我更改了每个原始网址,例如http://intranet.ourfirm.com/whatever/index.html to
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() 方法永远不会异步启动。
如果该方法被异步调用,则在前一个数字的 LEAVE 发生之前,会出现一个更大的数字@ENTER-Comment。
不幸的是,这从未发生过。
对 myRewritingHelper.getConnection() 的调用是非锁定的。
现在我的问题:是否有可能激发 WebviewClient 异步调用其 shouldInterceptRequest() 方法?
我非常确定,如果可以异步加载 Web 视图的多个资源,这将极大地提高性能!
Web 视图按顺序加载资源。
一个有趣的子问题是,为什么我必须将 Web 资源创建中的 mime-type 定义为 0(参见 *2)。
一个电话就像...
响应 = new WebResourceResponse(mime, 字符集, isContents);
...不起作用。
感谢您提供任何有用的答案
Edited:
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 方法中最大的“瓶颈”是注释 // Read input 之后的 3 个调用
String charset = conn.getContentEncoding() != null
conn.getContentEncoding():Charset.defaultCharset().displayName();
String mime = conn.getContentType();
InputStream is = conn.getInputStream();
这 3 个调用每次最多消耗 2 秒。因此,shouldInterceptRequestMethod() 每次调用都会消耗超过 2 秒的时间。(这就是我要求异步调用此方法的原因)
米哈伊尔·纳加诺夫建议进行预取。任何人都可以展示如何预取数据并将数据正确提供给 WebResourceResponse 的示例吗?
如果我使用真正的 mime 类型而不是 null 创建 WebResourceResponse (请参阅 *2),则无法加载内容。 html/text 将在 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 )。但这距离通用解决方案还很遥远!并且并非在每种情况下都有效
有人知道替代解决方案吗?抱歉米哈伊尔拿走了已接受的答案。