找不到StrictMode

时间:2016-04-20 12:15:38

标签: java android android-camera resource-leak android-strictmode

我正在通过Android的测试程序,当我使用StrictMode进行测试时,我收到了关于我的相机资源泄漏的警告,但我似乎找不到位置。这是警告:

A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
java.lang.Throwable: Explicit termination method 'release' not called
at dalvik.system.CloseGuard.open(CloseGuard.java:184)
at android.view.Surface.<init>(Surface.java:301)
at android.view.SurfaceView.<init>(SurfaceView.java:102)
at com.example.sition.diggin.camera.CameraView.<init>(CameraView.java:20)
at com.example.sition.diggin.CameraActivity.onResume(CameraActivity.java:60)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1202)
at android.app.Activity.performResume(Activity.java:5404)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2837)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2903)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2321)
at android.app.ActivityThread.access$700(ActivityThread.java:158)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1296)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:5365)
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:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)

我知道它与相机有关,但我不确定它是CameraView还是CameraActivity,所以这是CameraView:

public class CameraView extends SurfaceView implements SurfaceHolder.Callback {
    private Camera mCamera;

    // Constructor that obtains context and camera
    public CameraView(Context context, Camera camera) {
        super(context);
        this.mCamera = camera;
        SurfaceHolder mSurfaceHolder = this.getHolder();
        mSurfaceHolder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        try {
            SurfaceHolder mSurfaceHolder = this.getHolder();
            mSurfaceHolder.addCallback(this);
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        mCamera.stopPreview();
        getHolder().removeCallback(this);
        mCamera.release();
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int format,int width, int height) {
        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这是CameraActivity:

public class CameraActivity extends AppCompatActivity implements BearingToNorthProvider.ChangeEventListener {

    //region fields
    private Camera mCamera;
    private float mDist = 0f;
    private String flashMode;
    private ImageButton flashButton;
    private BearingToNorthProvider mBearingProvider;
    private boolean pictureTaken = false;
    private byte[] currentData;

    private double bearing;
    private double currentBearing = 0d;
    private String cardinalDirection = "?";
    private double rotation = 0d;

    private static final int REQUEST_CODE_ASK_PERMISSIONS = 2;
    //endregion

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

    @Override protected void onResume() {
        super.onResume();
        mCamera = getCameraInstance();
        CameraView mCameraView = new CameraView(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mCameraView);
        ImageButton captureButton = (ImageButton) findViewById(R.id.btnCapture);
        captureButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Camera.Parameters params = mCamera.getParameters();
                params.setFlashMode(flashMode);
                mCamera.setParameters(params);
                mCamera.takePicture(null, null, mPicture);
            }
        });
        SharedPreferences sharedPref = this.getSharedPreferences(getString(R.string.flashMode), Context.MODE_PRIVATE);
        flashMode = sharedPref.getString(getString(R.string.flashMode), Camera.Parameters.FLASH_MODE_OFF);
        flashButton = (ImageButton) findViewById(R.id.btnFlash);
        setFlashButton();
        mBearingProvider = new BearingToNorthProvider(this,this);
        mBearingProvider.setChangeEventListener(this);
        mBearingProvider.start();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mBearingProvider.stop();
    }


    /**
     * Helper method to access the camera returns null if it cannot get the
     * camera or does not exist
     *
     * @return the instance of the camera
     */
    private Camera getCameraInstance() {
        Camera camera = null;
        try {
            camera = Camera.open();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return camera;
    }

    private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            currentData = data;
            mBearingProvider.updateBearing();
            bearing = mBearingProvider.getBearing();
            cardinalDirection = bearingToString(bearing);
            findViewById(R.id.btnFlash).setVisibility(View.INVISIBLE);
            findViewById(R.id.btnCapture).setVisibility(View.INVISIBLE);
            findViewById(R.id.ivCompass).setVisibility(View.INVISIBLE);
            findViewById(R.id.ivFocusPoint).setVisibility(View.INVISIBLE);
            findViewById(R.id.ivCompassPointer).setVisibility(View.INVISIBLE);
            findViewById(R.id.pictureOverlay).setVisibility(View.VISIBLE);
        }
    };

    //region Picture Intents
    /**
     * Method that puts the necessary data in the intent and then sends it back to the ProjectOverview
     * @param v the view that activated this method
     */
    public void confirmPicture(View v) {
        String path = createFile(currentData);
        Intent intent = new Intent();
        intent.putExtra("path", path);
        String description = String.valueOf(((EditText) findViewById(R.id.tvPictureDesc)).getText());
        intent.putExtra("direction", cardinalDirection);
        intent.putExtra("description", description);

        //close this Activity...
        setResult(Activity.RESULT_OK, intent);
        finish();
    }

    /**
     * Method that puts no data in the intent and then sends it back to the ProjectOverview
     * @param v the view that activated this method
     */
    public void deletePicture(View v) {
        setResult(Activity.RESULT_CANCELED);
        finish();
    }

    /**
     * Method that restarts the camera giving the user a change to retake the picture
     * @param v the view that activated this method
     */
    public void retryPicture(View v) {
        findViewById(R.id.btnFlash).setVisibility(View.VISIBLE);
        findViewById(R.id.btnCapture).setVisibility(View.VISIBLE);
        findViewById(R.id.ivCompass).setVisibility(View.VISIBLE);
        findViewById(R.id.ivFocusPoint).setVisibility(View.VISIBLE);
        findViewById(R.id.ivCompassPointer).setVisibility(View.VISIBLE);
        findViewById(R.id.pictureOverlay).setVisibility(View.INVISIBLE);
        pictureTaken = false;
        mCamera.startPreview();
    }

    //endregion

    //region File Methods

    /**
     * Method that creates a file from the given byte array and saves the file in the Pictures Directory
     * @param data is the array of bytes that represent the picture taken by the camera
     * @return the path of created file
     */
    private String createFile(byte[] data){
        checkFilePermissions();
        File picFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + File.separator + "tempPic.jpg" + File.separator);
        String path = picFile.getPath();
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(picFile));
            bos.write(data);
            bos.flush();
            bos.close();
            return path;
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * Checks the permission for reading to and writing from the external storage
     */
    private void checkFilePermissions() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            int hasWriteExternalStoragePermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if (hasWriteExternalStoragePermission != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        REQUEST_CODE_ASK_PERMISSIONS);
            }
        }
    }

    //endregion

    //region Zoom Methods

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Get the pointer ID
        Camera.Parameters params = mCamera.getParameters();
        int action = event.getAction();


        if (event.getPointerCount() > 1) {
            // handle multi-touch events
            if (action == MotionEvent.ACTION_POINTER_DOWN) {
                mDist = getFingerSpacing(event);
            } else if (action == MotionEvent.ACTION_MOVE && params.isZoomSupported()) {
                mCamera.cancelAutoFocus();
                handleZoom(event, params);
            }
        } else {
            // handle single touch events
            if (action == MotionEvent.ACTION_UP) {
                handleFocus(event, params);
            }
        }
        return true;
    }

    /**
     * Method that handles the zoom of the camera
     * @param event the event that activated this method
     * @param params the parameters of the camera
     */
    private void handleZoom(MotionEvent event, Camera.Parameters params) {
        int maxZoom = params.getMaxZoom();
        int zoom = params.getZoom();
        float newDist = getFingerSpacing(event);
        if (newDist > mDist) {
            //zoom in
            if (zoom < maxZoom)
                zoom++;
        } else if (newDist < mDist) {
            //zoom out
            if (zoom > 0)
                zoom--;
        }
        mDist = newDist;
        params.setZoom(zoom);
        mCamera.setParameters(params);
    }

    /**
     * Method that handles the focus of the camera when zooming
     * @param event the event that activated this method
     * @param params the parameters of the camera
     */
    private void handleFocus(MotionEvent event, Camera.Parameters params) {
        int pointerId = event.getPointerId(0);
        int pointerIndex = event.findPointerIndex(pointerId);
        // Get the pointer's current position

        List<String> supportedFocusModes = params.getSupportedFocusModes();
        if (supportedFocusModes != null && supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            mCamera.autoFocus(new Camera.AutoFocusCallback() {
                @Override
                public void onAutoFocus(boolean b, Camera camera) {
                    // currently set to auto-focus on single touch
                }
            });
        }
    }

    /**
     * Method that determines the space between the first two fingers
     * @param event the event that activated this method
     * @return the distance between the two fingers
     */
    private float getFingerSpacing(MotionEvent event) {
        double x = event.getX(0) - event.getX(1);
        double y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    //endregion

    //region Flash Methods

    /**
     * Method that changes the flash mode the camera currently uses (on/off/auto)
     * @param v the view that activated this method
     */
    public void changeFlashMode(View v) {
        switch (flashMode) {
            case Camera.Parameters.FLASH_MODE_ON :
                flashMode = Camera.Parameters.FLASH_MODE_AUTO;
                break;
            case Camera.Parameters.FLASH_MODE_AUTO :
                flashMode = Camera.Parameters.FLASH_MODE_OFF;
                break;
            case Camera.Parameters.FLASH_MODE_OFF :
                flashMode = Camera.Parameters.FLASH_MODE_ON;
                break;
        }
        SharedPreferences sharedPref = getSharedPreferences(getString(R.string.flashMode), Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putString(getString(R.string.flashMode), flashMode);
        editor.apply();
        setFlashButton();
    }

    /**
     * Method that changes the image of the button based on flash mode
     */
    private void setFlashButton() {
        switch (flashMode) {
            case Camera.Parameters.FLASH_MODE_ON :
                flashButton.setImageDrawable(ContextCompat.getDrawable(this,R.drawable.camera_flash_on));
                break;
            case Camera.Parameters.FLASH_MODE_AUTO :
                flashButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.camera_flash_auto));
                break;
            case Camera.Parameters.FLASH_MODE_OFF :
                flashButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.camera_flash_off));
                break;
        }
    }

    //endregion

    //region Bearing Methods

    /**
     * Method that gives a cardinal direction based on the current bearing to the true north
     * @param bearing is the bearing to the true north
     * @return cardinal direction that belongs to the bearing
     */
    private String bearingToString(Double bearing) {
        String strHeading = "?";
        if (isBetween(bearing,-180.0,-157.5)) { strHeading = "South"; }
        else if (isBetween(bearing,-157.5,-112.5)) { strHeading = "SouthWest"; }
        else if (isBetween(bearing,-112.5,-67.5)) { strHeading = "West"; }
        else if (isBetween(bearing,-67.5,-22.5)) { strHeading = "NorthWest"; }
        else if (isBetween(bearing,-22.5,22.5)) { strHeading = "North"; }
        else if (isBetween(bearing,22.5,67.5)) { strHeading = "NorthEast"; }
        else if (isBetween(bearing,67.5,112.5)) { strHeading = "East"; }
        else if (isBetween(bearing,112.5,157.5)) { strHeading = "SouthEast"; }
        else if (isBetween(bearing,157.5,180.0)) { strHeading = "South"; }
        return strHeading;
    }

    /**
     * Method that checks if a certain number is in a certain range of numbers
     * @param x is the number to check
     * @param lower is the number that defines the lower boundary of the number range
     * @param upper is the number that defines the upper boundary of the number range
     * @return true if the number is between the other numbers, false otherwise
     */
    private boolean isBetween(double x, double lower, double upper) {
        return lower <= x && x <= upper;
    }

    @Override
    public void onBearingChanged(double bearing) {
        this.bearing = bearing;
        mBearingProvider.setContext(this);

        ImageView image = (ImageView) findViewById(R.id.ivCompass);
        if (image.getVisibility() == View.VISIBLE) {

            // create a rotation animation (reverse turn degree degrees)
            if (bearing < 0) {
                bearing += 360;
            }
            RotateAnimation ra = new RotateAnimation((float) currentBearing, (float) -bearing, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);

            // how long the animation will take place
            ra.setDuration(210);

            // set the animation after the end of the reservation status
            ra.setFillAfter(true);

            // Start the animation
            image.startAnimation(ra);
            rotation += currentBearing + bearing;
            currentBearing = -bearing;
        } else if (!pictureTaken){

            ImageView image2 = (ImageView) findViewById(R.id.ivCompass2);
            image2.setRotation((float) -rotation);
            image.clearAnimation();
            pictureTaken = true;
        }
        mBearingProvider.setContext(this);
    }

    //endregion
}

我也想知道这个警告有多糟糕,我担心这很糟糕,因为警告是红色的,而我到现在所有的其他警告都是灰色的。如果这不是一个重要的警告,请告诉我,以便继续进行测试。

修改

我现在几乎可以肯定问题在于CameraView,我现在测试了VideoActivity(它也使用了CameraView),并且我得到了同样的警告。

编辑2:

这个警告似乎并不是每次都会发生(大部分时间都是这样),但我找不到一种模式或逻辑上的原因,为什么它一次工作而另一次不起作用,但我'我会一直在寻找。

1 个答案:

答案 0 :(得分:0)

以下是同一问题的错误报告 https://code.google.com/p/android/issues/detail?id=54285

这种解决方法可能会解决您的问题 How to fix memory leak in SurfaceView

修改

像这样,

public class CameraView extends ... {
    ...

    public void release(){
        getHolder().getSurface().release();
    }

    ...
}


public class CameraActivity extends ... {
    ...
    CameraView mCameraView;
    ...

    @Override protected void onResume() {
        ...
        mCameraView = new CameraView(this, mCamera);
        ...
    }

   @Override protected void onPause() {
         super.onPause();
         mCameraView.release();
    }

    ...
}