Mediarecorder onStop 上的 ANR:视频录制相机 API

ANR on Mediarecorder onStop : Video Recording Camera API

我一直在尝试使用 Service class 和 Camera API 创建背景视频重新编码器。当我 运行 应用程序时,一切似乎都运行良好。但是,当我停止该服务时,我的应用程序会抛出 ANR。使用 DDMS tool,我在按下停止录制按钮后发现了有关我的 main 线程的以下信息:

  at android.media.MediaRecorder.stop(Native Method)    
  at com.svtech.thirdeye.thirdeye.Services.VideoRecordingOldApiService.stopRecording(VideoRecordingOldApiService.java:212)  
  at com.svtech.thirdeye.thirdeye.Services.VideoRecordingOldApiService.onDestroy(VideoRecordingOldApiService.java:250)  
  at android.app.ActivityThread.handleStopService(ActivityThread.java:2877) 
  at android.app.ActivityThread.access00(ActivityThread.java:162)    
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1466)   
  at android.os.Handler.dispatchMessage(Handler.java:107)   
  at android.os.Looper.loop(Looper.java:194)    
  at android.app.ActivityThread.main(ActivityThread.java:5371)  
  at java.lang.reflect.Method.invokeNative(Native Method)   
  at java.lang.reflect.Method.invoke(Method.java:525)   
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)    
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)   
  at dalvik.system.NativeStart.main(Native Method)  

我的Serviceclass的onDestroy()方法代码如下:

@Override
    public void onDestroy() {

        super.onDestroy();

        if (linearLayout != null) {

            windowManager.removeView(linearLayout);
            linearLayout = null;
        }
        mMediaRecorder.stop();
        if (mMediaRecorder != null) {
            mMediaRecorder.reset();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
        if (mCamera != null) {
            mCamera.release();
            mCamera.lock();
            mCamera = null;

        }
    }

编辑:已纠正 Service Class

public class VideoRecordingOldApiService extends Service implements SurfaceHolder.Callback {


    private Camera mCamera;
    private String outputFile;
    private MediaRecorder mMediaRecorder;
    private CameraCheck cameraCheck;
    private final static String TAG = "VideoRecorderService";
    private LinearLayout linearLayout;
    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;
    private WindowManager windowManager;
    private Thread thread;
    private Handler handler;


    public VideoRecordingOldApiService() {

    }


    @Override
    public void onCreate() {


        linearLayout = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.surfaceview_layout, null);
        linearLayout.setLayoutParams(new LinearLayout.LayoutParams(
                getResources().getDimensionPixelSize(R.dimen.textureView_width), getResources().getDimensionPixelSize(R.dimen.textureView_height)));
        windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(

                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                PixelFormat.OPAQUE
        );
        layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        windowManager.addView(linearLayout, layoutParams);

        surfaceView = (SurfaceView) linearLayout.findViewById(R.id.videoSurfaceView);
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        cameraCheck = new CameraCheck();
        handler = new Handler();

        //Setup Notification
        final Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
        notificationIntent.addCategory("android.intent.category.LAUNCHER");

        final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        Notification notification;

        notification = new Notification.Builder(getApplicationContext())
                .setSmallIcon(R.drawable.videorecordicon)
                .setOngoing(true)
                .setPriority(Notification.PRIORITY_DEFAULT)
                .setContentTitle(getResources().getString(R.string.video_recorder_notification))
                .setContentText(getResources().getString(R.string.notification_video_text) + "...")
                .setContentIntent(pendingIntent).build();

        startForeground(1, notification);

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (initCamera()) {

            thread = new Thread(new Runnable() {
                @Override
                public void run() {

                    Looper.prepare();

                    if (initRecorder()) {

                        mMediaRecorder.start();

                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(getApplicationContext(), R.string.video_recorder_started, Toast.LENGTH_LONG).show();
                            }
                        });


                    } else {
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(getApplicationContext(), "Couldn't start MediaRecorder", Toast.LENGTH_LONG).show();
                            }
                        });
                    }
                }
            });
            thread.start();

        } else {

            Toast.makeText(getApplicationContext(), "Camera not found", Toast.LENGTH_SHORT).show();
        }

        return START_REDELIVER_INTENT;
    }


    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }


    public String getFileName() {

        String fileName = null;

        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            fileName = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
                    + "/" + "video_record" + System.currentTimeMillis() + ".mp4";

            Log.i("Camera Recorder", fileName);
        } else {

            Toast.makeText(getApplicationContext(), "No External Storage Found", Toast.LENGTH_LONG).show();
        }

        return fileName;
    }


    private boolean initCamera() {

        try {

            mCamera = cameraCheck.getDesiredCamera(this, Camera.CameraInfo.CAMERA_FACING_BACK);
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setRecordingHint(true);

            Camera.Size previewSize = getOptimalPreviewSize(parameters.getSupportedPreviewSizes(),
                    surfaceView.getWidth(), surfaceView.getHeight());
            parameters.setPreviewSize(previewSize.width, previewSize.height);
            mCamera.setDisplayOrientation(90);


        } catch (Exception e) {

            Log.v(TAG, "Could not initialise the camera");
            e.printStackTrace();
            return false;

        }
        return true;
    }

    private boolean initRecorder() {

        mMediaRecorder = new MediaRecorder();
        outputFile = getFileName();


        CamcorderProfile profile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_BACK, CamcorderProfile.QUALITY_HIGH);
        profile.videoFrameWidth = mCamera.getParameters().getSupportedVideoSizes().get(1).width;
        profile.videoFrameHeight = mCamera.getParameters().getSupportedVideoSizes().get(1).height;
        profile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
        profile.audioCodec = MediaRecorder.AudioEncoder.AAC;
        profile.videoCodec = MediaRecorder.VideoEncoder.H264;


        try {
            mCamera.unlock();
            mMediaRecorder.setCamera(mCamera);
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mMediaRecorder.setProfile(profile);
            mMediaRecorder.setOutputFile(outputFile);
            //mMediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());


            mMediaRecorder.prepare();
            Log.v(TAG, "MediaRecorder initialized successfully");
        } catch (Exception e) {
            Log.v(TAG, "MediaRecorder failed to initialize");
            e.printStackTrace();
            return false;
        }

        return true;
    }


    private void releaseCamera() {

        if (mCamera != null) {
            mCamera.lock();
            mCamera.release();
            mCamera = null;

        }

    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (!thread.isInterrupted()) {
            thread.interrupt();
            Toast.makeText(getApplicationContext(), R.string.recording_done, Toast.LENGTH_LONG).show();
        }
        if (linearLayout != null) {

            windowManager.removeView(linearLayout);
            linearLayout = null;
        }

        stopForeground(true);
        releaseCamera();
        thread = null;
    }

    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) h / w;

        if (sizes == null)
            return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.height / size.width;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                continue;

            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }

        return optimalSize;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {


    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
}

谁能帮我弄清楚为什么我在 Mediarecoder 的 onStop() 方法上收到 ANR? DDMS 工具对 main 线程有何指示???

Service 的生命周期回调始终在应用的主线程上调用。仅从 用户感知 的角度来看,Service 在 "background" 中运行。如果你想要真正的后台操作,你需要使用你自己的线程。对 MediaRecorder.stop() 的调用是一个阻塞调用,因为它正在与媒体服务器进程对话,告诉它停止操作。这花费的时间太长(其他主线程操作被阻塞等待)并且系统为您的应用声明 ANR 并将其终止。