如何侦听完成加载URL的WebView?


447

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

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


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

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

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

Answers:


714

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

mWebView.setWebViewClient(new WebViewClient() {

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

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

1
完美的作品。您可以在加载之前使用mWebView.setVisibility(View.INVISIBLE)。加载完成后,将其设置回View.VISIBLE。我这个闪屏使用它: android-developers.blogspot.de/2009/03/...
约翰Hoeksma

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

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

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

150

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

更新:

根据文档:当嵌入式框架的内容更改时,即单击目标为iframe的链接时,不会调用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; 
            }
        }
    });

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

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

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

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

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

40

我非常喜欢@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.
            }
        }
    });
}

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

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

为什么不只是running = 1;onPageStarted()
Choletski '16

@Choletski因为最大的1和数目大于1不是1
马修读

25

我也找到了一种优雅的解决方案,尽管没有经过严格的测试:

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

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

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

1
这应该是公认的答案。
زياد

1
给予好评的这一个
Khdkls

23

我简化了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,因此页面已完全加载。


3
@polen PAGE_STARTED永不清除?如果从未调用过OverrideUrlLoading()怎么办?
2011年

20

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

mWebView.setWebChromeClient(new WebChromeClient(){

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

                //change your progress bar
            }


        });

顺便说一句,如果您只想显示一个不确定的ProgressBar来覆盖onPageFinished方法就足够了



5

您可以通过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年

5

感谢您的回答。它对我有所帮助,但我必须对其进行一些改进。我有几个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);
            }
        }

});

2

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

实现需要,我们称之为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 /布局/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函数,该函数用于测试文档的状态。如果已加载,我们将onPageLoaded()在Android的中启动一个自定义事件MainActivity;否则,我们会使用来注册一个事件监听器,以在页面准备好后更新Android window.addEventListener('onload', ...);

由于我们是在调用之后添加该脚本的this.getWebView().loadURL(""),因此我们根本不需要“监听”事件,因为我们只有通过连续调用来添加JavaScript钩子的机会,loadURL,一旦页面已加载。


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

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

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

1

仅显示进度条,“ 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如果活动关闭是静态变量,也请在活动关闭时设置“ ”,因为活动可以在页面完成之前完成。


0

使用SwipeRefreshLayout和加载网址ProgressBar

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>

0

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

}

0

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

            }
        }

0

使用它应该会有所帮助。`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,以查看如何依次调用所有这些方法以及在使用断点进行调试时它们的行为方式。}`


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.