Comment les rappels de SurfaceHolder sont-ils liés au cycle de vie de l’activité?

J’ai essayé d’implémenter une application qui nécessite un aperçu de la caméra sur une surface. À mon avis, les cycles de vie des activités et des surfaces sont les suivants:

  1. Lorsque je lance mon activité pour la première fois: onResume()->onSurfaceCreated()->onSurfaceChanged()
  2. Quand je quitte mon activité: onPause()->onSurfaceDestroyed()

Dans ce schéma, je peux faire les appels correspondants comme ouvrir / libérer la caméra et démarrer / arrêter la prévisualisation dans onPause/onResume et onSurfaceCreated()/onSurfaceDestroyed() .

Cela fonctionne bien, sauf si je verrouille l’écran. Lorsque je lance l’application, puis verrouille l’écran et le déverrouille plus tard, je vois:

onPause() – et rien d’autre après le locking de l’écran – puis onResume() après le délocking – et aucun rappel de surface après. En fait, onResume() est appelé après avoir appuyé sur le bouton d’alimentation et que l’écran est onResume() , mais l’écran de locking est toujours actif. C’est donc avant que l’activité devienne encore visible.

Avec ce schéma, j’obtiens un écran noir après le délocking et aucun rappel de surface n’est appelé.

Voici un fragment de code qui n’implique pas de travail réel avec la caméra, mais les rappels de SurfaceHolder . Le problème ci-dessus est reproduit même avec ce code sur mon téléphone (les rappels sont appelés dans une séquence normale lorsque vous appuyez sur le bouton “Retour”, mais sont manquants lorsque vous verrouillez l’écran):

 class Preview extends SurfaceView implements SurfaceHolder.Callback { private static final Ssortingng 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"); } } 

Des idées sur la raison pour laquelle la surface rest non détruite après la pause de l’activité? En outre, comment gérez-vous le cycle de vie des caméras dans de tels cas?

Edit: si le targetSDK est supérieur à 10, mettre l’application en veille appelle onPause et onStop . La source

J’ai regardé le cycle de vie de l’activité et de SurfaceView dans une application de caméra minuscule sur mon téléphone de pain d’épice. Vous avez tout à fait raison la surface n’est pas détruite lorsque vous appuyez sur le bouton d’alimentation pour mettre le téléphone en veille. Lorsque le téléphone se met en veille, l’activité est onPause . (Et ne le fait pas sur onStop .) Il le fait lorsque le téléphone se réveille et, comme vous le faites remarquer, il le fait lorsque l’écran de locking est toujours visible et accepte les entrées, ce qui est un peu étrange. Lorsque je rend l’activité invisible en appuyant sur le bouton Accueil, l’activité effectue à la fois onPause et onStop . Quelque chose provoque un rappel à surfaceDestroyed dans ce cas entre la fin de onPause et le début de onStop . Ce n’est pas très évident, mais cela semble très cohérent.

Lorsque vous appuyez sur le bouton d’alimentation pour mettre le téléphone en veille, à moins que quelque chose ne soit explicitement fait pour l’arrêter, l’appareil photo continue de fonctionner! Si la caméra effectue un rappel par image pour chaque image d’aperçu, avec un fichier Log.d (), les instructions du journal continuent à arriver pendant que le téléphone fait semblant de dormir. Je pense que c’est très sournois .

Comme autre confusion, les rappels à surfaceCreated et surfaceChanged se produisent après onResume dans l’activité, si la surface est en cours de création.

En règle générale, je gère la caméra dans la classe qui implémente les rappels 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; } } } 

et ensuite dans l’activité:

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

et cela semble fonctionner correctement pour moi. Les événements de rotation entraînent la destruction et la recréation de l’activité, ce qui entraîne la destruction et la recréation de SurfaceView.

Une autre solution simple qui fonctionne bien – pour changer la visibilité de la surface de prévisualisation.

 private SurfaceView preview; 

L’aperçu est init dans la méthode onCreate . Dans la méthode onResume , définissez View.VISIBLE pour la surface de prévisualisation:

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

et respectivement dans onPause définir la visibilité View.GONE :

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

Grâce aux deux réponses précédentes, j’ai réussi à faire en sorte que mon aperçu de la caméra fonctionne parfaitement en revenant de l’arrière-plan ou de l’écran de locking.

Comme @ e7fendy l’a mentionné, le rappel de SurfaceView ne sera pas appelé pendant le locking de l’écran, car la vue de la surface est toujours visible pour le système.

Par conséquent, comme @validcat a conseillé, appelez preview.setVisibility(View.VISIBLE); et preview.setVisibility(View.GONE); dans respectivement onPause () et onResume () forceront la vue de surface à se retransmettre et l’appelleront des callbacks.

D’ici là, la solution de @ emrys57 plus ces deux appels de méthode de visibilité ci-dessus rendront votre aperçu de la caméra parfaitement fonctionnel 🙂

Donc je ne peux donner que +1 à chacun de vous comme vous l’avez tous mérité;)

SurfaceHolder.Callback est lié à sa surface.

L’activité est-elle à l’écran? Si c’est le cas, il n’y aura pas SurfaceHolder.Callback, car la Surface est toujours à l’écran.

Pour contrôler n’importe quel SurfaceView, vous pouvez le gérer uniquement dans onPause / onResume. Pour SurfaceHolder.Callback, vous pouvez l’utiliser si la Surface est modifiée (créée, dimensionnée et détruite), par exemple pour initialiser OpenGL lorsque SurfaceCreated, et pour détruire OpenGL lorsque surfaceDestroyed, etc.

Voici une solution alternative pour toutes les méthodes de rappel, qui peuvent toutes être soumises au même comportement d’ordre des événements non défini avec le cycle d’activité. À moins d’inspecter tout le code android pour chaque rappel que vous utilisez pour déterminer le déclencheur d’origine et qui contrôle les implémentations et espère que la base de code ne changera plus, peut-on vraiment dire que l’ordre des événements entre les appels et les événements du cycle de vie des activités pourraient être garantis.

À l’heure actuelle, ces interactions d’ordre peuvent généralement être qualifiées de comportement indéfini, à des fins de développement.

Il serait donc préférable de toujours gérer correctement ce comportement indéfini, de sorte qu’il ne s’agira jamais d’un problème en premier lieu, en veillant à ce que les ordres soient définis.

Mon Sony Xperia, par exemple, en mode veille, modifie mon application actuelle en détruisant l’application, puis en la redémarrant et en la mettant dans l’état de pause, croyez-le ou non.

Je ne sais pas dans quelle mesure le test de comportement des ordres d’événements google fournit dans leur SDK une version de test spéciale pour les implémentations d’environnement hôte, mais ils doivent absolument s’assurer que les comportements matière.

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;

/ ** * Créé par woliver le 24/06/2016. * * Environnement hôte Android, dicte un cycle de vie d’activité pour OnCreate, onStart, onResume, onPause, onStop, onDestory, * où nous devons libérer de la mémoire et gérer les autres applications à utiliser. * Lorsque nous reprenons, nous sums parfois obligés de relier et d’activer ces éléments avec d’autres objects. * En général, ces autres objects fournissent des méthodes de rappel depuis l’environnement hôte qui fournissent * un onCreated et un onDestroy, dans lesquelles nous ne pouvons nous lier qu’à cet object à partir d’OnCreated et de la liaison onDestory. * Ce type de méthode de rappel, le temps nécessaire pour l’exécuter est contrôlé par notre environnement hôte * et rien ne garantit que le comportement / l’ordre d’exécution du cycle de vie d’activité et de ces méthodes de rappel * rest cohérent. * Pour les besoins du développement, les interactions et l’ordre d’exécution peuvent techniquement être appelés non définis *, comme c’est le cas pour l’implémentation de l’implémentation de l’hôte, samsung, sony, htc. * * Voir document de développement suivant: https://developer.android.com/reference/android/app/Activity.html * Quote: * Si une activité est complètement masquée par une autre activité, elle est arrêtée. Il conserve toujours toutes les informations d’état * et de membre, cependant, il n’est plus visible pour l’utilisateur, donc sa fenêtre est * cachée et il sera souvent tué par le système lorsque la mémoire est nécessaire ailleurs. * EndQuato: * * Si l’activité n’est pas cachée, les appels que le système hôte * aurait dû appeler, n’auront pas été appelés, comme les méthodes OnCreate et OnDestory, l’interface du rappel SurfaceView. * Cela signifie que vous devrez arrêter l’object qui a été lié à SurfaceView tel qu’une caméra * en pause et ne lierez jamais l’object car le rappel OnCreate ne sera jamais appelé. * * /

 public abstract class WaitAllActiveExecuter { private SparseArray 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 Ssortingng mContextStr; public WaitAllActiveExecuter(Ssortingng contextStr, int... identifiers) { mReferancesState = new SparseArray(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(); } 

Exemple d'implémentation et d'utilisation de classe, qui traite du comportement non défini des implémenteurs de l'environnement hôte android.

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