How SurfaceHolder callbacks are related to Activity lifecycle?

AndroidCameraSurfaceview

Android Problem Overview


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?

Android Solutions


Solution 1 - Android

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.

Solution 2 - Android

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
}

Solution 3 - Android

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

Solution 4 - Android

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.

Solution 5 - Android

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkrugloidView Question on Stackoverflow
Solution 1 - Androidemrys57View Answer on Stackoverflow
Solution 2 - AndroidvalidcatView Answer on Stackoverflow
Solution 3 - AndroidStephen VinouzeView Answer on Stackoverflow
Solution 4 - Androide7fendyView Answer on Stackoverflow
Solution 5 - AndroidWesley OliverView Answer on Stackoverflow