How SurfaceHolder callbacks are related to Activity lifecycle?
Asked Answered
D

5

73

I've been trying to implement an application that requires camera preview on a surface. As I see the things, both activity and surface lifecycles consist of the following states:

  1. When I first launch my Activity: onResume()->onSurfaceCreated()->onSurfaceChanged()
  2. When I leave my Activity: onPause()->onSurfaceDestroyed()

In this scheme, I can do corresponding calls like open/release camera and start/stop preview in onPause/onResume and onSurfaceCreated()/onSurfaceDestroyed().

It works fine, unless I lock the screen. When I launch the app, then lock the screen and unlock it later I see:

onPause() - and nothing else after the screen is locked - then onResume() after unlock - and no surface callbacks after then. Actually, onResume() is called after the power button is pressed and the screen is on, but the lock screen is still active, so, it's before the activity becomes even visible.

With this scheme, I get a black screen after unlock, and no surface callbacks are called.

Here's a code fragment that doesn't involve actual work with the camera, but the SurfaceHolder callbacks. The issue above is reproduced even with this code on my phone (callbacks are called in a normal sequence when you press "Back" button, but are missing when you lock the screen):

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

Any ideas on why the surface remains undestroyed after the Activity is paused? Also, how do you handle camera lifecycle in such cases?

Delainedelainey answered 15/7, 2012 at 21:40 Comment(1)
In which android Plaform/API level are you developing?Kanter
T
59

Edit: if the targetSDK is greater than 10, putting the app to sleep calls onPause and onStop. Source

I looked at the lifecycle of both the Activity and the SurfaceView in a tiny camera app on my gingerbread phone. You are entirely correct; the surface is not destroyed when the power button is pressed to put the phone to sleep. When the phone goes to sleep, the Activity does onPause. (And does not do onStop.) It does onResume when the phone wakes up, and, as you point out, it does this while the lock screen is still visible and accepting input, which is a bit odd. When I make the Activity invisible by pressing the Home button, the Activity does both onPause and onStop. Something causes a callback to surfaceDestroyed in this case between the end of onPause and the start of onStop. It's not very obvious, but it does seem very consistent.

When the power button is pressed to sleep the phone, unless something is explicitly done to stop it, the camera keeps running! If I have the camera do a per-image callback for each preview frame, with a Log.d() in there, the log statements keep coming while the phone is pretending to sleep. I think that is Very Sneaky.

As another confusion, the callbacks to surfaceCreated and surfaceChanged happen after onResume in the activity, if the surface is being created.

As a rule, I manage the camera in the class that implements the SurfaceHolder callbacks.

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

and then in the Activity:

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

and that seems to work OK for me. Rotation events cause the Activity to be destroyed and recreated, which causes the SurfaceView to be destroyed and recreated too.

Toggle answered 2/12, 2012 at 16:47 Comment(8)
Why before the super call?Hemorrhoidectomy
I'm not aware that it matters whether the code is executed before or after the super call. It's just important that super.onResume is called somewhere in the onResume routine. I think.Toggle
Actually it depends on the initialization of the surfaceview and surfaceviewholder. If you do some synchronous task then you initialize the view then it never calls after onResume. even not after synchronous task.but when i move to another activity and resume then it callback onSurfaceCreated or Change function. BTW thank you! @emrys57. atleast I have sorted out my problem is with surfaceview. :)Pruritus
Glad to have been of help. This is an old answer from Gingerbread, I would not be at all surprised if the details had changed now, I have not looked lately. Although my old code is still working now!Toggle
@Toggle Can you please help me with this? #33909757Mannequin
This solution is not good because camera.release() is not called on onPause. And Google docs says: "Applications should release the camera immediately during onPause() and re-open() it during onResume()).". Answer of validcat is much better (though a bit hacky).Function
Why is the camera opened in surfaceCreated and the preview is started in surfaceChanged?Kellby
Sadly, this gets even worse in Android P (Android 9 Pie): surfaceDestroyed() is no longer called at all in the standard sequence, at least for app using NativeActivity and probably others using SurfaceView/SurfaceHolder too! Please help by starring this bug: issuetracker.google.com/issues/112412004Erda
L
23

Another simple solution that works fine - to change visibility of the preview surface.

private SurfaceView preview;

preview is init in onCreate method. In onResume method set View.VISIBLE for preview surface:

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

and respectively in onPause set visibility View.GONE:

@Override
public void onPause() {
    super.onPause();
    preview.setVisibility(View.GONE);
    stopPreviewAndFreeCamera(); //stop and release camera
}
Lat answered 21/5, 2014 at 14:44 Comment(2)
You are a lifesaver!Sandrasandro
Thanks Dude! if ever you want a kidney, mine is there for you!! This solution solved my harrowing hours of debugging.Hypnology
P
3

Thanks to both all previous answers I managed to make my camera preview work plainly while going back from either background or lockscreen.

As @e7fendy mentionned, the SurfaceView's callback won't be called while on screenlock as the surface view is still visible for the system.

Hence, as @validcat advised, calling preview.setVisibility(View.VISIBLE); and preview.setVisibility(View.GONE); in respectively onPause() and onResume() will force the surface view to relayout itself and will call it callbacks.

By then, the solution from @emrys57 plus these two visibility method calls above will make your camera preview work plainly :)

So I can only give +1 to each of you as you all deserved it ;)

Primarily answered 10/3, 2015 at 14:29 Comment(0)
H
1

SurfaceHolder.Callback is related to its Surface.

Is the activity on the screen? If so, there won't be SurfaceHolder.Callback, because the Surface is still on the screen.

To control any SurfaceView, you can handle it in onPause/onResume only. For SurfaceHolder.Callback, you can use it if the Surface is changed (created, sizechanged, and destroyed), like initialize openGL when surfaceCreated, and destroy openGL when surfaceDestroyed, etc.

Hermanhermann answered 28/11, 2012 at 2:44 Comment(0)
I
-2

Here is an alternative solution for all callback methods, that may all be subject to the same undefined event order behavior with activity cycle. Unless you going to inspect all the android code for each call back you use to determine the origin trigger and who controlling the implementations and hope that the code base doesn't changed in the future, can one really state, that the event order between callsbacks and activity life cycle events could be guaranteed.

Right now these interactions of order can typically be referred to as undefined behavior, for development purposes.

So best would be to always correctly handle this undefined behavior, such that it will never be a problem in the first place, by ensure the orders are defined behavior.

My Sony Xperia for instance, on sleep, cycles my current app, by destroying the app and then restarting it and puts it into the pause state, believe it or not.

How much event ordering behavior testing google provides in their SDK as special test build for host environment implements I don't know, but they definitely need to make an effort to ensure, behaviors of event orders are all locked down by being rather strict on the matter.

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;

/** * Created by woliver on 2016/06/24. * * Android host environment, dictates an Activity Life Cycle for OnCreate, onStart, onResume, onPause, onStop, onDestory, * where by we are require to release memory and handles for other applications to use. * When resume we are required at times to rebind and activate these items with other objects. * Typically these other objects provide callback methods from the host enviroment which provide * an onCreated and onDestroy, in which we can only bind to this object from OnCreated and and loose * out bind onDestory. * These types of call back methods, shedual time to run is controller by our host enviroment * and their are no guarantees to that the behaviour/order of execution of the Activity Life Cycle and these call back methods * remains consistent. * For the purpose of development the interactions and order of execution can technically be called undefined * as it is up to the host implementation implementer, samsung, sony, htc. * * See following developer document: https://developer.android.com/reference/android/app/Activity.html * Quote: * If an activity is completely obscured by another activity, it is stopped. It still retains all state * and member information, however, it is no longer visible to the user so its window is * hidden and it will often be killed by the system when memory is needed elsewhere. * EndQuato: * * If the activity is not hidden, then any callbacks that one would have expected to have been call by the host * system, will not have been called, such as OnCreate and OnDestory methods interface SurfaceView callback. * This means that you will have to stop the object that has been binded to SurfaceView such as a camera * in pause and will never rebind the object as the OnCreate callback will never be called. * */

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

Example of Implementation and use of class, which deals with or the undefined behaviour of android host enviroment implementers.

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);
    }
}
Item answered 25/6, 2016 at 7:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.