How to handle screen orientation change when progress dialog and background thread active?

前端 未结 28 1252
轮回少年
轮回少年 2020-11-22 07:03

My program does some network activity in a background thread. Before starting, it pops up a progress dialog. The dialog is dismissed on the handler. This all works fine, exc

相关标签:
28条回答
  • 2020-11-22 08:00

    These days there is a much more distinct way to handle these types of issues. The typical approach is:

    1. Ensure your data is properly seperated from the UI:

    Anything that is a background process should be in a retained Fragment (set this with Fragment.setRetainInstance(). This becomes your 'persistent data storage' where anything data based that you would like retained is kept. After the orientation change event, this Fragment will still be accessible in its original state through a FragmentManager.findFragmentByTag() call (when you create it you should give it a tag not an ID as it is not attached to a View).

    See the Handling Runtime Changes developed guide for information about doing this correctly and why it is the best option.

    2. Ensure you are interfacing correctly and safely between the background processs and your UI:

    You must reverse your linking process. At the moment your background process attaches itself to a View - instead your View should be attaching itself to the background process. It makes more sense right? The View's action is dependent on the background process, whereas the background process is not dependent on the View.This means changing the link to a standard Listener interface. Say your process (whatever class it is - whether it is an AsyncTask, Runnable or whatever) defines a OnProcessFinishedListener, when the process is done it should call that listener if it exists.

    This answer is a nice concise description of how to do custom listeners.

    3. Link your UI into the data process whenever the UI is created (including orientation changes):

    Now you must worry about interfacing the background task with whatever your current View structure is. If you are handling your orientation changes properly (not the configChanges hack people always recommend), then your Dialog will be recreated by the system. This is important, it means that on the orientation change, all your Dialog's lifecycle methods are recalled. So in any of these methods (onCreateDialog is usually a good place), you could do a call like the following:

    DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
    if (f != null) {
        f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
            public void onProcessFinished() {
                dismiss();
            }
        });
     }
    

    See the Fragment lifecycle for deciding where setting the listener best fits in your individual implementation.

    This is a general approach to providing a robust and complete solution to the generic problem asked in this question. There is probably a few minor pieces missing in this answer depending on your individual scenario, but this is generally the most correct approach for properly handling orientation change events.

    0 讨论(0)
  • 2020-11-22 08:05

    My solution was to extend the ProgressDialog class to get my own MyProgressDialog.
    I redefined show() and dismiss() methods to lock the orientation before showing the Dialog and unlock it back when Dialog is dismissed. So when the Dialog is shown and the orientation of the device changes, the orientation of the screen remains until dismiss() is called, then screen-orientation changes according to sensor-values/device-orientation.

    Here is my code:

    public class MyProgressDialog extends ProgressDialog {
    private Context mContext;
    
    public MyProgressDialog(Context context) {
        super(context);
        mContext = context;
    }
    
    public MyProgressDialog(Context context, int theme) {
        super(context, theme);
        mContext = context;
    }
    
    public void show() {
        if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
            ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        else
            ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        super.show();
    }
    
    public void dismiss() {
        super.dismiss();
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
    }
    
    }
    
    0 讨论(0)
  • 2020-11-22 08:06

    I met the same problem. My activity needs to parse some data from a URL and it's slow. So I create a thread to do so, then show a progress dialog. I let the thread post a message back to UI thread via Handler when it's finished. In Handler.handleMessage, I get the data object (ready now) from thread and populate it to UI. So it's very similar to your example.

    After a lot of trial and error it looks like I found a solution. At least now I can rotate screen at any moment, before or after the thread is done. In all tests, the dialog is properly closed and all behaviors are as expected.

    What I did is shown below. The goal is to fill my data model (mDataObject) and then populate it to UI. Should allow screen rotation at any moment without surprise.

    class MyActivity {
    
        private MyDataObject mDataObject = null;
        private static MyThread mParserThread = null; // static, or make it singleton
    
        OnCreate() {
            ...
            Object retained = this.getLastNonConfigurationInstance();
            if(retained != null) {
                // data is already completely obtained before config change
                // by my previous self.
                // no need to create thread or show dialog at all
                mDataObject = (MyDataObject) retained;
                populateUI();
            } else if(mParserThread != null && mParserThread.isAlive()){
                // note: mParserThread is a static member or singleton object.
                // config changed during parsing in previous instance. swap handler
                // then wait for it to finish.
                mParserThread.setHandler(new MyHandler());
            } else {
                // no data and no thread. likely initial run
                // create thread, show dialog
                mParserThread = new MyThread(..., new MyHandler());
                mParserThread.start();
                showDialog(DIALOG_PROGRESS);
            }
        }
    
        // http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html
        public Object onRetainNonConfigurationInstance() {
            // my future self can get this without re-downloading
            // if it's already ready.
            return mDataObject;
        }
    
        // use Activity.showDialog instead of ProgressDialog.show
        // so the dialog can be automatically managed across config change
        @Override
        protected Dialog onCreateDialog(int id) {
            // show progress dialog here
        }
    
        // inner class of MyActivity
        private class MyHandler extends Handler {
            public void handleMessage(msg) {
                mDataObject = mParserThread.getDataObject();
                populateUI();
                dismissDialog(DIALOG_PROGRESS);
            }
        }
    }
    
    class MyThread extends Thread {
        Handler mHandler;
        MyDataObject mDataObject;
    
        // constructor with handler param
        public MyHandler(..., Handler h) {
            ...
            mHandler = h;
        }
    
        public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
        public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller
    
        public void run() {
            mDataObject = new MyDataObject();
            // do the lengthy task to fill mDataObject with data
            lengthyTask(mDataObject);
            // done. notify activity
            mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
        }
    }
    

    That's what works for me. I don't know if this is the "correct" method as designed by Android -- they claim this "destroy/recreate activity during screen rotation" actually makes things easier, so I guess it shouldn't be too tricky.

    Let me know if you see a problem in my code. As said above I don't really know if there is any side effect.

    0 讨论(0)
  • 2020-11-22 08:08

    Edit: Google engineers do not recommend this approach, as described by Dianne Hackborn (a.k.a. hackbod) in this StackOverflow post. Check out this blog post for more information.


    You have to add this to the activity declaration in the manifest:

    android:configChanges="orientation|screenSize"
    

    so it looks like

    <activity android:label="@string/app_name" 
            android:configChanges="orientation|screenSize|keyboardHidden" 
            android:name=".your.package">
    

    The matter is that the system destroys the activity when a change in the configuration occurs. See ConfigurationChanges.

    So putting that in the configuration file avoids the system to destroy your activity. Instead it invokes the onConfigurationChanged(Configuration) method.

    0 讨论(0)
提交回复
热议问题