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

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

评论

请考虑将我的答案标记为正确,因为它可以解决ian不能解决的一些问题。

我认为在页面加载时用js调用本机Java代码的更好方法。请参阅此答案,尽管它适用于ios,但该想法也适用于此。

请在下面查看我的答案,以寻求最小和简洁的解决方案

#1 楼

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

mWebView.setWebViewClient(new WebViewClient() {

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


评论


当互联网不是那么快时,我对外部负载有一些问题。例如Google Analytics(分析)或类似的其他内容。我尝试了一些变通办法,但它已经改善了很多,但还不够完善。有时,该应用程序在加载屏幕上冻结,除非您断开网络连接或连接到另一个良好的网络,否则无法退出。我正在尝试如何解决此问题。

–Felipe
2011年7月18日,0:10



完美的作品。您可以在加载之前使用mWebView.setVisibility(View.INVISIBLE)。加载完成后,将其设置回View.VISIBLE。我将其用于以下初始屏幕:android-developers.blogspot.de/2009/03/…

– Johan Hoeksma
2014年8月25日在13:24



这根本不是解决方案。不能保证调用此方法时页面已加载。

–鹰
2015年9月23日15:50

Google的设计如此糟糕。几乎所有时间,当此操作完成时,您都想针对父活动而不是Web视图本身进行操作。覆盖函数而不是订阅事件是一种常见的反模式,也是我不喜欢android开发的原因。

–瑞安
16 Jul 28'23:25



此方法不能保证加载完成,如果url加载需要一些中间的url导航,则可以多次调用此方法。因此,这被多次调用。但是,它是一种检查方法。

–初学者
17年1月25日在6:15

#2 楼

@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:

根据文档:
onPageStarted不会

我发现一个类似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; 
            }
        }
    });


评论


您可能已经注意到onPageStarted的文档中的警告:当嵌入式框架的内容发生更改(即,单击目标为iframe的链接)时,不会调用onPageStarted。因此,在这种情况下,这可能会歪曲逻辑。顺便说一句,@ polen的简化真的有效吗?

–an00b
2011年12月9日在16:51

不错,从来没有注意到框架的东西。我还没有尝试过Polarn的代码..我唯一可以保证工作的是上面使用的代码。

–neteinstein
2012年1月2日15:04

只是一个小的更正。实际上是“ public void onPageStarted(WebView视图,字符串url,位图图标)”,而不是“ public void onPageStarted(WebView视图,字符串url)”

– Mraffen
2014年7月2日在15:38

对于某些从未完成加载的较新网站无效,例如“ agoda.com”。您永远不会收到onPageFinished调用,因为它是重定向。

– Kenyee
16年8月24日在19:40

我已经看到onPageFinished在重定向期间被调用了两次。但是,根据onPageFinished的文档,您所说的“如果一个页面中有多个iframe,您将有多个onPageFinished(和onPageStarted)”是不正确的:“此方法仅针对主机调用。”

–石榴石
17年4月4日在11:23

#3 楼

我相当偏爱@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.
            }
        }
    });
}


评论


您的代码看起来不错。我一直在尝试从html数据而非url加载的webview。但是,如果第一个Web视图快速加载并且在第二个Web视图开始加载之前,则此事件触发onPageFinished。正常情况。 running = 1,running = 2,--running = 1,--running = 0。失败的异常情况-运行= 1,--running = 0,运行= 1正在运行= 0

–阿吉斯·玛玛娜(Ajith Memana)
2014年3月19日在7:04



还有另一种情况,当由于某种原因而未触发事件时,将永远无法到达TODO部分。因此,您需要一个计时器来检查超时。或者,当您知道所有内容均已正确加载时,可以在javascript中调用导出的Java函数。像documentReady()之类的东西。

– Codebeat
2015年6月14日在8:19



为什么不仅仅运行= 1;在onPageStarted()中?

–胆固醇
16年7月14日在7:29

@Choletski因为最大值1和大于1的数字不是1。

–马修·雷德(Matthew Read)
18年3月18日在9:04

#4 楼

我也找到了一种优雅的解决方案,但是并没有对其进行严格的测试:

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


评论


我已经对其进行了测试,并且可以正常运行。当然,您可以从onPageFinished方法外部调用它,并使用Handler在短时间内进行检查-在某些情况下,您不想扩展WebViewClient类。

– Gal Rom
17-10-31在10:31



我相信这可能比接受的答案要好,您只需要检查onError就可以了。

– Evin1_
18年4月10日在13:06

#5 楼

我已经简化了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;
            }

        }
 });


很容易理解,如果先前的回调位于onPageStarted上,则OnPageFinished,因此页面已完全加载。 br />

评论


@polen PAGE_STARTED永远不会清除吗?如果从未调用过OverrideUrlLoading()怎么办?

–an00b
2011-12-7 22:20

#6 楼

如果要显示进度条,则需要侦听进度更改事件,而不仅是页面的完成:

mWebView.setWebChromeClient(new WebChromeClient(){

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

                //change your progress bar
            }


        });


BTW如果要仅显示不确定的覆盖方法onPageFinished的ProgressBar就足够了

#7 楼

使用setWebViewClient()并覆盖onPageFinished()

#8 楼

您可以通过webview类中的getProgress方法跟踪Progress Staus。

初始化进度状态

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


评论


mProgressStatus = webview.getProgress();需要从UI线程调用。

–天网
2015年10月13日在6:03

#9 楼

感谢您的回答。它对我有所帮助,但我必须对其进行一些改进。我有几个pagestarts和finishs,所以我添加了一个计时器,用于检查是否认为pagefinish已启动是新的pagestart。好的,不好的解释。参见代码:)

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

});


#10 楼

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

此实现要求我们在setJavaScriptEnabled(true)的设置上调用WebView,因此它可能不适合您的应用程序要求,例如安全问题。

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

<?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的onPageLoaded()中启动自定义MainActivity事件;否则,我们将使用window.addEventListener('onload', ...);注册一个可在页面准备就绪后更新Android的事件侦听器。由于我们在对this.getWebView().loadURL("")进行了调用之后添加了此脚本,因此很可能不会根本不需要“监听”事件,因为一旦页面已加载,我们只有通过连续调用loadURL才有机会附加JavaScript钩子。

评论


很棒的解决方案,通常可以正常工作。我在使用https://reddit.com/r/Nature(通常使用Reddit)时遇到问题,当您单击某个线程并加载了线程时,我无法捕获该事件。你能帮我吗?

–Primož Kralj
18/12/11在11:03



@PrimožKraljGlad听到您在此方面取得了一些成功...我不确定具体要提出什么建议,但是如果您知道触发了任何可以触发的公共事件,例如评论计数已更新,您可以将其用作来源。您是否考虑过直接点击Reddit的API可以解决您的问题?

– Mapsy
18/12/11在16:04



谢谢,我将再看看触发的事件,并尝试挂钩其中的一个。我知道该API,并且将来会尝试使用该API实施更强大的解决方案。谢谢!

–Primož Kralj
18/12/12在9:07

#11 楼

仅显示进度条,“ onPageStarted”和“ onPageFinished”方法就足够了。但是如果您要使用“ is_loading”标志(以及页面重定向,...),则可以使用非排序方式执行此方法,例如“ onPageStarted> onPageStarted> 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”,如果它是一个静态变量,因为可以在页面完成之前完成活动。

#12 楼

对于Kotlin用户:

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


有很多方法可以通过它们覆盖

#13 楼

使用SwipeRefreshLayoutProgressBar加载网址: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>


#14 楼

这是一种允许您在特定网址完成加载后注册要执行的Runnable的方法。我们将每个RunnableString中的对应URL Map相关联,并使用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;
    }

}


#15 楼

当调用OnPageFinshed方法或进度达到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.

            }
        }


#16 楼

使用它应该会有所帮助。`varcurrentUrl =“ google.com”
var partOfUrl = currentUrl.substring(0,currentUrl.length-2)

webView.setWebViewClient(object: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,以查看所有这些方法如何依次调用以及它们在调试时的行为带有断点。
}
`

#17 楼

这将在他开始加载页面之前被调用
(并获得与onFinished()相同的参数)
 @Override
public void onPageCommitVisible(WebView view, String url) {
   super.onPageCommitVisible(view, url);
}