Does it work?

How to retain image in Android when screen rotation occurs

If you are programming an Android app and you’re allowing your users to select an image from gallery you will soon discover that as the screen rotates the image disappears!

Gosh! What’s happening?!

Let’s go a step back.

So far you should know that activities have a specific lifecycle. Each activity lives in different stages of its lifecycle during application usage.

The activity instance goes its journey through the following stages when it is created:

created (transient) –> started (transient) –> resumed.

When the users starts leaving the activity/application both temporarily or not, then the activity starts going down the other way:

paused (partially obscured) –> stopped (hidden) –> destroyed

So each time a users starts interacting with another activity of the same app or with another app, the current foreground activity goes to background and goes to the paused or stopped state.

If the user interaction with the app makes clear to Android that the activity is no more needed then the activity is destroyed.

If a phone call arrives or the application is put in the background, then the activity stays in the stopped state waiting to be resumed.

If the activity is not destroyed it keeps being in memory with all of its state retained and it will be fully resumed if needed.

See here for a full graph and explanation: Activity Lifecycle

What happens when it is destroyed?

One More Hidden Scenario – Screen Rotation

As far es onDestroy() is concerned there are two major scenarios:1) User interaction with the app makes clear to Android that the activity is no more needed
2) User interaction with the app leaves room to the possibility that the activity will be used again1) In the first case (e.g.: the users closes the application) the activity instance goes down all the states and is destroyed to free up memory.2) In the second case (e.g.; the user brings another app in foreground) the activity is stopped but retained in memory, ready to be resumed.If the activity lives in the stopped state long enough, there is the possibility that Android decides to destroy it in order to free up memory for foreground apps and activities.

But this is not the full story…read the following note:

Caution: Your activity will be destroyed and recreated each time the user rotates the screen.

source: Recreating an Activity

So what to do then?

The Wrong Answer

When destroying activities for its own purposes, Android preserves the activity status in a key-value pair blob dictionary (Bundle).
All the activity done by the user interacting with the Views in the layout is preserved in this activity state dictionary.

This means the text entered into an EditText for instance.
If you have other application specific state stored in private members of the Activity you need to add specific code to store it in the Bundle and to retrieve it from there when activity is recreated.

The events exposed to add our code are:

onSaveInstanceState and on RestoreInstanceState

You can add your persistence logic in the first one by adding key value pairs to the bundle.
You can then retrieve your values in the second event or in the onCreate event as well.

You can find detailed explanation and example here: Persist Activity State

This is the usual answer you will find on forums but it’s not applicable for Bitmaps.

The Right Answer

Every time the screen rotates Android destroys (onDestroy()) and recreates (onCreate()) the Activity in order to allow it to reload appropriate layouts and configurations.
If you need to recover large sets of data, like Bitmaps, during recreation or do any other intensive operation the Bundle is not the right choice for you.
It is not even designed to carry large objects.
In that case the only solution is to retain the Bitmap and not let it be destroyed by Android.
Here you find a detailed description of how to do it: Retain Bitmap during Screen Rotation
Below is my implementation:
The Trick is to dynamically add a Fragment to the Activity and store the Bitmap inside that Fragment.
It is then possible to tell Android to retain that fragment when the Activity is restarted.
We tell it with the following line:
setRetainInstance(true);

 

The Fragment does not need to have an associated Layout.

Create the Fragment:

import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;

public class ImageRetainingFragment extends Fragment {
    private Bitmap selectedImage;

    @Override    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment        
        setRetainInstance(true);
    }

    public void setImage(Bitmap selectedImage) {
        this.selectedImage = selectedImage;
    }

    public Bitmap getImage() {
        return this.selectedImage;
    }
}

Add the Fragment to the Activity:

private static final String FRAGMENT_NAME = "imageFragment";
...

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ....

    initializeImageRetainingFragment();
    tryLoadImage();
}


private void initializeImageRetainingFragment() {
    // find the retained fragment on activity restarts    
    FragmentManager fragmentManager = getSupportFragmentManager();
    this.imageRetainingFragment = (ImageRetainingFragment) fragmentManager.findFragmentByTag(FRAGMENT_NAME);
    // create the fragment and bitmap the first time    
    if (this.imageRetainingFragment == null) {
        this.imageRetainingFragment = new ImageRetainingFragment();
        fragmentManager.beginTransaction()
                 // Add a fragment to the activity state.               
                .add(this.imageRetainingFragment, FRAGMENT_NAME)
                .commit();
    }
}

private void tryLoadImage() {
    if (this.imageRetainingFragment == null) {
        return;
    }

    Bitmap selectedImage = this.imageRetainingFragment.getImage();
    if (selectedImage == null) {
        return;
    }

    ImageView selectedImageView = (ImageView) findViewById(R.id.selectedImage);
    selectedImageView.setImageBitmap(selectedImage);
}

 

Leave a Reply

%d bloggers like this: