July 5, 2015

Android Camera Preview Dilemma

Today I present you with the deceptively simple task of implementing a camera preview class for Android.

Android Camera Preview Dilemma

Most of what you need to get started with making a simple app that can display to a user what the Camera sees can be found here.

Most of the code you will see in my eloquently named MainActivity class is sourced from this standard Android tutorial. If you follow the tutorial closely you should have a simple activity capable of displaying on the screen what the camera sees. Such an activity would look something similar to what you see below.

public class MainActivity extends Activity {

    private Camera mCamera;
    private CameraView mView;
    FrameLayout mainView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /**Begin Camera Access Code */
        if(mCamera == null){mCamera = getCameraInstance();}
        mCamera.setDisplayOrientation(90);

        /**Adds the camera preview as the view for the frame layout of MainActivity */
        mainView = (FrameLayout) findViewById(R.id.camera_view);
        mView = new CameraView(this, mCamera);
        mainView.addView(mView);
        mCamera.setPreviewCallback(null);
    }
    
    /** A safe way to get an instance of the Camera object. */
    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open(); // attempt to get a Camera instance
        }
        catch (Exception e){
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }
}

This is a fairly basic activity that utilizes the CameraPreview class code available in the tutorial I linked above. As it stands, the activity accomplishes the objective of showing the user what the camera can see, but with a crucial action missing. Google gives us this warning as seen below when you read about accessing the Android camera.

Caution: If your application does not properly release the camera, all subsequent attempts to access the camera, including those by your own application, will fail and may cause your or other applications to be shut down.

This is a fairly large problem. The application as it stands has no code to release the camera when the user navigates away from our activity, causing any other application that would like to use the camera to fail due to the unavailability of the camera as a result of our application, which never released it. So now we embark on the crucial task making our application properly release and reaquire the camera as needed. The most reasonable way to do this is to utilize the built in Android onPause() and onResume() methods. And so I did this as shown in the next code block shown below.

public class MainActivity extends Activity {

    private Camera mCamera;
    private CameraView mView;
    FrameLayout mainView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /**Begin Camera Access Code */
        mCamera = getCameraInstance();
        mCamera.setDisplayOrientation(90);

        /**Adds the camera preview as the view for the frame layout of MainActivity */
        mainView = (FrameLayout) findViewById(R.id.camera_view);
        mView = new CameraView(this, mCamera);
        mainView.addView(mView);
        mCamera.setPreviewCallback(null);
    }

    protected void onPause(){
        super.onPause();
        mCamera.release();
    }

    protected void onResume() {
        super.onResume();
        mCamera = getCameraInstance();
        mCamera.setDisplayOrientation(90);
        mView = new CameraView(this, mCamera);
        mainView.addView(mView);
    }
    /** A safe way to get an instance of the Camera object. */
    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open(); // attempt to get a Camera instance
        }
        catch (Exception e){
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }
}

And what you just saw above was my first draft attempt at solving the seemingly easy problem of releasing the camera and reaquiring it when the user navigates away and back to our activity. There are two issues with the above code.

The first:

The CameraPreview class sourced from the Android tutorial has a callback written into it that is invoked after we release the camera. This poses a problem for us as we no longer have control of the camera and an error is thrown because our activity attempts to interact with the camera after we've released it.

The second:

This error arose from a slight bout of forgetfulness on my part. It slipped my mind that the standard Android activity lifecycle flow is that after the onCreated() method completes, the onResume() method is invoked directly afterward. This is specifically a problem because in the onResume() method we attempt to reaquire access to the camera by invoking the getCameraInstance() method. This is a problem because we never released the camera in our onCreate() method. It was my intent that the onPause() method could be the one to release it, but because of the activity lifecycle it isn't invoked before the first time onResume() is called.

So lets see if we can patch these holes up and create an activity that doesn't throw errors and properly releases and reaquires the camera. Below you'll read the final version of the camera preview activity I wrote that has a couple lines of code added to it from the version you've seen above. I'll discuss the additions and how they fix the two problems I described above.

public class MainActivity extends Activity {
    private Camera mCamera;
    private CameraView mView;
    FrameLayout mainView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /**Begin Camera Access Code */
        /**Sets the FrameLayout for use in onResume()*/
        mainView = (FrameLayout) findViewById(R.id.camera_view);
        mCamera.setPreviewCallback(null);
        mView.getHolder().removeCallback(mView);
    }

    protected void onPause(){
        super.onPause();
        if(mCamera != null) {
            mCamera.setPreviewCallback(null);
            mView.getHolder().removeCallback(mView);
            mCamera.release();
            mCamera = null;
        }
    }

    protected void onResume() {
        super.onResume();
        mCamera = getCameraInstance();
        if(mCamera != null){
            mCamera.setDisplayOrientation(90);
            mView = new CameraView(this, mCamera);
            mainView.addView(mView);
        }
    }
    /** A safe way to get an instance of the Camera object. */
    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open(); // attempt to get a Camera instance
        }
        catch (Exception e){
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }
}

So diving into addressing the first issue of the automatic callback. In the onCreate() and onPause() methods you can see the below two lines of code added to remove the callback to the CameraView class.

mCamera.setPreviewCallback(null);
mView.getHolder().removeCallback(mView);

These two lines make sure that the CameraView class doesn't attempt to take any action utilizing the camera after it has been released. Next we address the issue of the onResume() method attempting to access the camera while it is still in use. Our first course of action is because we know that onResume() is going to try to get the camera we should just move the code that is responsible for setting the camera preview into the onResume() method. The result is a rather stark looking onCreate() method in exchange for the onResume() method you see below.

protected void onResume() {
    super.onResume();
    mCamera = getCameraInstance();
    if(mCamera != null){
        mCamera.setDisplayOrientation(90);
        mView = new CameraView(this, mCamera);
        mainView.addView(mView);
    }
}

And with that migration of code we've sucessfully solved the conflict between onCreate() and onResume(). Now that both issues have been solved our activity sucessfully displays to a user a preview of what the camera sees without totally breaking both our activity on resume or any other activity that wants use of the camera.

Github repo with just code that is not wrapped in an android project available below...

https://github.com/codedge-llc/AndroidCameraPreviewExample