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

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


当前回答

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

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

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

}

xml res - - activity_main布局。

<?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中启动一个自定义的onPageLoaded()事件;否则,我们注册一个事件监听器,一旦页面准备好,使用window更新Android。addEventListener (onload,…);。

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

其他回答

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

更新:

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

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

当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.

            }
        }

对于Kotlin用户:

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

有很多方法你可以重写

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

mWebView.setWebChromeClient(new WebChromeClient(){

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

                //change your progress bar
            }


        });

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

使用setWebViewClient()并重写onPageFinished()