web-dev-qa-db-fra.com

Comment écouter une WebView finissant de charger une URL?

J'ai un WebView qui charge une page à partir d'Internet. Je veux montrer un ProgressBar jusqu'à ce que le chargement soit terminé.

Comment écouter le chargement de la page d'un WebView ?

415
Janusz

Étendez WebViewClient et appelez onPageFinished () comme suit:

mWebView.setWebViewClient(new WebViewClient() {

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

@ian ce n'est pas précis à 100%. Si vous avez plusieurs iframes dans une page, vous aurez plusieurs onPageFinished (et onPageStarted). Et si vous avez plusieurs redirections, cela peut également échouer. Cette approche résout (presque) tous les problèmes:

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

PDATE:

Selon la documentation: onPageStarted NE sera PAS appelé lorsque le contenu d’un cadre incorporé sera modifié, c’est-à-dire en cliquant sur un lien dont la cible est un iframe.

J'ai trouvé un cas spécifique comme celui-ci sur Twitter où seul un pageFinished était appelé et dérangé un peu la logique. Pour résoudre ce problème, j'ai ajouté une tâche planifiée pour supprimer le chargement après X secondes. Ce n'est pas nécessaire dans tous les autres cas.

PDATE 2:

Maintenant, avec la mise en œuvre actuelle de 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; 
            }
        }
    });
143
neteinstein

Je suis plutôt partisan de la solution @NeTeInStEiN (et @polen), mais je l'aurais implémentée avec un compteur au lieu de plusieurs booléens ou observateurs d'état (juste une autre saveur mais je pensais qu'elle pourrait être partagée). Il y a une nuance JS à ce sujet mais je pense que la logique est un peu plus facile à comprendre.

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.
            }
        }
    });
}
39
SciSpear

J'ai simplifié le code de NeTeInStEiN comme ceci:

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

        }
 });

Il est facile à comprendre, OnPageFinished si le rappel précédent est sur onPageStarted, donc la page est complètement chargée.

23
polen

J'ai également trouvé une solution élégante, mais je ne l'ai pas testée rigoureusement:

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

Si vous souhaitez afficher une barre de progression, vous devez écouter un événement de changement de progression, et pas seulement l'achèvement de la page:

mWebView.setWebChromeClient(new WebChromeClient(){

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

                //change your progress bar
            }


        });

BTW si vous souhaitez afficher uniquement une barre de progression indéterminée remplaçant la méthode onPageFinished, cela suffit

16
Francesco Laurita
6
Robby Pond

merci pour les réponses. Cela m'a aidé, mais j'ai dû l'améliorer un peu pour mes besoins. J'ai eu plusieurs pagestarts et finis donc j'ai ajouté une minuterie qui vérifie si un transfert de pagefinish est lancé un nouveau pagestart. Ok, mauvaise explication. Voir le code :)

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

});
5
EscapeNetscape

Vous pouvez suivre la progression de Staus à l'aide de la méthode getProgress de la classe webview .

Initialiser le statut de progression

private int mProgressStatus = 0;

puis le AsyncTask pour charger comme ceci:

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

Voici une nouvelle méthode pour détecter le chargement d’une URL à l’aide de capacité d’Android pour les hooks JavaScript . À l'aide de ce modèle, nous exploitons la connaissance de l'état du document par JavaScript pour générer un appel de méthode natif au sein du moteur d'exécution Android. Ces appels accessibles par JavaScript peuvent être effectués à l'aide de l'annotation @JavaScriptInterface.

Cette implémentation nécessite d'appeler setJavaScriptEnabled(true) sur les paramètres de WebView. Par conséquent, il se peut que ne convienne pas en fonction des besoins de votre application, par exemple. préoccupations de sécurité.

src/io/github/cawfree/webviewcallback/MainActivity.Java (Jelly Bean, API niveau 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"
/>

Pour l'essentiel, nous ajoutons une fonction JavaScript supplémentaire permettant de tester l'état du document. S'il est chargé, nous lançons un événement personnalisé onPageLoaded() dans MainActivity d'Android; sinon, nous enregistrons un écouteur d'événements qui met à jour Android une fois la page prête, à l'aide de window.addEventListener('onload', ...);.

Comme nous ajoutons ce script après l'appel de this.getWebView().loadURL(""), il est probable que nous n'avons pas besoin de 'écouter' pour les événements, dans la mesure où nous n'avons la possibilité d'ajouter le crochet JavaScript qu'en effectuant un appel successif à loadURL, une fois la page déjà chargée.

1
Mapsy

Le rendu ne termine pas le rendu lorsque la méthode OnPageFinshed est appelée ou que la progression atteint 100%, de sorte que les deux méthodes ne vous garantissent pas que la vue a été entièrement rendue.

Mais vous pouvez déterminer à partir de la méthode OnLoadResource ce qui a déjà été rendu et ce qui est toujours rendu. Et cette méthode est appelée plusieurs fois.

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

Chargement de l'URL avec SwipeRefreshLayout et ProgressBar:

rlPageActivity.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
Hamid

pour les utilisateurs de Kotlin:

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

il y a beaucoup de méthodes que vous pouvez remplacer si

0

Voici une méthode qui vous permet d’enregistrer une Runnable à exécuter une fois que le chargement d’une adresse Web donnée est terminé. Nous associons chaque Runnable à une URL correspondante String dans un Map, et nous utilisons la méthode WebView's getOriginalUrl() pour choisir le rappel approprié.

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
Mapsy

Juste pour montrer la barre de progression, les méthodes "onPageStarted" et "onPageFinished" suffisent; mais si vous souhaitez avoir un indicateur "is_loading" (avec les redirections de page, ...), cette méthode peut être exécutée sans séquence, comme la file d'attente "onPageStarted> onPageStarted> onPageFinished> onPageFinished".

Mais avec mon court test (testez vous-même.), La file d'attente des valeurs de la méthode "onProgressChanged" est "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);
    }
}

Définissez également "is_loading = false" à la fermeture de l'activité, s'il s'agit d'une variable statique, car l'activité peut être terminée avant la fin de la page.