Android 如何监听一个 WebView 完成加载一个 URL?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3149216/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to listen for a WebView finishing loading a URL?
提问by Janusz
回答by ian
Extend WebViewClientand call onPageFinished() as follows:
扩展WebViewClient并调用onPageFinished()如下:
mWebView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
// do your stuff here
}
});
回答by neteinstein
@ian this is not 100% accurate. If you have several iframes in a page you will have multiple onPageFinished (and onPageStarted). And if you have several redirects it may also fail. This approach solves (almost) all the problems:
@ian 这不是 100% 准确的。如果页面中有多个 iframe,您将有多个 onPageFinished(和 onPageStarted)。如果您有多个重定向,它也可能会失败。这种方法解决了(几乎)所有的问题:
boolean loadingFinished = true;
boolean redirect = false;
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String urlNewString) {
if (!loadingFinished) {
redirect = true;
}
loadingFinished = false;
webView.loadUrl(urlNewString);
return true;
}
@Override
public void onPageStarted(WebView view, String url) {
loadingFinished = false;
//SHOW LOADING IF IT ISNT ALREADY VISIBLE
}
@Override
public void onPageFinished(WebView view, String url) {
if (!redirect) {
loadingFinished = true;
//HIDE LOADING IT HAS FINISHED
} else {
redirect = false;
}
}
});
UPDATE:
更新:
According to the documentation: onPageStarted will NOT be called when the contents of an embedded frame changes, i.e. clicking a link whose target is an iframe.
根据文档:当嵌入框架的内容更改时,不会调用 onPageStarted,即单击目标为 iframe 的链接。
I found a specific case like that on Twitter where only a pageFinished was called and messed the logic a bit. To solve that I added a scheduled task to remove loading after X seconds. This is not needed in all the other cases.
我在 Twitter 上发现了一个类似的特定案例,其中只有一个 pageFinished 被调用并且有点搞乱了逻辑。为了解决这个问题,我添加了一个计划任务以在 X 秒后删除加载。这在所有其他情况下都不需要。
UPDATE 2:
更新 2:
Now with current Android WebView implementation:
现在使用当前的 Android WebView 实现:
boolean loadingFinished = true;
boolean redirect = false;
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(
WebView view, WebResourceRequest request) {
if (!loadingFinished) {
redirect = true;
}
loadingFinished = false;
webView.loadUrl(request.getUrl().toString());
return true;
}
@Override
public void onPageStarted(
WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
loadingFinished = false;
//SHOW LOADING IF IT ISNT ALREADY VISIBLE
}
@Override
public void onPageFinished(WebView view, String url) {
if (!redirect) {
loadingFinished = true;
//HIDE LOADING IT HAS FINISHED
} else {
redirect = false;
}
}
});
回答by SciSpear
I am pretty partial to @NeTeInStEiN (and @polen) solution but would have implemented it with a counter instead of multiple booleans or state watchers (just another flavor but I thought might share). It does have a JS nuance about it but I feel the logic is a little easier to understand.
我非常偏爱@NeTeInStEiN(和@polen)解决方案,但会使用计数器而不是多个布尔值或状态观察器来实现它(只是另一种风格,但我认为可能会分享)。它确实有 JS 的细微差别,但我觉得逻辑更容易理解。
private void setupWebViewClient() {
webView.setWebViewClient(new WebViewClient() {
private int running = 0; // Could be public if you want a timer to check.
@Override
public boolean shouldOverrideUrlLoading(WebView webView, String urlNewString) {
running++;
webView.loadUrl(urlNewString);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
running = Math.max(running, 1); // First request move it to 1.
}
@Override
public void onPageFinished(WebView view, String url) {
if(--running == 0) { // just "running--;" if you add a timer.
// TODO: finished... if you want to fire a method.
}
}
});
}
回答by Skynet
I found one elegant solution as well, have not tested it rigorously though:
我也找到了一个优雅的解决方案,但尚未对其进行严格测试:
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (m_webView.getProgress() == 100) {
progressBar.setVisibility(View.GONE);
m_webView.setVisibility(View.VISIBLE);
}
}
回答by polen
I have simplified NeTeInStEiN's code to be like this:
我已将NeTeInStEiN的代码简化为如下所示:
mWebView.setWebViewClient(new WebViewClient() {
private int webViewPreviousState;
private final int PAGE_STARTED = 0x1;
private final int PAGE_REDIRECTED = 0x2;
@Override
public boolean shouldOverrideUrlLoading(WebView view, String urlNewString) {
webViewPreviousState = PAGE_REDIRECTED;
mWebView.loadUrl(urlNewString);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
webViewPreviousState = PAGE_STARTED;
if (dialog == null || !dialog.isShowing())
dialog = ProgressDialog.show(WebViewActivity.this, "", getString(R.string.loadingMessege), true, true,
new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
// do something
}
});
}
@Override
public void onPageFinished(WebView view, String url) {
if (webViewPreviousState == PAGE_STARTED) {
dialog.dismiss();
dialog = null;
}
}
});
It is easy to understand, OnPageFinished if the previous callback is on onPageStarted, so the page is completely loaded.
很容易理解,OnPageFinished如果前面的回调是onPageStarted,那么页面就完全加载好了。
回答by Francesco Laurita
If you want show a progress bar you need to listen for a progress change event, not just for the completion of page:
如果要显示进度条,则需要侦听进度更改事件,而不仅仅是页面完成:
mWebView.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
//change your progress bar
}
});
BTW if you want display just an Indeterminate ProgressBar overriding the method onPageFinished is enough
顺便说一句,如果您只想显示一个 Indeterminate ProgressBar 覆盖 onPageFinished 方法就足够了
回答by Robby Pond
Use setWebViewClient()and override onPageFinished()
回答by Praveen
You can trace the Progress Staus by the getProgressmethod in webviewclass.
您可以通过webview类中的getProgress方法跟踪进度状态。
Initialize the progress status
初始化进度状态
private int mProgressStatus = 0;
then the AsyncTask for loading like this:
然后是用于加载的 AsyncTask,如下所示:
private class Task_News_ArticleView extends AsyncTask<Void, Void, Void> {
private final ProgressDialog dialog = new ProgressDialog(
your_class.this);
// can use UI thread here
protected void onPreExecute() {
this.dialog.setMessage("Loading...");
this.dialog.setCancelable(false);
this.dialog.show();
}
@Override
protected Void doInBackground(Void... params) {
try {
while (mProgressStatus < 100) {
mProgressStatus = webview.getProgress();
}
} catch (Exception e) {
}
return null;
}
protected void onPostExecute(Void result) {
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
}
}
回答by EscapeNetscape
thanks for the answers. It helped me, but I had to improve it a bit for my needs. I had several pagestarts and finishes so I added a timer which checks if atfer the pagefinish is started a new pagestart. Okay, bad explanation. See the code :)
感谢您的回答。它帮助了我,但我不得不根据我的需要改进它。我有几个页面开始和完成,所以我添加了一个计时器来检查页面完成是否启动了一个新的页面开始。好吧,不好解释。看代码:)
myWebView.setWebViewClient(new WebViewClient() {
boolean loadingFinished = true;
boolean redirect = false;
long last_page_start;
long now;
// Load the url
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!loadingFinished) {
redirect = true;
}
loadingFinished = false;
view.loadUrl(url);
return false;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.i("p","pagestart");
loadingFinished = false;
last_page_start = System.nanoTime();
show_splash();
}
// When finish loading page
public void onPageFinished(WebView view, String url) {
Log.i("p","pagefinish");
if(!redirect){
loadingFinished = true;
}
//call remove_splash in 500 miSec
if(loadingFinished && !redirect){
now = System.nanoTime();
new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
remove_splash();
}
},
500);
} else{
redirect = false;
}
}
private void show_splash() {
if(myWebView.getVisibility() == View.VISIBLE) {
myWebView.setVisibility(View.GONE);
myWebView_splash.setVisibility(View.VISIBLE);
}
}
//if a new "page start" was fired dont remove splash screen
private void remove_splash() {
if (last_page_start < now) {
myWebView.setVisibility(View.VISIBLE);
myWebView_splash.setVisibility(View.GONE);
}
}
});
回答by Mapsy
Here's a novel method for detected when a URL has loaded by utilising Android's capability for JavaScript hooks. Using this pattern, we exploit JavaScript's knowledge of the document's state to generate a native method call within the Android runtime. These JavaScript-accessible calls can be made using the @JavaScriptInterface
annotation.
这是一种利用Android 的 JavaScript 钩子功能来检测 URL 何时加载的新方法。使用这种模式,我们利用 JavaScript 对文档状态的了解在 Android 运行时内生成本地方法调用。可以使用@JavaScriptInterface
注释进行这些 JavaScript 可访问的调用。
This implementation requires that we call setJavaScriptEnabled(true)
on the WebView
's settings, so it might not be suitabledepending on your application's requirements, e.g. security concerns.
实现需要,我们称之为setJavaScriptEnabled(true)
上WebView
的设置,所以它可能不适合取决于应用程序的需求,如安全问题。
src/io/github/cawfree/webviewcallback/MainActivity.java(Jelly Bean, API Level 16)
src/io/github/cawfree/webviewcallback/MainActivity.java (果冻豆,API 级别 16)
package io.github.cawfree.webviewcallback;
/**
* Created by Alex Thomas (@Cawfree), 30/03/2017.
**/
import android.net.http.SslError;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
/** An Activity demonstrating how to introduce a callback mechanism into Android's WebView. */
public class MainActivity extends AppCompatActivity {
/* Static Declarations. */
private static final String HOOK_JS = "Android";
private static final String URL_TEST = "http://www.zonal.co.uk/";
private static final String URL_PREPARE_WEBVIEW = "";
/* Member Variables. */
private WebView mWebView = null;
/** Create the Activity. */
@Override protected final void onCreate(final Bundle pSavedInstanceState) {
// Initialize the parent definition.
super.onCreate(pSavedInstanceState);
// Set the Content View.
this.setContentView(R.layout.activity_main);
// Fetch the WebView.
this.mWebView = (WebView)this.findViewById(R.id.webView);
// Enable JavaScript.
this.getWebView().getSettings().setJavaScriptEnabled(true);
// Define the custom WebClient. (Here I'm just suppressing security errors, since older Android devices struggle with TLS.)
this.getWebView().setWebViewClient(new WebViewClient() { @Override public final void onReceivedSslError(final WebView pWebView, final SslErrorHandler pSslErrorHandler, final SslError pSslError) { pSslErrorHandler.proceed(); } });
// Define the WebView JavaScript hook.
this.getWebView().addJavascriptInterface(this, MainActivity.HOOK_JS);
// Make this initial call to prepare JavaScript execution.
this.getWebView().loadUrl(MainActivity.URL_PREPARE_WEBVIEW);
}
/** When the Activity is Resumed. */
@Override protected final void onPostResume() {
// Handle as usual.
super.onPostResume();
// Load the URL as usual.
this.getWebView().loadUrl(MainActivity.URL_TEST);
// Use JavaScript to embed a hook to Android's MainActivity. (The onExportPageLoaded() function implements the callback, whilst we add some tests for the state of the WebPage so as to infer when to export the event.)
this.getWebView().loadUrl("javascript:" + "function onExportPageLoaded() { " + MainActivity.HOOK_JS + ".onPageLoaded(); }" + "if(document.readyState === 'complete') { onExportPageLoaded(); } else { window.addEventListener('onload', function () { onExportPageLoaded(); }, false); }");
}
/** Javascript-accessible callback for declaring when a page has loaded. */
@JavascriptInterface @SuppressWarnings("unused") public final void onPageLoaded() {
// Display the Message.
Toast.makeText(this, "Page has loaded!", Toast.LENGTH_SHORT).show();
}
/* Getters. */
public final WebView getWebView() {
return this.mWebView;
}
}
res/layout/activity_main.xml
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<WebView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Essentially, we're appending an additional JavaScript function that is used to test the state of the document. If it's loaded, we launch a custom onPageLoaded()
event in Android's MainActivity
; otherwise, we register an event listener that updates Android once the page is ready, using window.addEventListener('onload', ...);
.
本质上,我们附加了一个额外的 JavaScript 函数,用于测试文档的状态。如果它已加载,我们将onPageLoaded()
在 Android 中启动一个自定义事件MainActivity
;否则,我们注册一个事件侦听器,在页面准备好后更新 Android,使用window.addEventListener('onload', ...);
.
Since we're appending this script after the call to this.getWebView().loadURL("")
has been made, it's probable that we don't need to 'listen'for the events at all, since we only get a chance to append the JavaScript hook by making a successive call to loadURL
, once the page has already been loaded.
由于我们在调用 之后附加此脚本this.getWebView().loadURL("")
,因此我们可能根本不需要“监听”事件,因为我们只有通过连续调用来获得附加 JavaScript 钩子的机会loadURL
, 一旦页面已经加载。