Fighting Orientation Changes in Android

One of the problems beginners often struggle with in Android is orientation changes. The problem with an orientation change is that it per se destroys the currently running activity, creates a new instance and starts that instance with the original Intent again.

This behavior can be pretty disturbing in applications with multiple background threads/asynchronous tasks. This article shows how to handle background threads when the current activity instance is destroyed and created back and forth.

Orientation Change Behavior

For the matter of purpose let us assume we have an Activity that shows a large Image using an ImageView loaded from a web server. As the image is very large we decided to show a progress dialog during loading the image.


public class RemoteImageViewActivity extends Activity {

  private ImageView imageView;
  private ProgressDialog dialog;

  private class DownloadImageTask extends AsyncTask<URL, Integer, Bitmap> {
     protected Bitmap doInBackground(URL... urls) {
         return Downloader.downloadFile(urls[0]); // or for testing purpose just Thread.sleep(30000);
     }

     protected void onPostExecute(Bitmap bitmap) {
         imageView.setImageBitmap(bitmap);
         dialog.dismiss();
     }
 }

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        imageView = (ImageView) findViewById(R.id.imageView);
        dialog = ProgressDialog.show(this, "", "Loading. Please wait...", true);

        try {
            new DownloadImageTask().execute(new URL("http://blog.andresteingress.com/"));

        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }
}

The example above shows a straight-forward implementation I’ve already seen in a handful of Android applications (mine included ;) ).

Now let us see what happens when an orientation change happens during the image is downloaded. As can be seen from the stack trace below, when orientation is changed, our code somehow leaks an activity instance

ERROR/WindowManager(1336): Activity com.example.RemoteImageViewActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@4051c210 that was originally added here
        android.view.WindowLeaked: Activity com.example.RemoteImageViewActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@4051c210 that was originally added here
        at android.view.ViewRoot.<init>(ViewRoot.java:258)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:148)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
        at android.view.Window$LocalWindowManager.addView(Window.java:424)
        at android.app.Dialog.show(Dialog.java:241)
        at android.app.ProgressDialog.show(ProgressDialog.java:107)
        at android.app.ProgressDialog.show(ProgressDialog.java:90)
        at com.example.RemoteImageViewActivity.onCreate(RemoteImageViewActivity.java:44)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
        at android.app.ActivityThread.access$1500(ActivityThread.java:117)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:130)
        at android.app.ActivityThread.main(ActivityThread.java:3683)

and after a while when the dialog needs to be dismissed another exception happens


ERROR/AndroidRuntime(1336): FATAL EXCEPTION: main
        java.lang.IllegalArgumentException: View not attached to window manager
        at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)
        at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:200)
        at android.view.Window$LocalWindowManager.removeView(Window.java:432)
        at android.app.Dialog.dismissDialog(Dialog.java:278)
        at android.app.Dialog.access$000(Dialog.java:71)
        at android.app.Dialog$1.run(Dialog.java:111)
        at android.app.Dialog.dismiss(Dialog.java:268)

The behavior seems odd at first glance, but when digging into the Android documentation, one finds the reason: per default Android recreates the currently running activity whenever the orientation is changed. That explains why onPostExecute can’t access the current application window and throws an exception (the first exception with the leaking activity instance is explained in a minute).

Handling Orientation changes

There are several ways how to handle an orientation change without leaking windows or accessing “dead” views. In our example above, one way would be to leave the dialog management over to the current Activity. This is done by overriding onCreateDialog and using its companion methods showDialog and dismissDialog.

public class RemoteImageViewActivity extends Activity
{

    protected static final int PROGRESS_BAR_DIALOG = 1;

    private ImageView imageView;

    private class DownloadImageTask extends AsyncTask<URL, Integer, Bitmap> {
        protected Bitmap doInBackground(URL... urls) {

            try {
                Thread.sleep(30000);

                return Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);

            } catch (InterruptedException e) {
                return null;
            }
        }

        protected void onPostExecute(Bitmap bitmap) {
            imageView.setImageBitmap(bitmap);

            dismissDialog(PROGRESS_BAR_DIALOG);
        }
    }

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        imageView = (ImageView) findViewById(R.id.imageView);

        showDialog(PROGRESS_BAR_DIALOG);

        try {
            new DownloadImageTask().execute(new URL("http://blog.andresteingress.com/"));

        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override protected Dialog onCreateDialog(int id) {
        switch (id)  {
            case PROGRESS_BAR_DIALOG:
                return new ProgressDialog.Builder(this).setTitle("").setMessage("Loading. Please wait...").setCancelable(false).create();
        }

        return null;
    }
}

Overriding the onCreateDialog has the advantage that the Activity base class handles persisting the dialogs state and recreating the dialog on orientation changes.

The problem with the example above is that changing the orientation twice or more times, leads again to the view leaking exception from above as the onCreate method of the activity is called whenever the orientation is changed.

ERROR/WindowManager(1336): Activity com.example.RemoteImageViewActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@4051c210 that was originally added here
        android.view.WindowLeaked: Activity com.example.RemoteImageViewActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@4051c210 that was originally added here
        at android.view.ViewRoot.<init>(ViewRoot.java:258)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:148)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
        at android.view.Window$LocalWindowManager.addView(Window.java:424)
        at android.app.Dialog.show(Dialog.java:241)
        at android.app.ProgressDialog.show(ProgressDialog.java:107)
        at android.app.ProgressDialog.show(ProgressDialog.java:90)
        at com.example.RemoteImageViewActivity.onCreate(RemoteImageViewActivity.java:44)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
        at android.app.ActivityThread.access$1500(ActivityThread.java:117)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:130)
        at android.app.ActivityThread.main(ActivityThread.java:3683)

This behavior is caused by DownloadImageTask being an inner class. After the orientation change, the asynchronous task refers to the old activity instance and, even worse, it additionally has a reference to the imageView to set the downloaded image accordingly.

But how could we solve that binding to the outer activity instance in our DownloadImageTask task class?

Using a static Handler/Activity Combo

In order to handle the problem with references to the old activity inside inner class object instances, we could use static variables in combination with Android’s Handler class.

public class RemoteImageViewActivity extends Activity
{
    private static RemoteImageViewActivity ACTIVITY = null;

    private static final int SHOW_PROGRESS_BAR_DIALOG = 1;
    private static final int HIDE_PROGRESS_BAR_DIALOG = 2;
    private static final int SHOW_IMAGE = 3;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (ACTIVITY == null) return;

            switch (msg.what)  {
                case SHOW_PROGRESS_BAR_DIALOG:
                     ACTIVITY.showDialog(PROGRESS_BAR_DIALOG);
                    break;

                case HIDE_PROGRESS_BAR_DIALOG:
                    ACTIVITY.dismissDialog(PROGRESS_BAR_DIALOG);
                    break;

                case SHOW_IMAGE:
                    ImageView imageView = (ImageView) ACTIVITY.findViewById(R.id.imageView);
                    imageView.setImageBitmap((Bitmap) msg.obj);

            }
        }
    };

    protected static final int PROGRESS_BAR_DIALOG = 1;

    private static class DownloadImageTask extends AsyncTask<URL, Integer, Bitmap> {

        private Handler handler;

        public DownloadImageTask(Handler handler)  {
            this.handler = handler;
        }

        protected Bitmap doInBackground(URL... urls) {

            try {
                Thread.sleep(30000);

                return Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);

            } catch (InterruptedException e) {
                return null;
            }
        }

        protected void onPostExecute(Bitmap bitmap) {
            handler.sendEmptyMessage(HIDE_PROGRESS_BAR_DIALOG);
            handler.sendMessage(Message.obtain(handler, SHOW_IMAGE, bitmap));
        }
    }

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        handler.sendEmptyMessage(SHOW_PROGRESS_BAR_DIALOG);

        try {
            new DownloadImageTask(handler).execute(new URL("http://blog.andresteingress.com/"));

        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();

        ACTIVITY = this;
    }

    @Override
    protected void onStop() {
        ACTIVITY = null;

        super.onStop();
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        switch (id)  {
            case PROGRESS_BAR_DIALOG:
                return new ProgressDialog.Builder(this).setTitle("").setMessage("Loading. Please wait...").setCancelable(false).create();
        }

        return null;
    }
}

The code above always keeps a reference to the current Activity in a static class variable. The asynchronous task implementation now keeps a reference to the Handler instance, avoiding to refer to the old Activity instance. With this little trick we can now safely switch orientations while the background task is running.

But wait, didn’t we see that onCreate is called multiple times during orientation changes? That means, whenever the orientation is changed we create a new instance of DownloadImageTask and start the task again!

Overriding android:configChanges

One way to solve the problem is to introduce another static variable indicating whether the download is currently running in the background or not. But there is another way: we can provide the android:configChanges="orientation" XML attribute in our AndroidManifest.xml.

The configChanges attribute is documented [0] as

Lists configuration changes that the activity will handle itself. When a configuration change occurs at runtime, the activity is shut down and restarted by default, but declaring a configuration with this attribute will prevent the activity from being restarted. Instead, the activity remains running and its onConfigurationChanged() method is called.

where as orientation is used to react on orientation changes.

Let’s add android:configChanges="orientation" to our activity XML declaration and modify the code to

public class RemoteImageViewActivity extends Activity
{

    protected static final int PROGRESS_BAR_DIALOG = 1;

    private ImageView imageView;

    private class DownloadImageTask extends AsyncTask<URL, Integer, Bitmap> {
        protected Bitmap doInBackground(URL... urls) {

            try {
                Thread.sleep(30000);

                return Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);

            } catch (InterruptedException e) {
                return null;
            }
        }

        protected void onPostExecute(Bitmap bitmap) {
            imageView.setImageBitmap(bitmap);

            dismissDialog(PROGRESS_BAR_DIALOG);
        }
    }

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        imageView = (ImageView) findViewById(R.id.imageView);

        showDialog(PROGRESS_BAR_DIALOG);

        try {
            new DownloadImageTask().execute(new URL("http://blog.andresteingress.com/"));

        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        switch (id)  {
            case PROGRESS_BAR_DIALOG:
                return new ProgressDialog.Builder(this).setTitle("").setMessage("Loading. Please wait...").setCancelable(false).create();
        }

        return null;
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // some work that needs to be done on orientation change
    }
}

By overriding the onConfigurationChanged method we have avoided activity recreation, which – in our case – does not seem to be necessary. As you can see in the code above, with this approach we do avoid the use of static class variables and the Handler object instance.

Conclusion

There are various ways to handle orientation changes and activity recreations in Android. If you have any other tips or preferred ways to do so, you are free to mention them in this article’s comments section.

[0] android:configChanges Documentation – http://developer.android.com/guide/topics/manifest/activity-element.html#config


About andresteingress

I am working as Groovy & Grails freelance developer in Linz, Austria. I enjoy making music, working on GContracts, committing to the Groovy programming language and writing blog posts on various JVM related topics.
This entry was posted in android. Bookmark the permalink.

7 Responses to Fighting Orientation Changes in Android

  1. Mike says:

    Thanks for your clear and well-written explanation of this orientation change issue. The last option (putting android:configChanges=”orientation” in the relevant in the AndroidManifest.xml file and then inserting the onConfigurationChanged() method) worked well. I’ve found no need for any static variables now, which makes the whole code cleaner.

  2. Gatti says:

    Simplest way I’ve found if you don’t want to change your code or add static variables. Simply block screen orientation changes while executing the task:

    Add the code
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

    at the start of doInBackground and the code
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);

    at the end of postExecute ;)

  3. Ivan de Aguirre says:

    Using android:configChanges is a partial solution and should be avoided. Activity will be recreated if user changes configurations other than orientation.

    The docs also mention that Activities may be destroyed to recover system resources so the application should be aware of Activity recreation anyway.

    Btw it’s a shame that Android API doesn’t provide a clever solution for this issue.

  4. numa says:

    I think loaders (esp AsynTaskLoader) address this problem. It was introduced in honeycomb but is available in the supports library.

  5. Francesco says:

    Hello,
    I think that the code under the section “Using a static Handler/Activity Combo” contains something wrong since a subtle NullPointerException is there.

    Thanks

  6. Richard says:

    Super tutorial. Solved my problem.

    As Francesco said, the handler was null onPostExecute, which was causing the NullPointerException, because the handler was running on the main thread, whereas the sendEmptyMessage was on the AsyncTask thread. Solved this by:

    protected void onPostExecute(Bitmap bitmap) {
    ACTIVITY.handler.sendEmptyMessage(HIDE_PROGRESS_BAR_DIALOG);
    ACTIVITY.handler.sendMessage(Message.obtain(handler, SHOW_IMAGE, bitmap));
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>