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

前端 未结 28 1198
轮回少年
轮回少年 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 07:43

    This is my solution when I faced it: ProgressDialog is not a Fragment child, so my custom class "ProgressDialogFragment" can extend DialogFragment instead in order to keep the dialog shown for configuration changes.

    import androidx.annotation.NonNull;
    import android.app.Dialog;
    import android.app.ProgressDialog;
    import android.os.Bundle; 
    import androidx.fragment.app.DialogFragment;
    import androidx.fragment.app.FragmentManager;
    
     /**
     * Usage:
     * To display the dialog:
     *     >>> ProgressDialogFragment.showProgressDialogFragment(
     *              getSupportFragmentManager(), 
     *              "fragment_tag", 
     *              "my dialog title", 
     *              "my dialog message");
     *              
     * To hide the dialog
     *     >>> ProgressDialogFragment.hideProgressDialogFragment();
     */ 
    
    
    public class ProgressDialogFragment extends DialogFragment {
    
        private static String sTitle, sMessage;
        private static ProgressDialogFragment sProgressDialogFragment;
    
        public ProgressDialogFragment() {
        }
    
        private ProgressDialogFragment(String title, String message) {
            sTitle = title;
            sMessage = message;
        }
    
    
        @NonNull
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return ProgressDialog.show(getActivity(), sTitle, sMessage);
        }
    
        public static void showProgressDialogFragment(FragmentManager fragmentManager, String fragmentTag, String title, String message) {
            if (sProgressDialogFragment == null) {
                sProgressDialogFragment = new ProgressDialogFragment(title, message);
                sProgressDialogFragment.show(fragmentManager, fragmentTag);
    
            } else { // case of config change (device rotation)
                sProgressDialogFragment = (ProgressDialogFragment) fragmentManager.findFragmentByTag(fragmentTag); // sProgressDialogFragment will try to survive its state on configuration as much as it can, but when calling .dismiss() it returns NPE, so we have to reset it on each config change
                sTitle = title;
                sMessage = message;
            }
    
        }
    
        public static void hideProgressDialogFragment() {
            if (sProgressDialogFragment != null) {
                sProgressDialogFragment.dismiss();
            }
        }
    }
    

    The challenge was to retain the dialog title & message while screen rotation as they reset to the default empty string, although the dialog still shown

    There are 2 approaches to solve this:

    First approach: Make the activity that utilizes the dialog to retain state during config change in manifest file:

    android:configChanges="orientation|screenSize|keyboardHidden"
    

    This approach is not preferred by Google.

    Second approach: on the activity's onCreate() method, you need to retain your DialogFragment by rebuilding the ProgressDialogFragment again with the title & message as follows if the savedInstanceState is not null:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_deal);
    
     if (savedInstanceState != null) {
          ProgressDialogFragment saveProgressDialog = (ProgressDialogFragment) getSupportFragmentManager()
                  .findFragmentByTag("fragment_tag");
          if (saveProgressDialog != null) {
              showProgressDialogFragment(getSupportFragmentManager(), "fragment_tag", "my dialog title", "my dialog message");
          }
      }
    }
    
    0 讨论(0)
  • 2020-11-22 07:45

    I going to contribute my approach to handling this rotation issue. This may not be relevant to OP as he's not using AsyncTask, but maybe others will find it useful. It's pretty simple but it seems to do the job for me:

    I have a login activity with a nested AsyncTask class called BackgroundLoginTask.

    In my BackgroundLoginTask I don't do anything out of the ordinary except to add a null check upon calling ProgressDialog's dismiss:

    @Override
    protected void onPostExecute(Boolean result)
    {    
    if (pleaseWaitDialog != null)
                pleaseWaitDialog.dismiss();
    [...]
    }
    

    This is to handle the case where the background task finishes while the Activity is not visible and, therefore, the progress dialog has already been dismissed by the onPause() method.

    Next, in my parent Activity class, I create global static handles to my AsyncTask class and my ProgressDialog (the AsyncTask, being nested, can access these variables):

    private static BackgroundLoginTask backgroundLoginTask;
    private static ProgressDialog pleaseWaitDialog;
    

    This serves two purposes: First, it allows my Activity to always access the AsyncTask object even from a new, post-rotated activity. Second, it allows my BackgroundLoginTask to access and dismiss the ProgressDialog even after a rotate.

    Next, I add this to onPause(), causing the progress dialog to disappear when our Activity is leaving the foreground (preventing that ugly "force close" crash):

        if (pleaseWaitDialog != null)
        pleaseWaitDialog.dismiss();
    

    Finally, I have the following in my onResume() method:

    if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
            {
               if (pleaseWaitDialog != null)
                 pleaseWaitDialog.show();
            }
    

    This allows the Dialog to reappear after the Activity is recreated.

    Here is the entire class:

    public class NSFkioskLoginActivity extends NSFkioskBaseActivity {
        private static BackgroundLoginTask backgroundLoginTask;
        private static ProgressDialog pleaseWaitDialog;
        private Controller cont;
    
        // This is the app entry point.
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            if (CredentialsAvailableAndValidated())
            {
            //Go to main menu and don't run rest of onCreate method.
                gotoMainMenu();
                return;
            }
            setContentView(R.layout.login);
            populateStoredCredentials();   
        }
    
        //Save current progress to options when app is leaving foreground
        @Override
        public void onPause()
        {
            super.onPause();
            saveCredentialsToPreferences(false);
            //Get rid of progress dialog in the event of a screen rotation. Prevents a crash.
            if (pleaseWaitDialog != null)
            pleaseWaitDialog.dismiss();
        }
    
        @Override
        public void onResume()
        {
            super.onResume();
            if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
            {
               if (pleaseWaitDialog != null)
                 pleaseWaitDialog.show();
            }
        }
    
        /**
         * Go to main menu, finishing this activity
         */
        private void gotoMainMenu()
        {
            startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class));
            finish();
        }
    
        /**
         * 
         * @param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password.
         */
        private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue)
        {
            SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE);
            SharedPreferences.Editor prefEditor = settings.edit();
            EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
            EditText pswText = (EditText) findViewById(R.id.editTextPassword);
            prefEditor.putString(USERNAME, usernameText.getText().toString());
            prefEditor.putString(PASSWORD, pswText.getText().toString());
            if (setValidatedBooleanTrue)
            prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true);
            prefEditor.commit();
        }
    
        /**
         * Checks if user is already signed in
         */
        private boolean CredentialsAvailableAndValidated() {
            SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
                    MODE_PRIVATE);
            if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true)
             return true;   
            else
            return false;
        }
    
        //Populate stored credentials, if any available
        private void populateStoredCredentials()
        {
            SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
                MODE_PRIVATE);
            settings.getString(USERNAME, "");
           EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
           usernameText.setText(settings.getString(USERNAME, ""));
           EditText pswText = (EditText) findViewById(R.id.editTextPassword);
           pswText.setText(settings.getString(PASSWORD, ""));
        }
    
        /**
         * Validate credentials in a seperate thread, displaying a progress circle in the meantime
         * If successful, save credentials in preferences and proceed to main menu activity
         * If not, display an error message
         */
        public void loginButtonClick(View view)
        {
            if (phoneIsOnline())
            {
            EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
            EditText pswText = (EditText) findViewById(R.id.editTextPassword);
               //Call background task worker with username and password params
               backgroundLoginTask = new BackgroundLoginTask();
               backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString());
            }
            else
            {
            //Display toast informing of no internet access
            String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable);
            Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT);
            toast.show();
            }
        }
    
        /**
         * 
         * Takes two params: username and password
         *
         */
        public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean>
        {       
           private Exception e = null;
    
           @Override
           protected void onPreExecute()
           {
               cont = Controller.getInstance();
               //Show progress dialog
               String pleaseWait = getResources().getString(R.string.pleaseWait);
               String commWithServer = getResources().getString(R.string.communicatingWithServer);
                if (pleaseWaitDialog == null)
                  pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true);
    
           }
    
            @Override
            protected Boolean doInBackground(Object... params)
            {
            try {
                //Returns true if credentials were valid. False if not. Exception if server could not be reached.
                return cont.validateCredentials((String)params[0], (String)params[1]);
            } catch (Exception e) {
                this.e=e;
                return false;
            }
            }
    
            /**
             * result is passed from doInBackground. Indicates whether credentials were validated.
             */
            @Override
            protected void onPostExecute(Boolean result)
            {
            //Hide progress dialog and handle exceptions
            //Progress dialog may be null if rotation has been switched
            if (pleaseWaitDialog != null)
                 {
                pleaseWaitDialog.dismiss();
                    pleaseWaitDialog = null;
                 }
    
            if (e != null)
            {
             //Show toast with exception text
                    String networkError = getResources().getString(R.string.serverErrorException);
                    Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT);
                toast.show();
            }
            else
            {
                if (result == true)
                {
                saveCredentialsToPreferences(true);
                gotoMainMenu();
                }
                else
                {
                String toastText = getResources().getString(R.string.invalidCredentialsEntered);
                    Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
                toast.show();
                } 
            }
            }
    
        }
    }
    

    I am by no means a seasoned Android developer, so feel free to comment.

    0 讨论(0)
  • 2020-11-22 07:45

    The simplest and most flexible solution is to use an AsyncTask with a static reference to ProgressBar. This provides an encapsulated and thus reusable solution to orientation change problems. This solution has served me well for varying asyncronous tasks including internet downloads, communicating with Services, and filesystem scans. The solution has been well tested on multiple android versions and phone models. A complete demo can be found here with specific interest in DownloadFile.java

    I present the following as a concept example

    public class SimpleAsync extends AsyncTask<String, Integer, String> {
        private static ProgressDialog mProgressDialog = null;
        private final Context mContext;
    
        public SimpleAsync(Context context) {
            mContext = context;
            if ( mProgressDialog != null ) {
                onPreExecute();
            }
        }
    
        @Override
        protected void onPreExecute() {
            mProgressDialog = new ProgressDialog( mContext );
            mProgressDialog.show();
        }
    
        @Override
        protected void onPostExecute(String result) {
            if ( mProgressDialog != null ) {
                mProgressDialog.dismiss();
                mProgressDialog = null;
            }
        }
    
        @Override
        protected void onProgressUpdate(Integer... progress) {
            mProgressDialog.setProgress( progress[0] );
        }
    
        @Override
        protected String doInBackground(String... sUrl) {
            // Do some work here
            publishProgress(1);
            return null;
        }
    
        public void dismiss() {
            if ( mProgressDialog != null ) {
                mProgressDialog.dismiss();
            }
        }
    }
    

    Usage in an Android Activity is simple

    public class MainActivity extends Activity {
        DemoServiceClient mClient = null;
        DownloadFile mDownloadFile = null;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate( savedInstanceState );
            setContentView( R.layout.main );
            mDownloadFile = new DownloadFile( this );
    
            Button downloadButton = (Button) findViewById( R.id.download_file_button );
            downloadButton.setOnClickListener( new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
                }
            });
        }
    
        @Override
        public void onPause() {
            super.onPause();
            mDownloadFile.dismiss();
        }
    }
    
    0 讨论(0)
  • 2020-11-22 07:46

    If you create a background Service that does all the heavy lifting (tcp requests/response, unmarshalling), the View and Activity can be destroyed and re-created without leaking window or losing data. This allows the Android recommended behavior, which is to destroy an Activity on each configuration change (eg. for each orientation change).

    It is a bit more complex, but it is the best way for invoking server request, data pre/post-processing, etc.

    You may even use your Service to queue each request to a server, so it makes it easy and efficient to handle those things.

    The dev guide has a full chapter on Services.

    0 讨论(0)
  • 2020-11-22 07:46

    This is my proposed solution:

    • Move the AsyncTask or Thread to a retained Fragment, as explained here. I believe it is a good practice to move all network calls to fragments. If you are already using fragments, one of them could be made responsible for the calls. Otherwise, you can create a fragment just for doing the request, as the linked article proposes.
    • The fragment will use a listener interface to signal the task completion/failure. You don't have to worry for orientation changes there. The fragment will always have the correct link to the current activity and progress dialog can be safely resumed.
    • Make your progress dialog a member of your class. In fact you should do that for all dialogs. In the onPause method you should dismiss them, otherwise you will leak a window on the configuration change. The busy state should be kept by the fragment. When the fragment is attached to the activity, you can bring up the progress dialog again, if the call is still running. A void showProgressDialog() method can be added to the fragment-activity listener interface for this purpose.
    0 讨论(0)
  • 2020-11-22 07:47

    Move the long task to a seperate class. Implement it as a subject-observer pattern. Whenever the activity is created register and while closing unregister with the task class. Task class can use AsyncTask.

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