web-dev-qa-db-fra.com

Android WebView rend le blanc/blanc, l'affichage ne met pas à jour sur les modifications CSS ou HTML, les animations sont saccadées

Chaque fois que je fais un changement de classe CSS, les changements n'apparaissent pas toujours. Cela semble se produire lorsqu'un événement tactile ajoute quelque chose comme un nom de classe down à un bouton. Le bouton ne se met pas à jour et ne fait rien d'autre sur la page. C'est très inconstant quand ça marche. J'ai aussi remarqué que parfois, mes éléments apparaissent en blanc, sans contenu ni rien. C'est très frustrant!

50
Kyle

J'ai implémenté la solution de Kyle et cela a résolu le problème. Cependant, j'ai remarqué une énorme décharge de batterie sous Android 4.0.4 lorsque l'application était ouverte. De plus, après le changement, des utilisateurs se sont plaints du fait que le clavier swiftKey ne fonctionnait plus avec mon application.

Chaque modification de mon application est déclenchée par une action de l'utilisateur. C'est pourquoi j'ai proposé une version modifiée qui ne déclenche que invalidate () après un événement tactile:

    Handler handler = new Handler();
    public boolean onTouchEvent (MotionEvent event){
        super.onTouchEvent(event);
        handler.postDelayed(triggerInvalidate, 60);
            handler.postDelayed(triggerInvalidate, 300);
        return true;
    }

    private Runnable triggerInvalidate=new Runnable(){
        public void run(){
            invalidate();
        }
    };

Jamais aucune programmation avec Java n'a été faite, il pourrait donc y avoir une meilleure solution pour le faire.

17
Olivier

Remarque:
Il existe une meilleure solution à partir d'Android 4.4+. C'est un remplacement WebView insuffisant appelé CrossWalk. Il utilise le dernier kit de chrome et c'est fantastique. Vous pouvez lire à ce sujet ici: crosswalk-project.org

De plus, il semble que depuis Android 4.4, la solution invalidate() n'est plus nécessaire et que vous pouvez vous en tirer en utilisant certaines des réponses plus sûres. Je n’utiliserais cette approche invalidate() que comme un dernier effort.


Je réponds à ma propre question pour, espérons-le, aider les gens avec les mêmes problèmes que moi.

J'ai essayé plusieurs méthodes pour améliorer les choses, même la très notoire -webkit-transform: translate3d(0,0,0); Même cela n'a pas très bien fonctionné.

Permettez-moi de partager avec vous ce qui a fonctionné.

Tout d'abord, utilisez l'API la plus récente. J'utilise l'API 15. Dans votre AndroidManifest.xml, veillez à activer accélération matérielle. Si votre version de l'API ne le supporte pas, passez au bit suivant.

Si votre version le supporte, vous pouvez l'activer en modifiant votre manifeste:

<application
   ...
   Android:hardwareAccelerated="true">

Assurez-vous également que votre manifeste possède le minimum d'API prise en charge par rapport à celui que vous utilisez. Depuis que j'utilise API 15, voici ce que mon manifeste contient:

<uses-sdk
    Android:minSdkVersion="15"
    Android:targetSdkVersion="15" />

(Update: vous devez maintenant modifier ces valeurs dans votre build.gradle)}

A présent, dans votre fichier CSS principal pour ce qui sera présenté dans une WebView, ajoutez quelque chose comme ceci:

body div {
    -webkit-transform: translate3d(0,0,0);
}

Ajouter sur le bit de corps du corps avec tout autre type d'élément que vous avez; vous pouvez probablement exclure des images, ul, li, etc. La raison pour laquelle vous appliquez ce style CSS à tout est simplement due à des essais et à des erreurs. Avec une plus grande arborescence DOM, vous devrez peut-être être plus spécifique. Je ne suis cependant pas sûr de ce que seraient les spécifications.

Lorsque vous instanciez votre WebView, vous souhaiterez définir certains paramètres:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.loadUrl("file:///Android_asset/www/index.html");
    appView.getSettings().setRenderPriority(RenderPriority.HIGH);
    appView.getSettings()
            .setPluginState(WebSettings.PluginState.ON_DEMAND);
}

Avant-dernier, mais un point crucial: je lisais le code source de la classe WebView et trouvai ce petit commentaire minuscule sur le redessinage forcé. Il existe un static final boolean qui, lorsqu'il est défini sur true force la vue à toujours être redessiné. Je ne suis pas très doué pour la syntaxe Java, mais je ne pense pas que vous puissiez modifier directement un attribut final statique d'une classe. Donc, ce que j'ai fini par faire, c'est d'étendre le cours de la manière suivante:

import org.Apache.cordova.CordovaWebView;

import Android.content.Context;
import Android.graphics.Canvas;

public class MyWebView extends CordovaWebView {
    public static final String TAG = "MyWebView";

    public MyWebView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Warning! This will cause the WebView to continuously be redrawn
        // and will drain the devices battery while the view is displayed!
        invalidate();
    }

}

N'oubliez pas que j'utilise Cordova/PhoneGap et que je devais donc passer de CordovaWebView. Si vous voyez dans la méthode onDraw, il existe un appel à invalidate. Cela entraînera un redessin constant de la vue. Je recommande fortement d'ajouter la logique à redessiner uniquement lorsque vous en avez besoin, cependant.

Il y a une dernière étape, si vous utilisez Cordova. Vous devez dire à PhoneGap d'utiliser votre nouvelle classe WebView au lieu de sa propre classe WebView. Dans votre classe MainActivity, ajoutez ceci:

public void init(){
    CordovaWebView webView = new MyWebView(MainActivity.this);
    super.init(webView, new CordovaWebViewClient(this, webView), new CordovaChromeClient(this, webView));
}

C'est tout! Essayez de lancer votre application et voyez si tout est beaucoup plus fluide. Avant de procéder à toutes ces modifications, les pages paraissaient blanches, aucune modification CSS n'était appliquée jusqu'à ce que, après avoir appuyé à nouveau sur l'écran, les animations soient super saccadées ou même non perceptibles. Je devrais mentionner que les animations sont toujours agitées, mais beaucoup moins agitées qu’avant.

Si quelqu'un a quelque chose à ajouter à cela, commentez juste sous. Je suis toujours ouvert aux optimisations. et je suis pleinement conscient qu'il existe peut-être de meilleures façons de faire ce que je viens de recommander.

Si ma solution ci-dessus ne vous convient pas, pouvez-vous décrire votre situation spécifique et quels résultats vous voyez avec Androids WebView?

Enfin, j'ai activé cette réponse en tant que "wiki de la communauté", afin que tout le monde soit libre de procéder à des ajustements.

Merci!


Modifier:

Avec la dernière version de PhoneGap, vous aurez besoin que votre méthode init () ressemble davantage à ceci:

public void init(){
    CordovaWebView webView = new MyWebView(MainActivity.this);
    super.init(webView, new IceCreamCordovaWebViewClient(this, webView), new CordovaChromeClient(this, webView));
}
58
Kyle

re: le problème de redessinage, vous pouvez forcer un redessin en lisant une propriété de l'élément

alors dites que vous faites ceci:

$('#myElement').addClass('foo'); // youre not seeing this take effect

si vous faites cela après:

$('#myElement').width();

ça va forcer un redessin.

De cette façon, vous pouvez être sélectif au lieu de redessiner toute la page tout le temps, ce qui coûte cher

8
Andrew Bullock

Pour moi, ce problème ne se produisait que sur les appareils Samsung. J'ai pu résoudre le problème en désactivant l'accélération matérielle pour WebViews:

webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

J'espère que ça aide.

8
Leandro Glossman

Comme indiqué ci-dessus et ailleurs, le remplacement de View.onDraw() et l'appel de View.invalidate entraîneront une perte de performances de la batterie/de l'application. Vous pouvez également faire un appel manuel invalidate toujours x ms comme si

/**
 * Due to bug in 4.2.2 sometimes the webView will not draw its contents after the data has loaded. 
 * Triggers redraw. Does not work in webView's onPageFinished callback for some reason
 */
private void forceWebViewRedraw()
{
    mWebView.post(new Runnable() {
        @Override
        public void run()
        {
            mWebView.invalidate();
            if(!isFinishing())
                mWebView.postDelayed(this, 1000);
        }
    });
}

J'ai essayé de mettre un appel invalide dans WebViewClient.onPageLoaded() mais cela ne semble pas fonctionner. Alors que ma solution pourrait être meilleure, c'est simple et cela fonctionne pour moi (je montre simplement un identifiant Twitter)

6
Dori

J'ai eu ce problème car onPageStared et onPageFinished je montre et masque une animation de chargement.

Depuis l'injection de JS, je fais mes menus et d'autres éléments du site Web d'origine et j'ai remarqué que l'injection de JS sur onPageFinished obligeait la vue Web à en tirer le contenu. Voici un exemple de ce que vous pouvez ajouter à la fin de onPageFinished

view.loadUrl("javascript:(function() { var select = document.getElementsByClassName('something')[0]\r\n" + 
                            "                     if(select)" +
                            "                       select.style.display = 'none';})()");
0
user3353754

Vider le cache de webView dans oncreate () et cela fonctionne pour moi.

webView.clearCache(true);
0
Ankush Chugh

Veuillez également consulter ma réponse à WebView ne parvient pas à afficher le rendu avant de toucher à Android 4.2.2 , L’idée est d’exporter la fonction de @ Olivier en JavaScript afin que vous puissiez déclencher une invalidation de JS dans des parties sensibles ... Mon dieu, encore un autre pirater !! :)

0
rupps