Wie hängen SurfaceHolder-Rückrufe mit dem Aktivitätslebenszyklus zusammen?

73

Ich habe versucht, eine Anwendung zu implementieren, die eine Kameravorschau auf einer Oberfläche erfordert. Aus meiner Sicht bestehen sowohl Aktivität als auch Oberflächenlebenszyklus aus den folgenden Zuständen:

  1. Wenn ich meine Aktivität zum ersten Mal starte: onResume()->onSurfaceCreated()->onSurfaceChanged()
  2. Wenn ich meine Aktivität verlasse: onPause()->onSurfaceDestroyed()

In diesem Schema kann ich entsprechende Anrufe wie Öffnen / Freigeben der Kamera und Starten / Stoppen der Vorschau in onPause/onResumeund ausführen onSurfaceCreated()/onSurfaceDestroyed().

Es funktioniert gut, es sei denn, ich sperre den Bildschirm. Wenn ich die App starte, sperre ich den Bildschirm und entsperre ihn später. Ich sehe:

onPause()- und nichts anderes, nachdem der Bildschirm gesperrt wurde - dann onResume()nach dem Entsperren - und danach keine Oberflächenrückrufe. Wird tatsächlich onResume()aufgerufen, nachdem der Netzschalter gedrückt und der Bildschirm eingeschaltet wurde, der Sperrbildschirm jedoch noch aktiv ist. Dies ist also der Fall, bevor die Aktivität überhaupt sichtbar wird.

Mit diesem Schema wird nach dem Entsperren ein schwarzer Bildschirm angezeigt, und es werden keine Oberflächenrückrufe aufgerufen.

Hier ist ein Codefragment, das nicht die eigentliche Arbeit mit der Kamera beinhaltet, sondern die SurfaceHolderRückrufe. Das obige Problem wird auch mit diesem Code auf meinem Telefon reproduziert (Rückrufe werden in normaler Reihenfolge aufgerufen, wenn Sie die Taste "Zurück" drücken, fehlen jedoch, wenn Sie den Bildschirm sperren):

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

Irgendwelche Ideen, warum die Oberfläche nach dem Anhalten der Aktivität unzerstört bleibt? Wie gehen Sie in solchen Fällen mit dem Kamera-Lebenszyklus um?

krugloid
quelle
In welcher Android Plaform / API-Ebene entwickeln Sie?
FerranB

Antworten:

59

Bearbeiten: Wenn das targetSDK größer als 10 ist, wird die App in den Ruhezustand versetzt onPause und onStop . Quelle

Ich habe den Lebenszyklus von Activity und SurfaceView in einer winzigen Kamera-App auf meinem Lebkuchen-Handy betrachtet. Sie sind völlig richtig; Die Oberfläche wird nicht zerstört, wenn der Netzschalter gedrückt wird, um das Telefon in den Ruhezustand zu versetzen. Wenn das Telefon in den Ruhezustand wechselt, wird die Aktivität ausgeführt onPause. (Und nicht onStop.) onResumeDies geschieht, wenn das Telefon aufwacht, und wie Sie darauf hinweisen, geschieht dies, während der Sperrbildschirm noch sichtbar ist und Eingaben akzeptiert, was etwas seltsam ist. Wenn ich die Aktivität durch Drücken der Home-Taste unsichtbar mache, führt die Aktivität sowohl onPauseals auch aus onStop. Etwas verursacht surfaceDestroyedin diesem Fall einen Rückruf zwischen dem Ende onPauseund dem Anfang von onStop. Es ist nicht sehr offensichtlich, aber es scheint sehr konsequent zu sein.

Wenn der Netzschalter gedrückt wird, um das Telefon in den Ruhezustand zu versetzen, läuft die Kamera weiter, es sei denn, es wird ausdrücklich etwas unternommen, um es zu stoppen! Wenn die Kamera für jedes Vorschaubild einen Rückruf pro Bild mit einem Log.d () ausführt, werden die Protokollanweisungen immer wieder angezeigt, während das Telefon vorgibt, zu schlafen. Ich finde das sehr hinterhältig .

Als eine weitere Verwirrung, die Rückrufe surfaceCreatedund surfaceChangedpassieren nach onResume der Tätigkeit, wenn die Oberfläche erstellt wird.

In der Regel verwalte ich die Kamera in der Klasse, die die SurfaceHolder-Rückrufe implementiert.

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

und dann in der Aktivität:

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

und das scheint für mich in Ordnung zu sein. Rotationsereignisse führen dazu, dass die Aktivität zerstört und neu erstellt wird, wodurch auch die SurfaceView zerstört und neu erstellt wird.

emrys57
quelle
Warum vor dem Superanruf?
Basickarl
Mir ist nicht bewusst, dass es wichtig ist, ob der Code vor oder nach dem Superaufruf ausgeführt wird. Es ist nur wichtig, dass super.onResumeirgendwo in der onResumeRoutine aufgerufen wird . Ich denke.
Emrys57
Tatsächlich hängt es von der Initialisierung der Oberflächenansicht und des Oberflächenansichtshalters ab. Wenn Sie eine synchrone Aufgabe ausführen, initialisieren Sie die Ansicht und werden nach onResume nie mehr aufgerufen. Auch nicht nach einer synchronen Aufgabe. Aber wenn ich zu einer anderen Aktivität wechsle und fortfahre, ruft sie die Funktion SurfaceCreated oder Change zurück. Übrigens danke! @ emrys57. Zumindest habe ich mein Problem mit der Oberflächenansicht gelöst. :)
Muhammad Adil
Ich bin froh, geholfen zu haben. Dies ist eine alte Antwort von Lebkuchen, ich wäre überhaupt nicht überrascht, wenn sich die Details jetzt geändert hätten, ich habe in letzter Zeit nicht nachgesehen. Obwohl mein alter Code jetzt noch funktioniert!
Emrys57
1
Warum wird die Kamera in surfaceCreated geöffnet und die Vorschau in surfaceChanged gestartet?
Yannick Müller
22

Eine weitere einfache Lösung, die gut funktioniert - um die Sichtbarkeit der Vorschauoberfläche zu ändern.

private SurfaceView preview;

Vorschau ist in der onCreateMethode init . In onResumeder View.VISIBLEfür die Vorschau eingestellten Methode :

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

und jeweils in onPausefestgelegter Sichtbarkeit View.GONE:

@Override
public void onPause() {
    super.onPause();
    preview.setVisibility(View.GONE);
    stopPreviewAndFreeCamera(); //stop and release camera
}
gültige Katze
quelle
Du bist ein Lebensretter!
Gtsouk
1
Danke Alter! Wenn du jemals eine Niere willst, ist meine für dich da !! Diese Lösung löste meine erschütternden Stunden des Debuggens.
Binil Jacob
2

Dank der beiden vorherigen Antworten gelang es mir, die Vorschau meiner Kamera einfach wiederzugeben, während ich entweder vom Hintergrund oder vom Sperrbildschirm zurückging.

Wie bei @ e7fendy erwähnt, wird der Rückruf von SurfaceView während des Screenlocks nicht aufgerufen, da die Oberflächenansicht für das System weiterhin sichtbar ist.

Wie @validcat empfohlen hat, wird durch Aufrufen von preview.setVisibility(View.VISIBLE);bzw. preview.setVisibility(View.GONE);in onPause () und onResume () die Oberflächenansicht gezwungen, sich selbst weiterzuleiten, und es werden Rückrufe aufgerufen.

Bis dahin sorgt die Lösung von @ emrys57 und diese beiden oben genannten Aufrufe für Sichtbarkeitsmethoden dafür, dass Ihre Kameravorschau problemlos funktioniert :)

Also kann ich jedem von euch nur +1 geben, wie ihr es alle verdient habt;)

ptitvinou
quelle
1

SurfaceHolder.Callback hängt mit seiner Oberfläche zusammen.

Ist die Aktivität auf dem Bildschirm? In diesem Fall wird SurfaceHolder.Callback nicht angezeigt, da die Oberfläche noch auf dem Bildschirm angezeigt wird.

Um SurfaceView zu steuern, können Sie es nur in onPause / onResume verarbeiten. Für SurfaceHolder.Callback können Sie es verwenden, wenn die Oberfläche geändert (erstellt, in der Größe geändert und zerstört) wird, z. B. openGL initialisieren, wenn Oberfläche erstellt wird, und openGL zerstören, wenn Oberfläche zerstört wird usw.

e7fendy
quelle
-2

Hier ist eine alternative Lösung für alle Rückrufmethoden, die möglicherweise alle dem gleichen undefinierten Ereignisreihenfolgeverhalten mit Aktivitätszyklus unterliegen. Wenn Sie nicht den gesamten Android-Code für jeden Rückruf überprüfen, den Sie verwenden, um den Ursprungsauslöser zu bestimmen und wer die Implementierungen steuert, und hoffen, dass sich die Codebasis in Zukunft nicht ändert, kann man wirklich sagen, dass die Ereignisreihenfolge zwischen Rückrufen und Aktivitätslebenszyklusereignisse könnten garantiert werden.

Derzeit können diese Ordnungswechselwirkungen für Entwicklungszwecke normalerweise als undefiniertes Verhalten bezeichnet werden.

Am besten behandeln Sie dieses undefinierte Verhalten immer korrekt, sodass es überhaupt kein Problem darstellt, indem Sie sicherstellen, dass die Befehle ein definiertes Verhalten sind.

Mein Sony Xperia zum Beispiel schaltet meine aktuelle App im Ruhezustand durch, indem es die App zerstört und dann neu startet und in den Pausenzustand versetzt, ob Sie es glauben oder nicht.

Wie viel Testverhalten von Ereignisbestellungen Google in seinem SDK als spezielles Test-Build für Host-Umgebungsimplemente bereitstellt, weiß ich nicht, aber sie müssen sich definitiv bemühen, um sicherzustellen, dass das Verhalten von Ereignisbestellungen durch eine strenge Einhaltung der Regeln begrenzt wird Angelegenheit.

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;

/ ** * Erstellt von woliver am 24.06.2016. * * Die Android-Hostumgebung bestimmt einen Aktivitätslebenszyklus für OnCreate, onStart, onResume, onPause, onStop, onDestory *, in dem Speicher und Handles für andere Anwendungen freigegeben werden müssen. * Bei der Wiederaufnahme müssen wir diese Elemente manchmal neu binden und mit anderen Objekten aktivieren. * In der Regel stellen diese anderen Objekte Rückrufmethoden aus der Host-Umgebung bereit, die * ein onCreated und ein onDestroy bereitstellen, in denen wir nur von OnCreated aus an dieses Objekt binden und die Bindung onDestory verlieren können. * Diese Art von Rückrufmethoden, deren Ausführungszeit von unserer Host-Umgebung gesteuert wird * und es gibt keine Garantie dafür, dass das Verhalten / die Reihenfolge der Ausführung des Aktivitätslebenszyklus und dieser Rückrufmethoden * konsistent bleibt. * Zum Zwecke der Entwicklung können die Interaktionen und die Reihenfolge der Ausführung technisch als undefiniert bezeichnet werden *, da dies dem Implementierer der Host-Implementierung, Samsung, Sony, HTC, überlassen bleibt. * * Siehe folgendes Entwicklerdokument:https://developer.android.com/reference/android/app/Activity.html * Zitat: * Wenn eine Aktivität durch eine andere Aktivität vollständig verdeckt wird, wird sie gestoppt. Es behält weiterhin alle Status- * und Mitgliedsinformationen bei, ist jedoch für den Benutzer nicht mehr sichtbar, sodass sein Fenster * ausgeblendet ist und häufig vom System beendet wird, wenn an anderer Stelle Speicher benötigt wird. * EndQuato: * * Wenn die Aktivität nicht ausgeblendet ist, wurden keine Rückrufe aufgerufen, von denen man erwartet hätte, dass sie vom Hostsystem * aufgerufen wurden, wie z. B. der SurfaceView-Rückruf der OnCreate- und OnDestory-Methodenschnittstelle. * Dies bedeutet, dass Sie das an SurfaceView gebundene Objekt wie eine Kamera * in einer Pause stoppen müssen und das Objekt niemals erneut binden müssen, da der OnCreate-Rückruf niemals aufgerufen wird. * * /

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

Beispiel für die Implementierung und Verwendung einer Klasse, die sich mit oder dem undefinierten Verhalten von Implementierern der Android-Host-Umgebung befasst.

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);
    }
}
Wesley Oliver
quelle