Problèmes de développement et d'optimisation d'Android WebView

  La fréquence d'utilisation de WebView dans les projets en cours devrait être très élevée.WebView est principalement utilisé pour charger certaines applications fréquemment interactives et faciles à modifier. Actuellement, HTML5 est une tendance. Au cours du développement, certains problèmes de développement et d'optimisation seront rencontrés, comme indiqué ci-dessous.

1. Problèmes de développement

 1. L'accélération matérielle WebView fait scintiller le rendu de la page

  Pour les systèmes au-dessus de 4.0, après avoir activé l'accélération matérielle, WebView rend les pages plus rapidement et glisse plus facilement. Mais il y a un effet secondaire lorsque la vue WebView est complètement recouverte puis soudainement restaurée, des blocs blancs apparaissent pendant cette période de transition et l'interface clignote. La solution à ce problème consiste à désactiver temporairement l'accélération matérielle de WebView avant la période de transition, puis à l'activer après la période de transition. Le code d'arrêt est le suivant :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 
    webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 
}

2. L'événement de clic WebView non sur le premier écran dans ViewPager ne répond pas

  Si vos multiples WebViews sont chargés un par un dans ViewPager, vous rencontrerez un tel problème. Le premier écran WebView de ViewPager est créé au premier plan, et il n'y a pas de problème lors du clic ; tandis que d'autres WebViews qui ne sont pas le premier écran sont créés en arrière-plan, et le journal d'erreur suivant apparaîtra après avoir glissé dessus et cliqué sur la page :

  20955-20968/xx.xxx.xxx E/webcoreglue﹕ Ne devrait pas se produire : aucun nœud de test basé sur rect trouvé

  La solution à ce problème consiste à hériter de la classe WebView, à remplacer la méthode onTouchEvent dans la sous-classe et à ajouter le code suivant :

@Override 
public boolean onTouchEvent(MotionEvent ev) { 
    if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
        onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY()); 
    } 
    retourne super.onTouchEvent(ev); 
}

3. WebView est en conflit avec l'événement TouchMove de l'élément parent supérieur

  Vous pouvez rencontrer une telle situation au cours du processus de développement. Il utilise ViewPager pour imbriquer plusieurs pages WebView, et en même temps, un élément de page dans un certain WebView doit répondre à l'événement TouchMove.

  À ce stade, vous constaterez que la couche supérieure (ViewPager) empêche la couche inférieure (WebView) de recevoir des événements TouchMove. Même si votre WebView renvoie true lorsque TouchDown, il n'est pas valide, car la couche supérieure utilise directement onInterceptTouchEvent pour intercepter les TouchMove suivants. . Pour résoudre ce problème, le moyen le plus simple consiste à réécrire la méthode WebView onTouchEvent, comme suit :

@Override 
public booléen onTouchEvent(MotionEvent ev) { 
    booléen ret = super.onTouchEvent(ev); 
    if (mPreventParentTouch) { 
        switch (ev.getAction()) { 
            case MotionEvent.ACTION_MOVE: 
                requestDisallowInterceptTouchEvent(true); 
                ret = vrai ; 
                casser; 
            cas MotionEvent.ACTION_UP :
            cas MotionEvent.ACTION_CANCEL :
                requestDisallowInterceptTouchEvent(false) ; 
                mPreventParentTouch = faux ; 
                casser; 
        } 
    } 
    retour ret ; 
}
 
public void preventParentTouchEvent () {
    mPreventParentTouch = vrai ; 
}

  La clé du contrôle du code réside dans la variable mPreventParentTouch. La valeur par défaut de mPreventParentTouch est false. Lorsque l'utilisateur touche un élément de la page, WebView est averti de définir mPreventParentTouch sur true. Le code schématique est le suivant :

<script type="text/javascript"> 
    document.getElementById("targetEle").addEventListener("touchstart", 
        function(ev) { 
            HostApp.preventParentTouchEvent(); // Notifier WebView pour empêcher les ancêtres d'intercepter leurs événements Touch 
        } 
    ) ; 
    document.getElementById("targetEle").addEventListener("touchmove", 
        function(ev) { 
            // faire quelque chose sur cette page 
        } 
    ); 
</script>

  Pour savoir comment la page Web notifie la WebView (c'est-à-dire appelle la méthode Java ), veuillez vous référer à l'article 8 .

  Je viens de mentionner que ce qui précède est une méthode simple et qu'elle ne peut pas résoudre le problème de mauvaise opération causé par un glissement trop rapide des doigts.C'est-à-dire que lorsque l'utilisateur glisse rapidement, il y a encore une certaine probabilité que ViewPager intercepte l'événement TouchMove et un Tab La bascule, et non l'élément de page, a répondu. Pour résoudre parfaitement ce problème, une méthode un peu plus compliquée (seul le processus global de messagerie est plus compliqué).

  Supposons d'abord qu'il existe un élément parent appelé ParentViewOnViewPager au-dessus de ViewPager. Lorsque nous recevons la notification de page preventParentTouchEvent, nous l'interceptons avant ViewPager. ParentViewOnViewPager.java est le suivant :

public class ParentViewOnViewPager étend FrameLayout { 
    private MineWebView mDispatchWebView; 

    public void preventParentTouchEvent (vue WebView) { 
        mDispatchWebView = (MineWebView)view ; 
    } 

    @Override 
    public boolean onInterceptTouchEvent(MotionEvent ev) { 
        if (ev.getAction() == MotionEvent.ACTION_MOVE && mDispatchWebView != null) { 
            mDispatchWebView.ignoreTouchCancel(true); 
            retourner vrai ; 
        } 
        retourne faux ; 
    } 

    @Override 
    public booléen onTouchEvent(MotionEvent ev) { 
        if (mDispatchWebView != null){ 
            switch (ev.getAction()) {
                cas MotionEvent.ACTION_MOVE : 
                    mDispatchWebView.onTouchEvent(ev) ; 
                    casser; 
                par défaut : 
                    mDispatchWebView.ignoreTouchCancel(false); 
                    mDispatchWebView.onTouchEvent(ev); 
                    mDispatchWebView = null ; 
                    casser; 
            } 
            renvoie vrai ; 
        } 
        retourne super.onTouchEvent(ev); 
    } 
}

  Autrement dit, lorsque ParentViewOnViewPager reçoit la notification, il lance l'interception TouchEvent et transfère l'événement Touch intercepté au mDispatchWebView qui charge la page pour l'envoi de l'événement. Cela ignore directement la couche ViewPager. Il convient de noter ici que lorsque ParentViewOnViewPager initie l'interception, WebView recevra un événement TouchCancel, et WebView doit ignorer cet événement pour éviter d'interrompre l'ensemble du processus de traitement lorsque la page reçoit cet événement. Comme indiqué dans le code suivant MineWebView.java :

public class MineWebView étend WebView { 
    booléen mIgnoreTouchCancel ; 

    public void ignoreTouchCancel (boolean val) { 
        mIgnoreTouchCancel = val; 
    } 

    @Override 
    public booléen onTouchEvent(MotionEvent ev) { 
        return ev.getAction() == MotionEvent.ACTION_CANCEL && mIgnoreTouchCancel || super.onTouchEvent(ev); 
    } 
}

  De plus, pour cette solution, le script JS côté page n'a pas besoin d'apporter de modifications.

2. Plan d'optimisation

1. Activer le cache WebView

  L'activation de la fonction de cache de WebView peut réduire les demandes de ressources serveur et utilise généralement la stratégie de cache par défaut.

//Définir le mode cache 
webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);   
//Activer la fonction API de stockage DOM 
webView.getSettings().setDomStorageEnabled(true);

2. Stockage local des fichiers de ressources

  Les ressources et autres fichiers (n'ont pas besoin d'être mis à jour) sont stockés localement et peuvent être obtenus directement à partir du local en cas de besoin. Quelles ressources ont besoin de nous pour stocker localement, bien sûr, certaines ressources qui ne seront pas mises à jour, telles que les fichiers image, les fichiers js, les fichiers css, la méthode de remplacement est également très simple, il suffit de réécrire la méthode de WebView.

            @Override 
            public WebResourceResponse shouldInterceptRequest(WebView view, String url) { 
                // TODO Stub de méthode généré automatiquement 
                if (url.endsWith("jquery-3.2.1.min.js")) { 
                    return ConvertLocalResponse(); 
                } 
                renvoie super.shouldInterceptRequest(view, url); 
            } 
            private WebResourceResponse ConvertLocalResponse() { 
                essayer { 
                    InputStream localStream = getResources().openRawResource(R.raw.jquery_min_js); 
                    WebResourceResponse localResponse = new WebResourceResponse("text/javascript","utf-8", localStream);
                    return localResponse ; 
                } catch (Exception e) { 
                    e.printStackTrace(); 
                    Log.i("result", "Error loading local js: "+e.toString()); 
                    return null; 
                } 
            }

3. Réduisez les opérations chronophages

  Afin d'atteindre cet objectif, nous devons réduire le temps de fonctionnement des opérations synchrones et essayer d'utiliser des opérations asynchrones au lieu d'opérations synchrones. Si le serveur a des opérations chronophages telles que l'accès aux données, essayez d'utiliser des opérations asynchrones (ajax) pour consacrer le temps d'origine aux opérations asynchrones.

4. Optimisation de l'interface client

  Comment résoudre la page blanche avant le chargement de WebView ? Nous avons besoin de WebView pour précharger la page. Voici deux méthodes :

  (1) ViewPager, placez la page d'accueil et la "page contenant WebView" dans ViewPager, définissez le nombre de pages préchargées, de sorte que la page où se trouve WebView puisse être préchargée, et passez à la page où se trouve WebView lorsque le chargement est terminé .

  (2) FrameLayout combine la disposition de la page d'accueil et de la page WebView et les affiche sur une seule page. La disposition WebView est initialement masquée. Une fois WebView chargé, la disposition d'accueil est masquée et la disposition WebView s'affiche.

  Il est plus facile d'utiliser FrameLayout. Les deux méthodes doivent surveiller le onProgressChanged de WebChromeClient et changer de page après le chargement. Les exemples sont les suivants :

  webView.setWebChromeClient(new WebChromeClient() { 
        @Override 
        public void onProgressChanged(vue WebView, int newProgress) { 
            super.onProgressChanged(view, newProgress); 
            if (newProgress >= 100) { 
                // 切换页面
            } 
        } 
    });

 5. Accélérez le chargement des pages Web HTML

  Par défaut, une fois le code html téléchargé sur WebView, webkit commence à analyser chaque nœud de la page Web, et lorsqu'il trouve un fichier de style externe ou un fichier de script externe, il lance de manière asynchrone une demande réseau pour télécharger le fichier, mais si le nœud d'image est également analysé avant cela, il lancera également une requête réseau pour télécharger l'image correspondante. Dans le cas de mauvaises conditions de réseau, un trop grand nombre de requêtes réseau entraînera une tension de la bande passante, ce qui affectera le temps de chargement du fichier css ou js, ce qui entraînera une page blanche trop longtemps. La solution consiste à indiquer à WebView de ne pas charger automatiquement les images en premier et d'attendre que la page soit terminée avant de lancer le chargement de l'image.

  Définissez le code suivant lorsque WebView est initialisé :

public void int () { 
    if(Build.VERSION.SDK_INT >= 19) { 
        webView.getSettings().setLoadsImagesAutomatically(true); 
    } else { 
        webView.getSettings().setLoadsImagesAutomatically(false); 
    } 
}

  En même temps, ajoutez le code suivant à la méthode onPageFinished() dans l'instance WebViewClient de WebView :

@Override 
public void onPageFinished (vue WebView, chaîne url) { 
    if (!webView.getSettings().getLoadsImagesAutomatically()) { 
        webView.getSettings().setLoadsImagesAutomatically(true); 
    } 
}

  Dans le code ci-dessus, nous sommes compatibles avec la version de l'API système supérieure à 19. Parce que lorsque le système au-dessus de 4.4 reprend le chargement de l'image lorsqu'onPageFinished, s'il y a plusieurs images faisant référence au même src, une seule balise d'image sera chargée, donc pour un tel système, nous la chargerons directement en premier.

6. Interface d'erreur personnalisée

  Lorsque WebView charge une page d'erreur (généralement 404 NOT FOUND), Android WebView affiche une interface d'erreur mignonne par défaut. De cette façon, les utilisateurs trouvent que ce que nous intégrons est une page Web, et nous nous attendons à ce que les utilisateurs obtiennent une expérience d'application native sur la page Web. La solution consiste à remplacer la méthode onReceivedError() dans l'instance WebViewClient :

@Override 
public void onReceivedError (vue WebView, int errorCode, description de la chaîne, chaîne failingUrl) { 
    super.onReceivedError (vue, errorCode, description, failingUrl); 
    loadDataWithBaseURL(null, "", "text/html", "utf-8", null); 
    mErrorFrame.setVisibility(View.VISIBLE); 
}

  Nous utilisons d'abord la méthode loadDataWithBaseURL() pour effacer le contenu de la page d'erreur par défaut, puis laissons notre vue personnalisée s'afficher (mErrorFrame est un LinearLayout au-dessus de la WebView, et la valeur par défaut est View.GONE).

7. S'il y a une barre de défilement

  Lorsque nous exécutons une fonction telle que l'extraction et le chargement de la page suivante, nous devons savoir s'il existe une barre de défilement verticale dans la vue Web actuelle au début de la page. Si c'est le cas, la page suivante ne sera pas chargée. Sinon , la page suivante sera chargée jusqu'à ce qu'elle apparaisse verticalement. Commencez par hériter de la classe WebView et ajoutez le code suivant à la sous-classe :

public boolean existVerticalScrollbar () { 
    return computeVerticalScrollRange() > computeVerticalScrollExtent(); 
}

  computeVerticalScrollRange() obtient la hauteur maximale qui peut être glissée, et computeVerticalScrollExtent() obtient la hauteur de la poignée de défilement elle-même. Lorsqu'il n'y a pas de barre de défilement, les deux valeurs sont égales. Lorsqu'il y a une barre de défilement, la première doit être supérieure à la seconde.

8. S'il a défilé jusqu'en bas de la page

  De même, lorsque nous effectuons la fonction d'extraction et de chargement de la page suivante, nous devons également connaître l'état de la barre de défilement de la page en cours. Si elle est presque en bas, nous devons lancer une page de mise à jour des données de demande de réseau. . Hériter également de la classe WebView et remplacer la méthode onScrollChanged dans la sous-classe, comme suit :

            @Override 
            protected void onScrollChanged(int newX, int newY, int oldX, int oldY) { 
                super.onScrollChanged(newX, newY, oldX, oldY); 
                if (newY != oldY) { 
                    float contentHeight = getContentHeight() * getScale() ; 
                    // Jamais déclenché sous la hauteur actuelle du contenu, le navigateur a une barre de défilement et glisse vers le bas 
                    if (mCurrContentHeight != contentHeight && newY > 0 && contentHeight <= newY + getHeight() + mThreshold) { 
                        // TODO Something ... 
                        mCurrContentHeight = contentHeight ; 
                    } 
                } 
            }

  Le mCurrContentHeight ci-dessus est utilisé pour enregistrer la hauteur de la page Web lorsqu'elle a été déclenchée la dernière fois, et il est utilisé pour empêcher TODO d'être déclenché plusieurs fois lorsque la hauteur totale de la page Web n'a pas changé et qu'un défilement continu se produit dans la zone cible. TODO sera déclenché lorsque la différence est <= cette valeur.

9. Les pages Web distantes doivent accéder aux ressources locales

  Lorsque nous chargeons le contenu extrait du serveur Web dans WebView, nous ne pouvons pas accéder aux ressources locales, telles que les ressources d'image dans le répertoire des actifs, car un tel comportement est un comportement inter-domaines (Cross-Domain), et WebView est interdit. La solution à ce problème consiste à télécharger d'abord le contenu html en local, puis à utiliser loadDataWithBaseURL pour charger le html. De cette façon, le lien de file:///android_asset/xxx.png peut être utilisé en html pour faire référence aux ressources sous assets dans le package. Les exemples sont les suivants :

private void loadWithAccessLocal(final String htmlUrl) { 
    new Thread(new Runnable() { 
        public void run() { 
            try { 
                final String htmlStr = NetService.fetchHtml(htmlUrl); 
                if (htmlStr != null) { 
                    TaskExecutor.runTaskOnUiThread(new Runnable () { 
                        @Override 
                        public void run() { 
                            loadDataWithBaseURL(htmlUrl, htmlStr, "text/html", "UTF-8", ""); } }); 
                        return 
                    ; 
                    } 
                } 
            catch (Exception e) {
                Log.e("Exception :" + e.getMessage()); 
            } 

            TaskExecutor.runTaskOnUiThread(new Runnable() { 
                @Override 
                public void run() { 
                    onPageLoadedError(-1, "fetch html failed"); 
                } 
            }); 
        } 
    }).start(); 
}

  demande attention :

  • Le processus de téléchargement de HTML à partir du réseau doit être placé dans un thread de travail
  • Une fois le code HTML téléchargé avec succès, l'étape de rendu du code HTML doit être placée sur le fil principal de l'interface utilisateur, sinon WebView signalera une erreur.
  • Si le téléchargement html échoue, vous pouvez utiliser la méthode que nous avons décrite précédemment pour afficher une interface d'erreur personnalisée

10. Évitez les problèmes de sécurité causés par addJavaScriptInterface

  L'utilisation du projet open source Safe Java-JS WebView Bridge peut être un bon remplacement pour la méthode addJavaScriptInterface, tout en ajoutant la prise en charge des rappels asynchrones, et il n'y a aucun risque de sécurité.

Je suppose que tu aimes

Origine blog.csdn.net/juruiyuan111/article/details/126862027
conseillé
Classement