如何监听WebView完成加载URL?

我有一个WebView,它正在从互联网加载一个页面。我想显示一个ProgressBar,直到加载完成。

如何监听WebView的页面加载完成?

401960 次浏览

扩展WebViewClient并调用onPageFinished()如下所示:

mWebView.setWebViewClient(new WebViewClient() {


public void onPageFinished(WebView view, String url) {
// do your stuff here
}
});

你可以通过webview类中的getProgress方法跟踪进度状态。

初始化进度状态

private int mProgressStatus = 0;

然后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();
}
}
}

如果你想显示一个进度条,你需要监听一个进度变化事件,而不仅仅是页面的完成:

mWebView.setWebChromeClient(new WebChromeClient(){


@Override
public void onProgressChanged(WebView view, int newProgress) {


//change your progress bar
}




});

顺便说一句,如果你只想显示一个覆盖onPageFinished方法的Indeterminate ProgressBar就足够了

@ian这不是百分之百准确的。如果你有几个iframe在一个页面,你将有多个onPageFinished(和onpagstarted)。如果你有几个重定向,它也可能失败。这种方法解决了(几乎)所有问题:

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

更新:

根据文档: 当嵌入帧的内容发生变化时,即点击目标为iframe的链接时,onpagstarted将不会被调用

我在Twitter上发现了一个类似的特定情况,其中只调用了pageFinished,逻辑有点混乱。为了解决这个问题,我添加了一个计划任务,在X秒后删除加载。这在所有其他情况下都不需要。

更新2:

现在使用当前的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;
}
}
});

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


}
});

这很容易理解,OnPageFinished如果之前的回调是onpagstarted,所以页面是完全加载的。

我非常倾向于@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.
}
}
});
}

谢谢你的回答。它帮助了我,但我必须根据我的需要稍加改进。我有几个页面开始和结束,所以我添加了一个定时器,检查是否在页面结束后开始一个新的页面开始。好吧,糟糕的解释。请参阅代码:)

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


});

我发现了一个优雅的解决方案,虽然没有严格测试:

public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (webView.getProgress() == 100) {
progressBar.setVisibility(View.GONE);
webView.setVisibility(View.VISIBLE);
}
}
< p > 使用__ABC0和ProgressBar加载url:

UrlPageActivity.java: < / >强

    WebView webView;
SwipeRefreshLayout _swipe_procesbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_url_page);




String url = "http://stackoverflow.com/";


_swipe_procesbar = (SwipeRefreshLayout)findViewById(R.id.url_path_swipe_procesbar);


_swipe_procesbar.post(new Runnable() {
@Override
public void run() {
_swipe_procesbar.setRefreshing(true);
}
}
);


webView = (WebView) findViewById(R.id.url_page_web_view);
webView.getSettings().setJavaScriptEnabled(true);


webView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
_swipe_procesbar.setRefreshing(false);
_swipe_procesbar.setEnabled(false);
}
});
webView.loadUrl(url);
}

activity_url_page.xml:

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/url_path_swipe_procesbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.test.test1.UrlPageActivity">




<WebView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/url_page_web_view" />
</RelativeLayout>


</android.support.v4.widget.SwipeRefreshLayout>

这里有一个使用Android的JavaScript钩子功能检测URL何时加载的新方法。使用这种模式,我们利用JavaScript对文档状态的了解在Android运行时内生成一个本机方法调用。这些javascript可访问的调用可以使用@JavaScriptInterface注释进行。

这个实现要求我们在WebView的设置上调用setJavaScriptEnabled(true),所以它可能不合适取决于你的应用程序的需求,例如安全问题。

src / io / github / cawfree / webviewcallback / MainActivity.java (糖豆,空气污染指数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

<?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"
/>

实际上,我们添加了一个额外的JavaScript函数,用于测试文档的状态。如果它被加载,我们在Android的MainActivity;否则,我们注册一个事件监听器,一旦页面准备好,使用window.addEventListener('onload', ...);更新Android。

因为我们是在调用this.getWebView().loadURL("")之后追加这个脚本的,所以很可能我们根本不需要为事件“听”,因为我们只有在页面已经加载之后,通过对loadURL的连续调用才有机会追加JavaScript钩子。

这里有一个方法,它允许你注册一个Runnable,在一个特定的网址加载完成后执行。我们将每个RunnableMap中对应的URL String相关联,并使用WebViewgetOriginalUrl()方法来选择适当的回调。

package io.github.cawfree.webviewcallback;


/**
*  Created by Alex Thomas (@Cawfree), 30/03/2017.
**/


import android.net.http.SslError;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;


import java.util.HashMap;
import java.util.Map;


/** An Activity demonstrating how to introduce a callback mechanism into Android's WebView. */
public class MainActivity extends AppCompatActivity {


/* Member Variables. */
private WebView               mWebView;
private Map<String, Runnable> mCallbackMap;


/** 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);
this.mCallbackMap = new HashMap<>();
// Define the custom WebClient. (Here I'm just suppressing security errors, since older Android devices struggle with TLS.)
this.getWebView().setWebViewClient(new WebViewClient() {
/** Handle when a request has been launched. */
@Override public final void onPageFinished(final WebView pWebView, final String pUrl) {
// Determine whether we're allowed to process the Runnable; if the page hadn't been redirected, or if we've finished redirection.
if(pUrl.equals(pWebView.getOriginalUrl())) {
// Fetch the Runnable for the OriginalUrl.
final Runnable lRunnable = getCallbackMap().get(pWebView.getOriginalUrl());
// Is it valid?
if(lRunnable != null) { lRunnable.run(); }
}
// Handle as usual.
super.onPageFinished(pWebView, pUrl);
}
/** Ensure we handle SSL state properly. */
@Override public final void onReceivedSslError(final WebView pWebView, final SslErrorHandler pSslErrorHandler, final SslError pSslError) { pSslErrorHandler.proceed(); }
});
// Assert that we wish to visit Zonal's website.
this.getWebView().loadUrl("http://www.zonal.co.uk/");
// Align a Callback for Zonal; this will be serviced once the page has loaded.
this.getCallbackMap().put("http://www.zonal.co.uk/", new Runnable() {     @Override public void run() { /* Do something. */ } });
}


/* Getters. */
public final WebView getWebView() {
return this.mWebView;
}


private final Map<String, Runnable> getCallbackMap() {
return this.mCallbackMap;
}


}

只是为了显示进度条,“onpagstarted”和“onPageFinished”方法就足够了;但如果你想有一个“is_loading”标志(以及页面重定向,…),这个方法可以执行非排序,如“onpagstarted > onpagstarted > onPageFinished > onPageFinished”队列。

但与我的短测试(自己测试),“onProgressChanged”方法值队列是“0-100 > 0-100 > 0-100 >…”

private boolean is_loading = false;


webView.setWebChromeClient(new MyWebChromeClient(context));


private final class MyWebChromeClient extends WebChromeClient{
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 0){
is_loading = true;
} else if (newProgress == 100){
is_loading = false;
}
super.onProgressChanged(view, newProgress);
}
}

如果活动关闭是一个静态变量,也要在活动关闭时设置"is_loading = false",因为活动可以在页面结束之前结束。

对于Kotlin用户:

webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
// do your logic
}
}

有很多方法你可以重写

当onpagefinished方法被调用或者进度达到100%时,渲染器将不会完成渲染,所以这两个方法都不能保证视图被完全渲染。

但是你可以从OnLoadResource方法中找出什么已经被渲染了什么还在渲染。这个方法被调用了很多次。

        @Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
// Log and see all the urls and know exactly what is being rendered and visible. If you wanna know when the entire page is completely rendered, find the last url from log and check it with if clause and implement your logic there.
if (url.contains("assets/loginpage/img/ui/forms/")) {
// loginpage is rendered and visible now.
// your logic here.


}
}
用这个应该会有帮助。' var currentUrl = "google.com" var partOfUrl = currentUrl。substring (0, currentUrl.length-2) < / p >

webView。setWebViewClient(对象:WebViewClient() {

override fun onLoadResource(WebView view, String url) {
//call loadUrl() method  here
// also check if url contains partOfUrl, if not load it differently.
if(url.contains(partOfUrl, true)) {
//it should work if you reach inside this if scope.
} else if(!(currentUrl.startWith("w", true))) {
webView.loadurl("www.$currentUrl")


} else if(!(currentUrl.startWith("h", true))) {
webView.loadurl("https://$currentUrl")


} else { ...}




}


override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
// you can call again loadUrl from here too if there is any error.
}

//你也应该覆盖其他覆盖错误的方法,如onReceiveError,以查看所有这些方法是如何一个接一个地被调用的,以及它们在调试断点时的行为。 } ' < / p >

这个将在他开始加载页面之前被调用 (并获得与onFinished()相同的参数)

@Override
public void onPageCommitVisible(WebView view, String url) {
super.onPageCommitVisible(view, url);
}

< >强芬兰湾的科特林的解决方案 第一个解决方案是创建webviewclient作为私有类,这是更有效的。 另一方面,第二个解决方案是sorter:)

第一个解决方案

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val webView : WebView = findViewById(R.id.webView)
webView.webViewClient = MyWebViewClient()
}


private class MyWebViewClient : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
println("Load Started")
}
override fun onPageFinished(view: WebView, url: String) {
println("Load Finished")
}
}
}

第二个解决方案

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val webView : WebView = findViewById(R.id.webView)
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
println("Load Started")
}
override fun onPageFinished(view: WebView, url: String) {
println("Load Finished")
}
}
}

两个解其实是一样的。所以onPageStarted函数在页面开始加载时运行,另一个挂起onPageFinished函数在页面完全加载时工作。你可能想在onPageFinished函数里面写一个loadFinishedRun ()的例子函数。

迟到总比不到好