Android: java.lang.RuntimeException: takePicture f

2019-05-27 11:39发布

问题:

I'm am trying to capture an image in the ontouchevent of surfaceview. However, everytime a touch the screen, the app crashes with the following exception:

01-05 21:03:18.500: ERROR/AndroidRuntime(10367): FATAL EXCEPTION: main
        java.lang.RuntimeException: takePicture failed
        at android.hardware.Camera.native_takePicture(Native Method)
        at android.hardware.Camera.takePicture(Camera.java:1126)
        at android.hardware.Camera.takePicture(Camera.java:1071)
        at com.test.MotionDetector.CameraSurfaceView.onTouchEvent(CameraSurfaceView.java:107)
        at android.view.View.dispatchTouchEvent(View.java:7350)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2470)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2212)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2470)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2212)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2470)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2212)
        at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2151)
        at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1480)
        at android.app.Activity.dispatchTouchEvent(Activity.java:2469)
        at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2099)
        at android.view.View.dispatchPointerEvent(View.java:7535)
        at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3492)
        at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3424)
        at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4534)
        at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4512)
        at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4616)
        at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:171)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:125)
        at android.os.Looper.loop(Looper.java:124)
        at android.app.ActivityThread.main(ActivityThread.java:4921)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1038)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805)
        at dalvik.system.NativeStart.main(Native Method)

My code:

package com.nadim.MotionDetector;

import android.content.Context;
import android.hardware.Camera;
import android.os.Environment;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

    private SurfaceHolder mHolder;
    private Camera mCamera;
    private Context context;

    public CameraSurfaceView(Context context, Camera camera) {
        super(context);
        this.context = context;
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }


    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d("MotionDetector", "Error setting camera preview: " + e.getMessage());
        }


    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.getSurface() == null) {
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e) {
            Log.d("MotionDetector", "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        Toast.makeText(context, "Picture taken", Toast.LENGTH_SHORT).show();
        //System.gc(); tried this because it was suggested in a stackoverflow question but it didn't help.
        mCamera.takePicture(null, mPicture, mPicture);
        return true; //processed
    }


    private Camera.PictureCallback mPicture = new Camera.PictureCallback() {

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {

            File pictureFile = getOutputMediaFile();
            if (pictureFile == null) {
                return;
            }

            try {
                FileOutputStream fos = new FileOutputStream(pictureFile);
                fos.write(data);
                fos.close();
            } catch (FileNotFoundException e) {
                Log.d("MotionDetector", "File not found: " + e.getMessage());
            } catch (IOException e) {
                Log.d("MotionDetector", "Error accessing file: " + e.getMessage());
            }
        }
    };

    private static File getOutputMediaFile() {
        File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), "motiondetect");
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile;
        mediaFile = new File(mediaStorageDir, timeStamp + ".jpg");

        return mediaFile;
    }
}

Permissions and features used:

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.camera.flash" />

I don't know why the crash occurs. Many other topic suggest calling Camera.startPreview first. I am doing that, but it doesn't solve the issue.

LogCat before taking picture:

01-05 21:45:57.667: WARN/ActivityManager(754): No content provider found for permission revoke: file:///data/local/tmp/com.test.MotionDetector
01-05 21:45:57.667: WARN/ActivityManager(754): No content provider found for permission revoke: file:///data/local/tmp/com.test.MotionDetector
01-05 21:45:57.687: INFO/ActivityManager(754): Force stopping com.test.MotionDetector appid=10103 user=-1: uninstall pkg
01-05 21:45:57.687: INFO/ActivityManager(754): Killing 4264:com.test.MotionDetector/u0a103 (adj 7): stop com.test.MotionDetector
01-05 21:45:57.687: INFO/ActivityManager(754): Force finishing activity ActivityRecord{437a6678 u0 com.test.MotionDetector/.MotionDetectActivity t144}
01-05 21:45:57.697: WARN/InputDispatcher(754): channel '42adff40 com.test.MotionDetector/com.test.MotionDetector.MotionDetectActivity (server)' ~ Consumer closed input channel or an error occurred.  events=0x9
01-05 21:45:57.697: ERROR/InputDispatcher(754): channel '42adff40 com.test.MotionDetector/com.test.MotionDetector.MotionDetectActivity (server)' ~ Channel is unrecoverably broken and will be disposed!
01-05 21:45:57.697: WARN/InputDispatcher(754): Attempted to unregister already unregistered input channel '42adff40 com.test.MotionDetector/com.test.MotionDetector.MotionDetectActivity (server)'
01-05 21:45:57.697: INFO/WindowState(754): WIN DEATH: Window{42adff40 u0 com.test.MotionDetector/com.test.MotionDetector.MotionDetectActivity}
01-05 21:45:57.757: INFO/PackageManager(754): Running dexopt on: com.test.MotionDetector
01-05 21:45:57.757: INFO/PackageManager(754): Package com.test.MotionDetector codePath changed from /data/app/com.test.MotionDetector-1.apk to /data/app/com.test.MotionDetector-2.apk; Retaining data and using new
01-05 21:45:57.807: INFO/ActivityManager(754): Force stopping com.test.MotionDetector appid=10103 user=-1: update pkg
01-05 21:45:57.807: WARN/PackageManager(754): Code path for pkg : com.test.MotionDetector changing from /data/app/com.test.MotionDetector-1.apk to /data/app/com.test.MotionDetector-2.apk
01-05 21:45:57.807: WARN/PackageManager(754): Resource path for pkg : com.test.MotionDetector changing from /data/app/com.test.MotionDetector-1.apk to /data/app/com.test.MotionDetector-2.apk
01-05 21:45:57.937: INFO/ActivityManager(754): Force stopping com.test.MotionDetector appid=10103 user=0: pkg removed
01-05 21:45:57.987: DEBUG/BackupManagerService(754): Received broadcast Intent { act=android.intent.action.PACKAGE_REMOVED dat=package:com.test.MotionDetector flg=0x4000010 (has extras) }
01-05 21:45:58.047: DEBUG/BackupManagerService(754): Received broadcast Intent { act=android.intent.action.PACKAGE_ADDED dat=package:com.test.MotionDetector flg=0x4000010 (has extras) }
01-05 21:45:58.567: INFO/Icing.InternalIcingCorporaProvider(12750): Updating corpora: A: com.test.MotionDetector, C: MAYBE
01-05 21:45:58.647: DEBUG/PackageAddedReceiver(1086): package added com.test.MotionDetector
01-05 21:45:58.737: INFO/ActivityManager(754): START u0 {flg=0x10000000 cmp=com.test.MotionDetector/.MotionDetectActivity} from pid 4578
01-05 21:45:58.777: INFO/ActivityManager(754): Start proc com.test.MotionDetector for activity com.test.MotionDetector/.MotionDetectActivity: pid=4630 uid=10103 gids={50103, 1028, 1015}
01-05 21:45:59.627: INFO/ActivityManager(754): Displayed com.test.MotionDetector/.MotionDetectActivity: +861ms (total +972ms)
01-05 21:45:59.657: INFO/WindowManager(754): Screen frozen for +869ms due to Window{43cb77a8 u0 com.test.MotionDetector/com.test.MotionDetector.MotionDetectActivity}

回答1:

Assuming that you want the jpeg saved, you don't need raw callback, so change the call to takePicture() from:

mCamera.takePicture(null, mPicture, mPicture);

to

mCamera.takePicture(null, null, mPicture);

in your onTouchEvent(). This will use jpeg picture callback.

Calling takePicture() also causes the preview to stop, so you might want to call startPreview() again in your onPictureTaken() callback, if you want to take more pictures or have a preview restarted.

Also, in your onTouchEvent(), you might be getting multiple events, so filter for the one that works for you:

@Override
public boolean onTouchEvent(MotionEvent event) {
    Toast.makeText(context, "Picture taken", Toast.LENGTH_SHORT).show();
    //System.gc(); tried this because it was suggested in a stackoverflow question but it didn't help.

    switch(event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // A pressed gesture has started, the motion contains the initial starting location
            break;

        case MotionEvent.ACTION_UP:
            // A pressed gesture has finished, the motion contains the final release location 
            // as well as any intermediate points since the last down or move event.

            mCamera.takePicture(null, mPicture, mPicture);

            break;

        default:
            break;
    }

    return true; //processed
}

If you place takePicture() in ACTION_DOWN, it will be called as soon as you touch the screen, whereas when in ACTION_UP, it will happen when you remove finger. See which one works for you.