- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我想创建一个 Intranet 应用程序。这个应用程序将显示内容,通常只能在我们的内部环境中访问。例如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 )(可从我们公司以外的任何地方访问)
一切正常,但有一个大问题。它像 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/
为了让我的代码几乎完全用 Jquery 编写,我想用 Jquery 重写 AJAX 调用。 这是从网页到 Tomcat servlet 的调用。 我目前情况的类似代码: var http = new
我想使用 JNI 从 Java 调用 C 函数。在 C 函数中,我想创建一个 JVM 并调用一些 Java 对象。当我尝试创建 JVM 时,JNI_CreateJavaVM 返回 -1。 所以,我想知
环顾四周,我发现从 HTML 调用 Javascript 函数的最佳方法是将函数本身放在 HTML 中,而不是外部 Javascript 文件。所以我一直在网上四处寻找,找到了一些简短的教程,我可以根
我有这个组件: import {Component} from 'angular2/core'; import {UserServices} from '../services/UserService
我正在尝试用 C 实现一个简单的 OpenSSL 客户端/服务器模型,并且对 BIO_* 调用的使用感到好奇,与原始 SSL_* 调用相比,它允许一些不错的功能。 我对此比较陌生,所以我可能会完全错误
我正在处理有关异步调用的难题: 一个 JQuery 函数在用户点击时执行,然后调用一个 php 文件来检查用户输入是否与数据库中已有的信息重叠。如果是这样,则应提示用户确认是否要继续或取消,如果他单击
我有以下类(class)。 public Task { public static Task getInstance(String taskName) { return new
嘿,我正在构建一个小游戏,我正在通过制作一个数字 vector 来创建关卡,该数字 vector 通过枚举与 1-4 种颜色相关联。问题是循环(在 Simon::loadChallenge 中)我将颜
我有一个java spring boot api(数据接收器),客户端调用它来保存一些数据。一旦我完成了数据的持久化,我想进行另一个 api 调用(应该处理持久化的数据 - 数据聚合器),它应该自行异
首先,这涉及桌面应用程序而不是 ASP .Net 应用程序。 我已经为我的项目添加了一个 Web 引用,并构建了各种数据对象,例如 PayerInfo、Address 和 CreditCard。但问题
我如何告诉 FAKE 编译 .fs文件使用 fsc ? 解释如何传递参数的奖励积分,如 -a和 -target:dll . 编辑:我应该澄清一下,我正在尝试在没有 MSBuild/xbuild/.sl
我使用下划线模板配置了一个简单的主干模型和 View 。两个单独的 API 使用完全相同的配置。 API 1 按预期工作。 要重现该问题,请注释掉 API 1 的 URL,并取消注释 API 2 的
我不确定什么是更好的做法或更现实的做法。我希望从头开始创建目录系统,但不确定最佳方法是什么。 我想我在需要显示信息时使用对象,例如 info.php?id=100。有这样的代码用于显示 Game.cl
from datetime import timedelta class A: def __abs__(self): return -self class B1(A):
我在操作此生命游戏示例代码中的数组时遇到问题。 情况: “生命游戏”是约翰·康威发明的一种细胞自动化技术。它由一个细胞网格组成,这些细胞可以根据数学规则生存/死亡/繁殖。该网格中的活细胞和死细胞通过
如果我像这样调用 read() 来读取文件: unsigned char buf[512]; memset(buf, 0, sizeof(unsigned char) * 512); int fd;
我用 C 编写了一个简单的服务器,并希望调用它的功能与调用其他 C 守护程序的功能相同(例如使用 ./ftpd start 调用它并使用 ./ftpd stop 关闭该实例)。显然我遇到的问题是我不知
在 dos 中,当我粘贴此命令时它会起作用: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" https://google.
在 dos 中,当我粘贴此命令时它会起作用: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" https://google.
我希望能够从 cmd 在我的 Windows 10 计算机上调用 python3。 我已重新安装 Python3.7 以确保选择“添加到路径”选项,但仍无法调用 python3 并使 CMD 启动 P
我是一名优秀的程序员,十分优秀!