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