What could cause an Android activity to relaunch i

2019-03-18 15:01发布

问题:

I have a weird bug in my application that causes an activity to relaunch itself in an infinite loop when I'm returning from a camera application, after taking a picture.

The UI flow is like this:

  1. Main Activity ->
  2. Accept Photo activity -> in onCreate() open camera with startActivityForResult()
  3. Camera screen -> take picture (or cancel) -> return to Accept Photo
  4. The Accept Photo screen is created completely and immediately stopped and recreated in an infinite loop

The weird part is that it only happens for some cameras. On my Nexus S running Jellybean, the stock camera behaves correctly, while Camera Zoom FX causes this bug. On my Archos G9 tablet running ICS, both the stock camera and Zoom FX cause the bug.

I've checked the code step by step and I can't find the source of the relaunch call. When I'm stopping the debugger in the second (and subsequent) onCreate() call, in the call stack there is an ActivityThread.handleRelaunchActivity() call. It's Intent doesn't have much info: the action is null, the class is AcceptPhoto. The mFlags has the 603979776 value, which I don't know how to translate into the actual intent flags.

The weirdness doesn't stop here, though. On my tablet, the first time I take the picture, the application is fine. If I try to take a second picture, the screen goes crazy. If, instead of taking the seconds picture, I return to a previous screen, it's all fine until I open a new activity. It doesn't matter from where, if I return all the way to the root activity and start a new activity, it starts flickering.

I'll try to post some code, but I suspect the bug is not caused by my code, but I'm triggering something in the underlying Android code. What I hope for is maybe someone can point me in the correct direction to find a way to work around this bug. Anything can be helpful, so I thank you for any idea!

The code used to open the camera (called in AcceptPhoto.onCreate(), using an utility class):

private void openCamera(Context context) {
    Intent pictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    File tempFile = getTempFile(context);
    try {
        if (tempFile != null) {

            pictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));

            ((Activity) context).startActivityForResult(pictureIntent, GET_ITEM_PHOTO);
        } else {
            Toast.makeText(context, "Could not create temp file", Toast.LENGTH_SHORT).show();
        }
    } catch (Exception e) {
        Toast.makeText(context, "Error opening camera " + e.getMessage(), Toast.LENGTH_LONG).show();
        e.printStackTrace();
    }
}

The code used to display the picture, called in AcceptPhoto.onActivityResult():

private void displayPhoto() {
    if (cameraUtils == null) {
        cameraUtils = new CameraUtil();
    }
    previewImageView.setImageDrawable(null);
    File tempFile = cameraUtils.getTempFile(this);

    int rotation = 0;
    try {
        ExifInterface exif = new ExifInterface(tempFile.getPath());
        String orientation = exif.getAttribute(ExifInterface.TAG_ORIENTATION);
        Log.i("SPRING", "Photo orientation " + orientation);
        rotation = getBitmapRotation(Integer.valueOf(orientation));
        Log.i("SPRING", "The image needs to be rotated by " + (rotation) + " degrees");
    } catch (IOException e1) {
        e1.printStackTrace();
    }
    try {
        previewBitmap = BitmapEncoderUtil.loadPrescaledBitmap(tempFile);
        if (rotation != 0) {

            Matrix rotationMatrix = new Matrix();
            rotationMatrix.postRotate(rotation);

            int w = previewBitmap.getWidth();
            int h = previewBitmap.getHeight();

            Bitmap rotatedBitmap = Bitmap.createBitmap(previewBitmap, 0, 0, w, h, rotationMatrix, false);

            previewBitmap = rotatedBitmap;
        }
        previewImageView.setImageBitmap(previewBitmap);
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Method used in the utility class to create/retrieve the file where the camera saves the photo:

public File getTempFile(Context context) {

    String externalStorageStateString = Environment.getExternalStorageState();
    File cacheDirectory;
    // try to save in external storage
    if (externalStorageStateString.equals(Environment.MEDIA_MOUNTED)) {
        cacheDirectory = context.getExternalCacheDir();
    } else {
        // save in internal storage
        cacheDirectory = context.getCacheDir();
    }
    File tempSnapshotFile = new File(cacheDirectory, MissionOtherActivity.ITEM_SNAPSHOT_PATH);

    // make sure the file exists, possible fix for the camera bug
    try {
        if (tempSnapshotFile.exists() == false) {
            tempSnapshotFile.getParentFile().mkdirs();
            tempSnapshotFile.createNewFile();
        }

    } catch (IOException e) {
        Log.e("SPRING", "Could not create file.", e);
    }
    return tempSnapshotFile;
}

回答1:

After much investigation, it appears the call to relaunch is coming from onConfigurationChanged. I still haven't figured out why, but at least I know what to avoid.

It explains why some cameras triggered this bug and others not: some cameras used the same configuration as my application, others not.

EDIT: I've discovered after other investigations that I had a bug in my extended Application class. In the onConfigurationChanged method, I was changing the configuration by forcing a certain locale. This was triggering a new onConfigurationChanged() call, which caused an infinite loop and the subsequent screen create/destroy sequence. I have no idea why I've put that code in the onConfigurationChanged() method, but I guess you have to suffer in order to learn :)