2012-07-15 19 views
63

He intentado implementar una aplicación que requiere una vista previa de la cámara en una superficie. Como veo las cosas, tanto en los ciclos de vida de actividad y de la superficie consisten en los siguientes estados:¿Cómo se relacionan las devoluciones de llamada de SurfaceHolder con el ciclo de vida de la actividad?

  1. La primera vez que lanzar mi Actividad: onResume()->onSurfaceCreated()->onSurfaceChanged()
  2. Cuando salgo de mi Actividad: onPause()->onSurfaceDestroyed()

En este esquema, puedo hacer llamadas correspondientes como abrir/soltar la cámara e iniciar/detener la vista previa en onPause/onResume y onSurfaceCreated()/onSurfaceDestroyed().

Funciona bien, a menos que bloquee la pantalla. Cuando inicio de la aplicación, a continuación, bloquear la pantalla y desbloquear más tarde veo:

onPause() - y nada más después de que la pantalla esté bloqueada - a continuación, después de la liberación onResume() - y no hay devoluciones de llamada de la superficie después de entonces. En realidad, se llama al onResume() después de presionar el botón de encendido y la pantalla está encendida, pero la pantalla de bloqueo todavía está activa, por lo tanto, es antes de que la actividad se vuelva incluso visible.

Con este esquema, aparece una pantalla negra después del desbloqueo, y no se realizan llamadas a la superficie.

Aquí hay un fragmento de código que no implica el trabajo real con la cámara, pero las devoluciones de llamada SurfaceHolder. El problema anterior se reproduce incluso con este código en mi teléfono (devoluciones de llamada se denominan en una secuencia normal cuando se pulsa el botón "Volver", pero faltan cuando se bloquea la pantalla):

class Preview extends SurfaceView implements SurfaceHolder.Callback { 

    private static final String tag= "Preview"; 

    public Preview(Context context) { 
     super(context); 
     Log.d(tag, "Preview()"); 
     SurfaceHolder holder = getHolder(); 
     holder.addCallback(this); 
     holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 
    } 

    public void surfaceCreated(SurfaceHolder holder) { 
     Log.d(tag, "surfaceCreated"); 
    } 

    public void surfaceDestroyed(SurfaceHolder holder) { 
     Log.d(tag, "surfaceDestroyed"); 
    } 

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 
     Log.d(tag, "surfaceChanged"); 
    } 
} 

¿Alguna idea de por qué el la superficie permanece sin destruir después de que la Actividad está en pausa? Además, ¿cómo manejas el ciclo de vida de la cámara en tales casos?

+0

¿En qué nivel Plaform/API de Android está desarrollando? – FerranB

Respuesta

51

Editar: si el targetSDK es mayor de 10, poniendo la aplicación en las llamadas de espera yonStop. Source

Miré el ciclo de vida de Activity y SurfaceView en una pequeña aplicación de cámara en mi teléfono Gingerbread. Usted es completamente correcto; la superficie no se destruye cuando se presiona el botón de encendido para poner el teléfono en modo de suspensión. Cuando el teléfono se va a dormir, la Actividad es . (Y no lo hace onStop). Hace onResume cuando el teléfono se activa y, como usted señala, hace esto mientras la pantalla de bloqueo sigue siendo visible y acepta entradas, lo cual es un poco extraño. Cuando hago que la Actividad sea invisible presionando el botón de Inicio, la Actividad hace tanto como onStop. Algo provoca una devolución de llamada al surfaceDestroyed en este caso entre el final de y el inicio de onStop.No es muy obvio, pero parece muy consistente.

Cuando se presiona el botón de encendido para apagar el teléfono, a menos que se haga algo explícitamente para detenerlo, ¡la cámara sigue funcionando! Si hago que la cámara realice una devolución de llamada por imagen para cada fotograma de vista previa, con un Log.d() allí, las declaraciones de registro siguen apareciendo mientras el teléfono pretende dormir. Creo que es Very Sneaky.

Como otro confusión, las devoluciones de llamada a surfaceCreated y surfaceChanged suceden después deonResume en la actividad, si se está creando la superficie.

Como regla, administro la cámara en la clase que implementa las devoluciones de llamada de SurfaceHolder.

class Preview extends SurfaceView implements SurfaceHolder.Callback { 
    private boolean previewIsRunning; 
    private Camera camera; 

    public void surfaceCreated(SurfaceHolder holder) { 
     camera = Camera.open(); 
     // ... 
     // but do not start the preview here! 
    } 

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 
     // set preview size etc here ... then 
     myStartPreview(); 
    } 

    public void surfaceDestroyed(SurfaceHolder holder) { 
     myStopPreview(); 
     camera.release(); 
     camera = null; 
    } 

    // safe call to start the preview 
    // if this is called in onResume, the surface might not have been created yet 
    // so check that the camera has been set up too. 
    public void myStartPreview() { 
     if (!previewIsRunning && (camera != null)) { 
      camera.startPreview(); 
      previewIsRunning = true; 
     } 
    } 

    // same for stopping the preview 
    public void myStopPreview() { 
     if (previewIsRunning && (camera != null)) { 
      camera.stopPreview(); 
      previewIsRunning = false; 
     } 
    } 
} 

y luego en la Actividad:

@Override public void onResume() { 
    preview.myStartPreview(); // restart preview after awake from phone sleeping 
    super.onResume(); 
} 
@Override public void onPause() { 
    preview.myStopPreview(); // stop preview in case phone is going to sleep 
    super.onPause(); 
} 

y que parece funcionar bien para mí. Los eventos de rotación hacen que la actividad se destruya y se vuelva a crear, lo que hace que SurfaceView se destruya y recree también.

+0

¿Por qué antes de la súper llamada? –

+0

No soy consciente de que importa si el código se ejecuta antes o después de la súper llamada. Es importante que se llame a 'super.onResume' en algún lugar de la rutina' onResume'.Creo. – emrys57

+0

En realidad, depende de la inicialización de la vista de superficie y del visualizador de superficie. Si realiza alguna tarea sincrónica, entonces inicializa la vista y luego nunca llama después de Reanudar. incluso no después de la tarea sincrónica. Pero cuando me muevo a otra actividad y reanudo, entonces se devuelve la llamada a la función SurfaceSurfaceCreated o Change. ¡Por cierto, gracias! @ emrys57. Al menos he resuelto que mi problema es con Surfaceview. :) –

1

SurfaceHolder.Callback está relacionado con su superficie.

¿Está la actividad en la pantalla? Si es así, no habrá SurfaceHolder.Callback, porque la Superficie aún está en la pantalla.

Para controlar cualquier SurfaceView, puede manejarlo en OnPause/onResume solamente. Para SurfaceHolder.Callback, se puede usar si se cambia la superficie (creado, SizeChanged, y destruyó), como inicializar OpenGL cuando surfaceCreated, y destruir OpenGL cuando surfaceDestroyed, etc.

18

Otra solución simple que funciona bien: para cambiar la visibilidad de la superficie de vista previa.

private SurfaceView preview; 

vista previa es init en el método onCreate. En onResume método establecido View.VISIBLE para la superficie previsualización:

@Override 
public void onResume() { 
    preview.setVisibility(View.VISIBLE); 
    super.onResume(); 
} 

y, respectivamente, en visibilidad conjunto View.GONE:

@Override 
public void onPause() { 
    super.onPause(); 
    preview.setVisibility(View.GONE); 
    stopPreviewAndFreeCamera(); //stop and release camera 
} 
+0

¡Eres un salvavidas! – gtsouk

+0

Confirmando que esto también funciona con las API de Camera2. –

1

Gracias a ambos todas las respuestas anteriores he conseguido hacer mi trabajo de cámara de vista previa claridad, mientras que ir de vuelta de ya sea de fondo o bloqueos de pantalla.

Como se menciona en @ e7fendy, la devolución de llamada de SurfaceView no se ejecutará mientras se está en el bloqueo de pantalla, ya que la vista de superficie aún está visible para el sistema.

Por lo tanto, como se recomienda @validcat, llamar a preview.setVisibility(View.VISIBLE); y preview.setVisibility(View.GONE); respectivamente en OnPause() y onResume() forzará a la vista de superficie a retransmitirse y lo llamará devoluciones de llamada.

Para entonces, la solución de @ emrys57 además de estos dos método de visibilidad por encima de llama hará que su trabajo de cámara de vista previa claramente :)

así que sólo puedo dar +1 a cada uno de ustedes como todos ustedes lo merecía;)

-2

Aquí hay una solución alternativa para todos los métodos de devolución de llamada, que pueden estar todos sujetos al mismo comportamiento de orden de evento indefinido con ciclo de actividad. A menos que vaya a inspeccionar todo el código de Android para cada devolución de llamada que usa para determinar el desencadenante de origen y quién controla las implementaciones y espera que la base de código no cambie en el futuro, ¿puede decirse realmente que el orden de eventos entre retrollamadas? y los eventos del ciclo de vida de la actividad podrían estar garantizados.

En este momento, estas interacciones de orden normalmente se pueden denominar comportamientos indefinidos, con fines de desarrollo.

Lo mejor sería manejar correctamente este comportamiento indefinido, de manera que nunca será un problema en primer lugar, asegúrate de que las órdenes sean un comportamiento definido.

Mi Sony Xperia, por ejemplo, mientras duerme, hace un ciclo de mi aplicación actual, destruyendo la aplicación y luego reiniciando y poniéndola en estado de pausa, créalo o no.

Cuántas pruebas de comportamiento de ordenación de eventos proporciona Google en su SDK como construcción de prueba especial para implementos de entorno host No lo sé, pero definitivamente es necesario hacer un esfuerzo para garantizar que los comportamientos de las órdenes de eventos estén bloqueados por ser bastante estricto al respecto.

https://code.google.com/p/android/issues/detail?id=214171&sort=-opened&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened

importación android.util.Log; import android.util.SparseArray;

/** * Creado por woliver en 2016/06/24. * * entorno de host Android, dicta un Ciclo de vida de la actividad para OnCreate, onStart, onResume, onPause, onStop, onDestory, * donde estamos obligados a liberar memoria y maneja para que otras aplicaciones utilicen. * Cuando se reanuda, a veces se nos solicita volver a vincular y activar estos elementos con otros objetos. * Normalmente, estos otros objetos proporcionan métodos de devolución de llamada desde el entorno del host que proporcionan * onCreated y onDestroy, en los que solo podemos enlazar a este objeto desde OnCreated y * out bind onDestory. * Este tipo de métodos de devolución de llamada, tiempo de ejecución temporal es controlado por nuestro entorno de host * y no garantizan que el comportamiento/orden de ejecución del Ciclo de vida de la actividad y estos métodos de devolución de llamada * permanezcan uniformes. * Con fines de desarrollo, las interacciones y el orden de ejecución se pueden llamar técnicamente indefinidos *, ya que depende del implementador de la implementación del host, samsung, sony, htc. * * Consulte el siguiente documento del desarrollador: https://developer.android.com/reference/android/app/Activity.html * Cita: * Si una actividad está completamente oscurecida por otra actividad, se detiene. Aún conserva todos los estados * y la información de los miembros, sin embargo, ya no es visible para el usuario, por lo que su ventana está * oculta y, a menudo, el sistema la eliminará cuando se necesite memoria en otro lugar. * EndQuato: * * Si la actividad no está oculta, no se habrán llamado a las devoluciones de llamada que se esperaba del sistema host *, como los métodos OnCreate y OnDestory interfaz de devolución de llamada de SurfaceView. * Esto significa que tendrá que detener el objeto que se ha vinculado a SurfaceView, como una cámara * en pausa, y nunca volverá a vincular el objeto, ya que nunca se llamará a la devolución de llamada OnCreate. * */

public abstract class WaitAllActiveExecuter<Size> 
{ 
    private SparseArray<Boolean> mReferancesState = null; 

// Use a dictionary and not just a counter, as hosted code 
// environment implementer may make a mistake and then may double executes things. 
private int mAllActiveCount = 0; 
private String mContextStr; 

public WaitAllActiveExecuter(String contextStr, int... identifiers) 
{ 
    mReferancesState = new SparseArray<Boolean>(identifiers.length); 

    mContextStr = contextStr; 

    for (int i = 0; i < identifiers.length; i++) 
     mReferancesState.put(identifiers[i], false); 
} 

public void ActiveState(int identifier) 
{ 
    Boolean state = mReferancesState.get(identifier); 

    if (state == null) 
    { 
     // Typically panic here referance was not registered here. 
     throw new IllegalStateException(mContextStr + "ActiveState: Identifier not found '" + identifier + "'"); 
    } 
    else if(state == false){ 

     mReferancesState.put(identifier, true); 
     mAllActiveCount++; 

     if (mAllActiveCount == mReferancesState.size()) 
      RunActive(); 
    } 
    else 
    { 
     Log.e(mContextStr, "ActivateState: called to many times for identifier '" + identifier + "'"); 
     // Typically panic here and output a log message. 
    } 
} 

public void DeactiveState(int identifier) 
{ 
    Boolean state = mReferancesState.get(identifier); 

    if (state == null) 
    { 
     // Typically panic here referance was not registered here. 
     throw new IllegalStateException(mContextStr + "DeActiveState: Identifier not found '" + identifier + "'"); 
    } 
    else if(state == true){ 

     if (mAllActiveCount == mReferancesState.size()) 
      RunDeActive(); 

     mReferancesState.put(identifier, false); 
     mAllActiveCount--; 
    } 
    else 
    { 
     Log.e(mContextStr,"DeActiveState: State called to many times for identifier'" + identifier + "'"); 
     // Typically panic here and output a log message. 
    } 
} 

private void RunActive() 
{ 
    Log.v(mContextStr, "Executing Activate"); 

    ExecuterActive(); 
} 

private void RunDeActive() 
{ 
    Log.v(mContextStr, "Executing DeActivate"); 

    ExecuterDeActive(); 
} 


abstract public void ExecuterActive(); 

abstract public void ExecuterDeActive(); 
} 

Ejemplo de Aplicación y uso de la clase, que se ocupa o el comportamiento indefinido de ambiente anfitrión androide implementadores.

private final int mBCTSV_SurfaceViewIdentifier = 1; 
private final int mBCTSV_CameraIdentifier = 2; 

private WaitAllActiveExecuter mBindCameraToSurfaceView = 
     new WaitAllActiveExecuter("BindCameraToSurfaceViewe", new int[]{mBCTSV_SurfaceViewIdentifier, mBCTSV_CameraIdentifier}) 
{ 
    @Override 
    public void ExecuterActive() { 

     // Open a handle to the camera, if not open yet and the SurfaceView is already intialized. 
     if (mCamera == null) 
     { 
      mCamera = Camera.open(mCameraIDUsed); 

      if (mCamera == null) 
       throw new RuntimeException("Camera could not open"); 

      // Look at reducing the calls in the following two methods, some this is unessary. 
      setDefaultCameraParameters(mCamera); 
      setPreviewSizesForCameraFromSurfaceHolder(getSurfaceHolderForCameraPreview()); 
     } 

     // Bind the Camera to the SurfaceView. 
     try { 
      mCamera.startPreview(); 
      mCamera.setPreviewDisplay(getSurfaceHolderForCameraPreview()); 
     } catch (IOException e) { 

      e.printStackTrace(); 
      ExecuterDeActive(); 

      throw new RuntimeException("Camera preview could not be set"); 
     } 
    } 

    @Override 
    public void ExecuterDeActive() { 

     if (mCamera != null) 
     { 
      mCamera.stopPreview(); 

      mCamera.release(); 
      mCamera = null; 
     } 
    } 
}; 

@Override 
protected void onPause() { 


    mBindCameraToSurfaceView.DeactiveState(mBCTSV_CameraIdentifier); 

    Log.v(LOG_TAG, "Activity Paused - After Super"); 
} 

@Override 
public void onResume() { 

    mBindCameraToSurfaceView.ActiveState(mBCTSV_CameraIdentifier); 
} 

private class SurfaceHolderCallback implements SurfaceHolder.Callback 
{ 
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 
    Log.v(LOG_TAG, "Surface Changed"); 

    } 

    public void surfaceCreated(SurfaceHolder surfaceHolder) { 

     Log.v(LOG_TAG, "Surface Created"); 
     mBindCameraToSurfaceView.ActiveState(mBCTSV_SurfaceViewIdentifier); 
    } 

    public void surfaceDestroyed(SurfaceHolder arg0) { 

     Log.v(LOG_TAG, "Surface Destoryed"); 
     mBindCameraToSurfaceView.DeactiveState(mBCTSV_SurfaceViewIdentifier); 
    } 
} 
Cuestiones relacionadas