Как прослушивать WebView, заканчивая загрузку URL-адреса?


у меня есть WebView это загрузка страницы из интернета. Я хочу показать ProgressBar до завершения загрузки.

как я могу слушать для завершения загрузки страницы a WebView?

13   370   2010-06-30 16:28:26

13 ответов:

реализовать WebViewClient и продлить onPageFinished() следующим образом:

mWebView.setWebViewClient(new WebViewClient() {

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

@ian это не на 100% точно. Если у вас есть несколько iframes на странице вы будете иметь несколько 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.

Я нашел a конкретный случай, как это на Twitter, где только страница была вызвана и немного испортила логику. Чтобы решить эту проблему, я добавил запланированную задачу для удаления загрузки через 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 достаточно

Я также нашел одно элегантное решение, но не проверил его строго:

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

использовать setWebViewClient() и заменить onPageFinished()

Вы можете отслеживать прогресс Staus по getProgress метод webview класса.

инициализировать состояние

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 и заканчивается, поэтому я добавил таймер, который проверяет, если atfer 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);
            }
        }

});

загрузка url с помощью 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>

вот метод, который позволяет зарегистрировать Runnable выполняется после завершения загрузки определенного веб-адреса. Мы связываем друг с другом Runnable С соответствующим URL String на Map и мы используем WebView ' s getOriginalUrl() метод для выбора соответствующего обратного вызова.

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

}

вот новый метод для обнаружения, когда URL-адрес загружен с помощьювозможность Android для JavaScript крючки. Используя этот шаблон, мы используем знания JavaScript о состоянии документа для создания собственного вызова метода в среде выполнения Android. Эти JavaScript-доступные вызовы могут быть сделаны с помощью @JavaScriptInterface Примечание.

эта реализация требует, чтобы мы называли setJavaScriptEnabled(true) на WebView'ы настройки, так что это может не быть подходит в зависимости от требований вашего приложения, например, проблемы безопасности.

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 / 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, которая используется для проверки состояния документа. Если он загружен, мы запускаем пользовательский onPageLoaded() событие в Андроид MainActivity; в противном случае мы регистрируем прослушиватель событий, который обновляет Android после того, как страница будет готова, используя window.addEventListener('onload', ...);.

так как мы добавляем этот скрипт после вызова this.getWebView().loadURL("") было сделано, вполне вероятно, что нам не нужно 'слушать' для событий вообще, так как мы только получаем шанс добавить JavaScript-крючок, сделав последовательный вызов loadURL, как только страница уже загружена.

просто чтобы показать индикатор выполнения, достаточно методов" onPageStarted "и" onPageFinished"; но если вы хотите иметь флаг" is_loading " (вместе с перенаправлениями страниц,...), эти методы могут выполняться с non-последовательности, такие как "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 " on activity close, если это статическая переменная, потому что действие может быть завершено до завершения страницы.