2

Ich habe versucht, einen Hintergrundvideo-Rekorder zu erstellen, der Service Klasse verwendet und Camera API verwendet. Wenn ich die App starte, scheint alles in Ordnung zu sein. Wenn ich jedoch den Dienst stoppe, löst meine App ANR aus. Mit DDMS tool, fand ich folgende Informationen über meinen main Faden, nachdem ich Stoppen der Aufnahme-Taste drücken:ANR auf Mediarekorder onStop: Videoaufzeichnungskamera API

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.access$2000(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) 

Der onDestroy() Methodencode meiner Service Klasse wird wie folgt:

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

     } 
    } 

EDITS: Rektifiziertes Service Klasse

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

    } 
} 

Kann mir jemand helfen herauszufinden, warum ich ANR auf onStop() Methode von Mediarecoder bekomme? Und was zeigt DDMS-Tool über main Thread ???

Antwort

3

Lifecycle-Callbacks von Service werden immer im Hauptthread Ihrer App aufgerufen. A Service läuft im "Hintergrund" nur von einer Benutzerwahrnehmung Standpunkt. Wenn Sie echte Hintergrundoperationen wünschen, müssen Sie Ihre eigenen Threads verwenden. Der Anruf an MediaRecorder.stop() ist ein blockierender Anruf, da er mit dem Medienserver-Prozess kommuniziert und ihn auffordert, den Betrieb zu stoppen. Dies dauert zu lange (andere Haupt-Thread-Ops sind blockiert, warten) und das System erklärt ANR für Ihre App und tötet es.

+0

Danke für die Antwort Mann! Ich verwende 'SurfaceView' für Videoaufnahmen. Können Sie mir sagen, wo soll ich 'Mediarecorder.onStop()' ?? Die Aufnahme soll durch Tastendruck gestoppt werden. @Larry Schiefer –

+0

Sie können einen separaten Thread ausführen, der alle Interaktionen des Medienrecorders ausführt. Senden Sie diesem Thread einfach einen Befehl zum Stoppen. –

+0

Ich habe Ihren Vorschlag in meinem Code umgesetzt .. Jetzt wirft meine App nicht ANR. Es erstellt erfolgreich eine Ausgabedatei im Verzeichnis. Wenn ich jedoch die Ausgabedatei abspiele, erhalte ich den Fehler "Kann dieses Video nicht abspielen". Ich habe meinen vollständigen 'Service'-Klassencode gepostet. Kannst du mir bitte sagen, wo die Dinge schief gehen ??? @Larry Schiefer –