2010-02-02 24 views
7

Mi programa arrojó una NullPointerException el otro día cuando intentó usar un controlador creado en otro hilo para enviar un mensaje a ese hilo. El controlador creado por el otro hilo aún no se ha creado, o aún no es visible para el hilo de llamada, a pesar de que el hilo de llamada ya ha llamado a iniciar el otro hilo. Esto solo ocurre muy raramente. Casi cada ejecución de prueba no obtiene la excepción.¿Cómo me aseguro de que otro Thread's Handler no sea nulo antes de llamarlo?

Me preguntaba cuál es la mejor manera de evitar este problema con una complicación mínima y una penalización de rendimiento. El programa es un juego muy sensible al rendimiento, especialmente una vez que se está ejecutando. Por lo tanto, trato de evitar usar la sincronización después de la configuración, por ejemplo, y preferiría evitar girar en una variable en cualquier momento.

Antecedentes:
En Android, la clase Handler se puede utilizar para "poner en cola una acción que se realizará en un hilo diferente al suyo". Documentación aquí:
http://developer.android.com/intl/de/reference/android/os/Handler.html

El controlador debe crearse en la secuencia donde se utilizará. Entonces, crearlo en el constructor de un hilo, que es ejecutado por el hilo que crea ese hilo, no es una opción.

cuando el manejador es para un subproceso distinto del hilo de interfaz de usuario, la clase Looper debe usarse también:
http://developer.android.com/intl/de/reference/android/os/Looper.html

La documentación da a este ejemplo de uso de las dos clases para este propósito:

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

Mi muy feo solución actualmente se ve así:

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

} 

Con el bacalao e en el hilo de creación que se ve así:

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

¿Hay alguna solución mejor? Gracias.

Respuesta

4

Preparación de un Looper puede bloquear por un tiempo, así que me imagino que está golpeando una condición donde prepare() toma un momento para completar, por lo tanto mHandler sigue siendo indefinido.

Puede hacer que su Thread extienda HandlerThread, aunque aún así tendrá que esperar hasta que se haya inicializado Looper. Tal vez algo así podría funcionar, donde tiene el Handler definido por separado, pero utilizando el Looper de su hilo personalizado.

Quizás.

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 puede hacer el bloqueo en lugar de tener que lidiar con la complejidad adicional. Para mi aplicación particular, ni siquiera necesito la subclase HandlerThread. –

11

me gustaría ir con la espera clásica/notificar

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 regresó de getHandler luego puede ser utilizado muchas veces sin invocar sincronizada getHandler.

+0

Mucho más limpio. Gracias. Esta es una buena respuesta y la voté igual que la marcada. –

1

sólo quiero añadir que la respuesta comprobado es la mejor, pero si se prueba que así no va a funcionar lástima pues es necesario llamar súper en ejecución de méthode ya que es el encargado de preparar la lanzadera de modo el código debe ser como este:

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

Buena corrección, gracias. Otra opción es no anular la ejecución en la subclase HandlerThread, o no subclasificarla en absoluto. That Handler es lo que normalmente funciona en el otro subproceso cuando recibe mensajes de todos modos. La implementación predeterminada de HandlerThread solo es necesaria para ejecutar el Looper. –

Cuestiones relacionadas