2010-02-02 1 views
7

Mon programme a lancé une exception NullPointerException l'autre jour lorsqu'il a essayé d'utiliser un gestionnaire créé sur un autre thread pour envoyer un message à ce thread. Le gestionnaire créé par l'autre thread n'a pas encore été créé ou n'est pas encore visible par le thread appelant, bien que le thread appelant ait déjà appelé start sur l'autre thread. Cela n'arrive que très rarement. Presque tous les tests ne présentent pas l'exception. Je me demandais quel est le meilleur moyen d'éviter ce problème avec une complication minimale et une pénalité de performance. Le programme est un jeu très sensible aux performances, surtout une fois qu'il est en cours d'exécution. Par conséquent, j'essaie d'éviter d'utiliser la synchronisation après l'installation, par exemple, et je préférerais éviter de tourner sur une variable à tout moment. Dans la version Android, la classe Handler peut être utilisée pour "mettre en file d'attente une action à exécuter sur un thread différent du vôtre". Documentation ici:
http://developer.android.com/intl/de/reference/android/os/Handler.htmlComment puis-je m'assurer qu'un autre gestionnaire de thread n'est pas nul avant de l'appeler?

Le gestionnaire doit être créé sur le thread où il sera utilisé. Donc, le créer dans le constructeur d'un thread, qui est exécuté par le thread créant ce thread, n'est pas une option.

Lorsque le gestionnaire est un autre thread que le thread d'interface utilisateur, la classe Looper doit également être utilisé:
http://developer.android.com/intl/de/reference/android/os/Looper.html

La documentation donne cet exemple d'utiliser les deux classes à cette fin:

class LooperThread extends Thread { 
    public Handler mHandler; 

    public void run() { 
     Looper.prepare(); 

     mHandler = new Handler() { 
      public void handleMessage(Message msg) { 
       // process incoming messages here 
      } 
     }; 

     Looper.loop(); 
    } 
} 

Ma solution très laid ressemble actuellement à ceci:

public class LooperThread extends Thread { 

    public volatile Handler mHandler; 

    public final ArrayBlockingQueue<Object> setupComplete = new ArrayBlockingQueue<Object>(1); 

    public void run() { 
     Looper.prepare(); 

     mHandler = new Handler() { 
      public void handleMessage(Message msg) { 
       // process incoming messages here 
      } 
     }; 

     setupComplete(); 

     Looper.loop(); 
    } 

    public void waitForSetupComplete() { 
     while (true) { 
      try { 
       setupComplete.take(); 
       return; 
      } catch (InterruptedException e) { 
       //Ignore and try again. 
      } 
     } 
    } 

    private void setupComplete() { 
     while(true) { 
      try { 
       setupComplete.put(new Object()); 
       return; 
      } catch (InterruptedException e) { 
       //Ignore and try again. 
      }   
     } 
    } 

} 

la morue e dans le fil de création ressemblant à ceci:

LooperThread otherThread = new LooperThread(); 
    otherThread.start();   
    otherThread.waitForSetupComplete(); 
    otherThread.mHandler.sendEmptyMessage(0); 

Y a-t-il de meilleures solutions? Merci.

Répondre

4

Préparer un Looper peut bloquer pendant un moment, donc j'imagine que vous atteignez une condition où prepare() prend un moment pour terminer, donc mHandler est encore indéfini.

Vous pouvez avoir votre Thread étendre HandlerThread, bien que même alors vous devez encore attendre pour vous assurer que le Looper a été initialisé. Peut-être quelque chose comme cela pourrait fonctionner, où vous avez le Handler défini séparément, mais en utilisant le Looper de votre fil personnalisé.

Peut-être.

private void setUp() { 
    mHandlerThread = new CustomThread("foo", Process.THREAD_PRIORITY_BACKGROUND); 
    mHandlerThread.start(); 

    // Create our handler; this will block until looper is initialised 
    mHandler = new CustomHandler(mHandlerThread.getLooper()); 
    // mHandler is now ready to use 
} 

private class CustomThread extends HandlerThread { 
    public void run() { 
     // ... 
    } 
} 

private class CustomHandler extends Handler { 
    CustomHandler(Looper looper) { 
     super(looper); 
    } 

    @Override 
    public void handleMessage(Message msg) { 
     // ... 
    } 
} 
+1

Ah, bien. HandlerThread # getLooper peut faire le blocage au lieu de devoir gérer la complexité supplémentaire. Pour mon application particulière, je n'ai même pas besoin de sous-classer HandlerThread. –

11

je partirais avec l'attente classique/notify

public class LooperThread extends Thread { 

    private Handler mHandler; 

    public void run() { 
     Looper.prepare(); 

     synchronized (this) { 
      mHandler = new Handler() { 
       public void handleMessage(Message msg) { 
        // process incoming messages here 
       } 
      }; 
      notifyAll(); 
     } 

     Looper.loop(); 
    } 

    public synchronized Handler getHandler() { 
     while (mHandler == null) { 
      try { 
       wait(); 
      } catch (InterruptedException e) { 
       //Ignore and try again. 
      } 
     } 
     return mHandler; 
    } 
} 

Handler est revenu de getHandler peut alors être utilisé plusieurs fois sans faire appel synchronisé getHandler.

+0

propre Beaucoup. Je vous remercie. C'est une bonne réponse et je l'ai voté ainsi que le vérifié. –

1

Je veux juste ajouter que la réponse vérifiée est le meilleur, mais si vous testez comme ça ne va travailler becouse vous devez appeler super sur run Methode car il est responsable de la préparation du boucleur si le code devrait ressembler à ceci:

private void setUp() { 
    mHandlerThread = new CustomThread("foo", Process.THREAD_PRIORITY_BACKGROUND); 
    mHandlerThread.start(); 

    // Create our handler; this will block until looper is initialised 
    mHandler = new CustomHandler(mHandlerThread.getLooper()); 
    // mHandler is now ready to use 
} 

private class CustomThread extends HandlerThread { 
    public void run() { 
    super.run() // <- VERY IMPORTANT OTHERWISE IT DOES NOT WORK 
    // your code goes here 
    } 
} 

private class CustomHandler extends Handler { 
CustomHandler(Looper looper) { 
    super(looper); 
} 

@Override 
public void handleMessage(Message msg) { 
    // ... 
} 

}

+0

Bonne correction, merci. Une autre option consiste à ne pas surcharger l'exécution de la sous-classe HandlerThread, ou à ne pas la sous-classer du tout. Ce gestionnaire est ce qui fonctionne normalement sur l'autre thread quand il reçoit des messages de toute façon. L'implémentation par défaut de HandlerThread est juste nécessaire pour exécuter le Looper. –

Questions connexes