ANR dans SurfaceView sur des périphériques spécifiques uniquement – La seule solution est un court temps de sommeil

Dans mon application Android, j'utilise un SurfaceView pour dessiner des choses. Cela fonctionne bien sur des milliers de périphériques – sauf que les utilisateurs ont commencé à déclarer des ANR sur les périphériques suivants:

  • LG G4
    • Android 5.1
    • 3 Go de RAM
    • Affichage 5.5 "
    • 2560 x 1440 px résolution
  • Sony Xperia Z4
    • Android 5.0
    • 3 Go de RAM
    • Affichage 5,2 "
    • 1920 x 1080 px résolution
  • Huawei Ascend Mate 7
    • Android 5.1
    • 3 Go de RAM
    • Affichage de 6,0 "
    • 1920 x 1080 px résolution
  • HTC M9
    • Android 5.1
    • 3 Go de RAM
    • Affichage 5.0 "
    • 1920 x 1080 px résolution

J'ai donc eu un LG G4 et j'ai bien réussi à vérifier le problème. Il est directement lié à SurfaceView .

  • Button ClickLIstener ne fonctionne-t-il pas dans le jeu LibGdx?
  • Existe-t-il une bibliothèque ou un algorithme pour le calendrier persan (Shamsi ou Jalali) dans Android?
  • Comment comparer le temps dans java / android (entrée donnée dans les chaînes)?
  • Custom android.app.Application not firing onCreate event
  • ListActivity TwoLineListItem Alternative
  • Synchroniser l'heure du système dans 2 téléphones
  • Maintenant, devinez ce qui a résolu le problème après des heures de débogage? Il remplace …

     mSurfaceHolder.unlockCanvasAndPost(c); 

    … avec …

     mSurfaceHolder.unlockCanvasAndPost(c); System.out.println("123"); // THIS IS THE FIX 

    Comment se peut-il?

    Le code suivant est mon thread de rendu qui fonctionne bien, sauf pour les périphériques mentionnés:

     import android.graphics.Canvas; import android.view.SurfaceHolder; public class MyThread extends Thread { private final SurfaceHolder mSurfaceHolder; private final MySurfaceView mSurface; private volatile boolean mRunning = false; public MyThread(SurfaceHolder surfaceHolder, MySurfaceView surface) { mSurfaceHolder = surfaceHolder; mSurface = surface; } public void setRunning(boolean run) { mRunning = run; } @Override public void run() { Canvas c; while (mRunning) { c = null; try { c = mSurfaceHolder.lockCanvas(); if (c != null) { mSurface.doDraw(c); } } finally { // when exception is thrown above we may not leave the surface in an inconsistent state if (c != null) { try { mSurfaceHolder.unlockCanvasAndPost(c); } catch (Exception e) { } } } } } } 

    Le code est, en partie, de l'exemple LunarLander dans le SDK Android, plus spécifiquement LunarView.java .

    La mise à jour du code pour correspondre à l'exemple amélioré d'Android 6.0 (niveau API 23) donne les éléments suivants:

     import android.graphics.Canvas; import android.view.SurfaceHolder; public class MyThread extends Thread { /** Handle to the surface manager object that we interact with */ private final SurfaceHolder mSurfaceHolder; private final MySurfaceView mSurface; /** Used to signal the thread whether it should be running or not */ private boolean mRunning = false; /** Lock for `mRunning` member */ private final Object mRunningLock = new Object(); public MyThread(SurfaceHolder surfaceHolder, MySurfaceView surface) { mSurfaceHolder = surfaceHolder; mSurface = surface; } /** * Used to signal the thread whether it should be running or not * * @param running `true` to run or `false` to shut down */ public void setRunning(final boolean running) { // do not allow modification while any canvas operations are still going on (see `run()`) synchronized (mRunningLock) { mRunning = running; } } @Override public void run() { while (mRunning) { Canvas c = null; try { c = mSurfaceHolder.lockCanvas(null); synchronized (mSurfaceHolder) { // do not allow flag to be set to `false` until all canvas draw operations are complete synchronized (mRunningLock) { // stop canvas operations if flag has been set to `false` if (mRunning) { mSurface.doDraw(c); } } } } // if an exception is thrown during the above, don't leave the view in an inconsistent state finally { if (c != null) { mSurfaceHolder.unlockCanvasAndPost(c); } } } } } 

    Mais encore, cette classe ne fonctionne pas sur les appareils mentionnés. Je reçois un écran noir et l'application cesse de répondre.

    La seule chose (que j'ai trouvé) qui corrige le problème est l'ajout de l'appel System.out.println("123") . Et en ajoutant un temps de sommeil court à la fin de la boucle pour fournir les mêmes résultats:

     try { Thread.sleep(10); } catch (InterruptedException e) { } 

    Mais ce ne sont pas des corrections réelles, n'est-ce pas? N'est-ce pas étrange?

    (Selon les modifications apportées au code, je peux également voir une exception dans le journal des erreurs. Il existe de nombreux développeurs avec le même problème, mais malheureusement, aucun ne fournit une solution pour mon cas (spécifique à un périphérique).

    Peux-tu aider?

    2 Solutions collect form web for “ANR dans SurfaceView sur des périphériques spécifiques uniquement – La seule solution est un court temps de sommeil”

    Ce qui fonctionne actuellement pour moi, bien que ne répare pas vraiment la cause du problème mais combat les symptômes de manière superficielle:

    1. Suppression des opérations de Canvas

    Mon thread de rendu appelle la méthode personnalisée doDraw(Canvas canvas) sur la sous-classe SurfaceView .

    Dans cette méthode, si je supprime tous les appels à Canvas.drawBitmap(...) , Canvas.drawRect(...) et d'autres opérations sur le Canvas , l'application ne gèle plus.

    Un appel unique à Canvas.drawColor(int color) peut être laissé dans la méthode. Et même des opérations coûteuses comme BitmapFactory.decodeResource(Resources res, int id, Options opts) et lecture / écriture dans mon cache Bitmap interne est bien. Pas de gel.

    Évidemment, sans aucun dessin, SurfaceView n'est pas vraiment utile.

    2. Dormir 10 ms dans la boucle de démarrage du thread de rendu

    La méthode que mon thread de rendu exécute:

     @Override public void run() { Canvas c; while (mRunning) { c = null; try { c = mSurfaceHolder.lockCanvas(); if (c != null) { mSurface.doDraw(c); } } finally { if (c != null) { try { mSurfaceHolder.unlockCanvasAndPost(c); } catch (Exception e) { } } } } } 

    Il suffit d'ajouter un temps de sommeil court dans la boucle (par exemple, à la fin) corrige tout le gel sur le LG G4:

     while (mRunning) { ... try { Thread.sleep(10); } catch (Exception e) { } } 

    Mais qui sait pourquoi cela fonctionne et si cela résout vraiment le problème (sur tous les périphériques).

    3. Impression de quelque chose sur System.out

    La même chose qui a fonctionné avec Thread.sleep(...) ci-dessus fonctionne également avec System.out.println("123") , étrangement.

    4. Retardant le début du thread de rendu de 10 ms

    C'est ainsi que je démarre mon thread de rendu à partir de SurfaceView :

     @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { mRenderThread = new MyThread(getHolder(), this); mRenderThread.setRunning(true); mRenderThread.start(); } 

    Lors de l'enroulement de ces trois lignes dans l'exécution retardée suivante, l'application ne gèle plus:

     new Handler().postDelayed(new Runnable() { @Override public void run() { ... } }, 10); 

    Cela semble être parce qu'il n'y a qu'une impasse présumée au début. Si cela est effacé (avec l'exécution retardée), il n'y a pas d'autre impasse. L'application fonctionne très bien après cela.

    Mais en quittant l' Activity , l'application gèle encore.

    5. Utilisez simplement un autre appareil

    Outre le LG G4, Sony Xperia Z4, Huawei Ascend Mate 7, HTC M9 (et probablement quelques autres appareils), l'application fonctionne bien sur des milliers de périphériques.

    Cela pourrait-il être un problème spécifique à l'appareil? On aurait certainement entendu parler de ça …


    Toutes ces "solutions" sont piratées. J'aimerais qu'il y ait une meilleure solution – et je parie qu'il existe!

    Regardez la trace ANR. Où semble-t-il être bloqué? Les ANR signifient que le thread UI principal ne répond pas, alors ce que vous faites sur le thread du rendu n'est pas pertinent, à moins que les deux ne se battent sur un verrou.

    Les symptômes que vous signalez ressemblent à une course. Si votre thread UI principal est bloqué, disons, mRunningLock , il est concevable que votre thread de rendu ne le laisse que déverrouiller pour une fenêtre très courte. L'ajout du message de journal ou de l'appel de veille donne au fil principal l'opportunité de se réveiller et de travailler avant que le fil du rendu ne le saisisse de nouveau.

    (Cela n'a pas vraiment de sens pour moi – votre code semble qu'il devrait être bloqué en attente de lockCanvas() en attendant le rafraîchissement de l'affichage, donc vous devez regarder la trace de thread dans l'ANR.)

    FWIW, vous n'avez pas besoin de synchroniser sur mSurfaceHolder . Un premier exemple l'a fait, et chaque exemple depuis lors l'a cloné.

    Une fois que vous avez réglé ce problème, vous voudrez peut-être lire sur les boucles de jeu .

    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.