2012-07-15 12 views
63

Ho cercato di implementare un'applicazione che richiede l'anteprima della fotocamera su una superficie. Per come la vedo le cose, sia l'attività di superficie e cicli di vita sono costituite dai seguenti stati:In che modo i callback di SurfaceHolder sono correlati al ciclo di vita di Activity?

  1. Quando ho primo lancio la mia attività: onResume()->onSurfaceCreated()->onSurfaceChanged()
  2. Quando lascio la mia attività: onPause()->onSurfaceDestroyed()

In questo schema, posso fare chiamate corrispondenti come aprire/rilasciare la fotocamera e avviare/interrompere l'anteprima in onPause/onResume e onSurfaceCreated()/onSurfaceDestroyed().

Funziona bene, a meno che non blocchi lo schermo. Quando avvio l'app, quindi blocco dello schermo e sblocco in seguito vedo:

onPause() - e nient'altro dopo che lo schermo è bloccato - quindi onResume() dopo lo sblocco - e nessun callback di superficie dopo. In realtà, onResume() viene chiamato dopo aver premuto il pulsante di alimentazione e lo schermo è attivo, ma la schermata di blocco è ancora attiva, quindi è prima che l'attività diventi visibile.

Con questo schema, ottengo uno schermo nero dopo lo sblocco e non si chiamano callback di superficie.

Ecco un frammento di codice che non implica il lavoro effettivo con la fotocamera, ma i callback SurfaceHolder. Il problema di cui sopra è riprodotto anche con questo codice sul mio cellulare (callback vengono chiamati in una sequenza normale quando si preme il pulsante "Indietro", ma mancano quando si blocca lo schermo):

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

Tutte le idee sul perché il la superficie rimane non distrutta dopo che l'attività è stata messa in pausa? Inoltre, come gestisci il ciclo di vita della fotocamera in questi casi?

+0

In quale livello di Plaform/API Android si sta sviluppando? – FerranB

risposta

51

Edit: se il targetSDK è maggiore di 10, mettendo l'app a riposo chiamate eonStop. Source

Ho esaminato il ciclo di vita di Activity e SurfaceView in una piccola app fotocamera sul mio telefono di pan di zenzero. Sei completamente corretto; la superficie non viene distrutta quando si preme il pulsante di accensione per mettere il telefono in stato di Stop. Quando il telefono entra in modalità di sospensione, l'attività viene eseguita su . (E non lo fa onStop.) Lo fa onResume quando il telefono si sveglia, e, come si fa notare, lo fa mentre la schermata di blocco è ancora visibile e accetta l'input, che è un po 'strano. Quando rendo l'attività invisibile premendo il pulsante Home, l'attività fa sia e onStop. Qualcosa causa una richiamata a surfaceDestroyed in questo caso tra la fine di e l'inizio di onStop.Non è molto ovvio, ma sembra molto coerente.

Quando si preme il pulsante di accensione per spegnere il telefono, a meno che non venga fatto qualcosa in modo esplicito per fermarlo, la fotocamera continua a funzionare! Se ho la fotocamera fare una richiamata per immagine per ogni fotogramma di anteprima, con un Log.d() in là, le dichiarazioni di registro continuano a venire mentre il telefono fa finta di dormire. Penso che sia Very Sneaky.

Come ulteriore confusione, i richiami alla surfaceCreated e surfaceChanged accadono dopoonResume nell'attività, se viene creata la superficie.

Come regola generale, gestisco la telecamera nella classe che implementa i callback di 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; 
     } 
    } 
} 

e poi in attività:

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

e che sembra funzionare bene per me. Gli eventi di rotazione fanno sì che l'attività venga distrutta e ricreata, il che fa sì che SurfaceView sia distrutto e ricreato.

+0

Perché prima della super chiamata? –

+0

Non sono a conoscenza del fatto che sia importante se il codice viene eseguito prima o dopo la super chiamata. È semplicemente importante che 'super.onResume' venga chiamato da qualche parte nella routine' onResume'.Credo. – emrys57

+0

In realtà dipende dall'inizializzazione di surfaceview e surfaceviewholder. Se si esegue un'attività sincrona, la vista viene inizializzata e non viene mai eseguita dopo onResume. anche non dopo task. sincrono sincrono quando mi sposto in un'altra attività e riprendo quindi la funzione callback onSurfaceCreated o Change. A proposito, grazie! @ Emrys57. almeno ho risolto il mio problema è con surfaceview. :) –

1

SurfaceHolder.Callback è correlato alla sua superficie.

L'attività è visualizzata sullo schermo? In tal caso, non ci sarà SurfaceHolder.Callback, perché Surface è ancora sullo schermo.

Per controllare qualsiasi SurfaceView, è possibile gestirlo solo onPause/onResume. Per SurfaceHolder.Callback, è possibile utilizzarlo se la superficie è cambiato (creato, SizeChanged, e distrutto), come inizializzare OpenGL quando surfaceCreated, e distruggere openGL quando surfaceDestroyed, ecc

18

Un'altra soluzione semplice che funziona bene - per modificare la visibilità della superficie di anteprima.

private SurfaceView preview; 

anteprima è init in onCreate metodo. In onResume metodo impostato View.VISIBLE per superficie anteprima:

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

e, rispettivamente, in set visibilità View.GONE:

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

Sei un vero toccasana! – gtsouk

+0

Confermare che questo funziona anche con le API di Camera2. –

1

grazie sia tutte le risposte precedenti sono riuscito a fare la mia macchina fotografica anteprima lavoro chiaramente mentre andando indietro da sfondo o lockscreen.

Come @ e7fendy menzionato, la richiamata di SurfaceView non verrà chiamata mentre è sullo schermo poiché la vista di superficie è ancora visibile per il sistema.

Quindi, come consigliato @validcat, chiamando preview.setVisibility(View.VISIBLE); e preview.setVisibility(View.GONE); rispettivamente onPause() e onResume() obbligherà la vista di superficie a relayout stesso e chiameremo callback.

Da allora, la soluzione da @ emrys57 più questi due metodo visibilità chiama sopra renderà la vostra macchina fotografica anteprima lavoro chiaramente :)

Quindi posso solo dare uno a ciascuno di voi, come voi tutti meritato;)

-2

Ecco una soluzione alternativa per tutti i metodi di callback, che possono essere tutti soggetti allo stesso comportamento di ordine evento non definito con ciclo di attività. A meno che non si proceda a ispezionare tutto il codice di Android per ogni richiamo che si utilizza per determinare il trigger di origine e chi controlla le implementazioni e sperare che la base di codice non venga modificata in futuro, si può davvero affermare che l'ordine di eventi tra le richiamate e gli eventi del ciclo di vita delle attività potrebbero essere garantiti.

In questo momento queste interazioni di ordine possono essere generalmente definite comportamento non definito, a fini di sviluppo.

Quindi sarebbe meglio gestire sempre questo comportamento indefinito, in modo tale che non sarà mai un problema in primo luogo, assicurarsi che gli ordini siano comportamenti definiti.

Il mio Sony Xperia, ad esempio, in modalità sleep, esegue il ciclo della mia app corrente, distruggendo l'app, quindi riavviandola e inserendola nello stato di pausa, che ci crediate o meno.

evento quanto ordinato test comportamento di Google offre in prova il loro SDK come speciali costruire per ambiente host implementa Non lo so, ma sicuramente bisogno di fare uno sforzo per garantire, comportamenti di ordini di eventi sono tutti bloccati per essere piuttosto severo in materia.

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

import android.util.Log; import android.util.SparseArray;

/** * Creato da woliver il 24/06/2016. * * Ambiente host Android, determina un ciclo di vita attività per OnCreate, onStart, onResume, onPause, onStop, onDestory, * dove da parte nostra è necessario rilasciare memoria e handle per altre applicazioni da utilizzare. * Quando si riprende, a volte è necessario riavvolgere e attivare questi elementi con altri oggetti. * In genere questi altri oggetti forniscono metodi di callback dall'ambiente host che forniscono * un onCreated e onDestroy, in cui possiamo solo associare a questo oggetto da OnCreated e perdere * bind onDestory. * Questi tipi di metodi di richiamata, il tempo necessario per l'esecuzione è il controller dal nostro ambiente host * e non garantiscono che il comportamento/l'ordine di esecuzione del ciclo di vita dell'attività e questi metodi di richiamata * rimangano coerenti. * Ai fini dello sviluppo, le interazioni e l'ordine di esecuzione possono tecnicamente essere definiti non definiti * in quanto dipende dall'implementatore dell'implementazione host, samsung, sony, htc. * * Vedere il documento di sviluppo seguente: https://developer.android.com/reference/android/app/Activity.html * Citazione: * Se un'attività è completamente nascosta da un'altra attività, viene interrotta. Conserva ancora tutto lo stato * e le informazioni sui membri, tuttavia, non è più visibile all'utente, quindi la sua finestra è * nascosta e verrà spesso uccisa dal sistema quando la memoria è necessaria altrove. * EndQuato: * * Se l'attività non è nascosta, eventuali callback che ci si sarebbe aspettati di chiamare dal sistema host *, non sono stati chiamati, come i metodi OnCreate e OnDestory interfacciano il callback di SurfaceView. * Ciò significa che è necessario interrompere l'oggetto che è stato associato a SurfaceView come una fotocamera * in pausa e non potrà mai riassociare l'oggetto in quanto il callback OnCreate non verrà mai chiamato. * */

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

esempio di implementazione e l'uso di classe, che si occupa o il comportamento indefinito di Android ambiente ospite esecutori.

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