如何听WebView完成加载一个URL?

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

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

实现WebViewClient并扩展onPageFinished (),如下所示:

 mWebView.setWebViewClient(new WebViewClient() { public void onPageFinished(WebView view, String url) { // do your stuff here } }); 

@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; } if(loadingFinished && !redirect){ //HIDE LOADING IT HAS FINISHED } else{ redirect = false; } } }); 

更新:

根据文档:onPageStarted不会被调用时,一个嵌入式帧的内容改变,即点击一个目标是一个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; } if(loadingFinished && !redirect){ //HIDE LOADING IT HAS FINISHED } else{ redirect = false; } } }); 

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

我简化了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如果以前的回调是onPageStarted,所以页面被完全加载。

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

 mWebView.setWebChromeClient(new WebChromeClient(){ @Override public void onProgressChanged(WebView view, int newProgress) { //change your progress bar } }); 

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

使用setWebViewClient()并覆盖onPageFinished()

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

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

您可以通过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(); } } } 

感谢您的答案。 它帮助了我,但是我不得不为了我的需要改进它。 我有几个pagestarts和完成,所以我添加了一个计时器,检查是否更新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); } } }); 

使用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> 

这里有一个方法,允许你注册一个Runnable在一个特定的Web地址完成加载后执行。 我们将每个Runnable与一个Map相应的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; } } 

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

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

src / io / github / cawfree / webviewcallback / MainActivity.java (Jelly Bean,API Level 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函数。 如果它被加载,我们在Android的MainActivity启动一个自定义的onPageLoaded()事件; 否则,我们使用window.addEventListener('onload', ...);注册一个事件监听器,在页面准备就绪后更新Android window.addEventListener('onload', ...);

由于我们在调用this.getWebView().loadURL("")后追加了这个脚本, this.getWebView().loadURL("")已经被创建,所以我们可能根本不需要“听”事件,因为我们只有一个机会一旦页面已经被加载,通过连续调用loadURL来附加JavaScript钩子。