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

前端 未结 28 1249
轮回少年
轮回少年 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:47

    If you maintain two layouts, all UI thread should be terminated.

    If you use AsynTask, then you can easily call .cancel() method inside onDestroy() method of current activity.

    @Override
    protected void onDestroy (){
        removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
        if (loginTask != null){
            if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
                loginTask.cancel(true); //cancel AsyncTask
        }
        super.onDestroy();
    }
    

    For AsyncTask, read more in "Cancelling a task" section at here.

    Update: Added condition to check status, as it can be only cancelled if it is in running state. Also note that the AsyncTask can only be executed one time.

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

    Tried to implement jfelectron's solution because it is a "rock-solid solution to these issues that conforms with the 'Android Way' of things" but it took some time to look up and put together all the elements mentioned. Ended up with this slightly different, and I think more elegant, solution posted here in it's entirety.

    Uses an IntentService fired from an activity to perform the long running task on a separate thread. The service fires back sticky Broadcast Intents to the activity which update the dialog. The Activity uses showDialog(), onCreateDialog() and onPrepareDialog() to eliminate the need to have persistent data passed in the application object or the savedInstanceState bundle. This should work no matter how your application is interrupted.

    Activity Class:

    public class TesterActivity extends Activity {
    private ProgressDialog mProgressDialog;
    private static final int PROGRESS_DIALOG = 0;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    
        Button b = (Button) this.findViewById(R.id.test_button);
        b.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                buttonClick();
            }
        });
    }
    
    private void buttonClick(){
        clearPriorBroadcast();
        showDialog(PROGRESS_DIALOG);
        Intent svc = new Intent(this, MyService.class);
        startService(svc);
    }
    
    protected Dialog onCreateDialog(int id) {
        switch(id) {
        case PROGRESS_DIALOG:
            mProgressDialog = new ProgressDialog(TesterActivity.this);
            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mProgressDialog.setMax(MyService.MAX_COUNTER);
            mProgressDialog.setMessage("Processing...");
            return mProgressDialog;
        default:
            return null;
        }
    }
    
    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        switch(id) {
        case PROGRESS_DIALOG:
            // setup a broadcast receiver to receive update events from the long running process
            IntentFilter filter = new IntentFilter();
            filter.addAction(MyService.BG_PROCESS_INTENT);
            registerReceiver(new MyBroadcastReceiver(), filter);
            break;
        }
    }
    
    public class MyBroadcastReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.hasExtra(MyService.KEY_COUNTER)){
                int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
                mProgressDialog.setProgress(count);
                if (count >= MyService.MAX_COUNTER){
                    dismissDialog(PROGRESS_DIALOG);
                }
            }
        }
    }
    
    /*
     * Sticky broadcasts persist and any prior broadcast will trigger in the 
     * broadcast receiver as soon as it is registered.
     * To clear any prior broadcast this code sends a blank broadcast to clear 
     * the last sticky broadcast.
     * This broadcast has no extras it will be ignored in the broadcast receiver 
     * setup in onPrepareDialog()
     */
    private void clearPriorBroadcast(){
        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
        sendStickyBroadcast(broadcastIntent);
    }}
    

    IntentService Class:

    public class MyService extends IntentService {
    
    public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
    public static final String KEY_COUNTER = "counter";
    public static final int MAX_COUNTER = 100;
    
    public MyService() {
      super("");
    }
    
    @Override
    protected void onHandleIntent(Intent intent) {
        for (int i = 0; i <= MAX_COUNTER; i++) {
            Log.e("Service Example", " " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Intent broadcastIntent = new Intent();
            broadcastIntent.setAction(BG_PROCESS_INTENT);
            broadcastIntent.putExtra(KEY_COUNTER, i);
            sendStickyBroadcast(broadcastIntent);
        }
    }}
    

    Manifest file entries:

    before application section:

    uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
    uses-permission android:name="android.permission.BROADCAST_STICKY"
    

    inside application section

    service android:name=".MyService"
    
    0 讨论(0)
  • 2020-11-22 07:48

    If you're struggling with detecting orientation change events of a dialog INDEPENDENT OF AN ACTIVITY REFERENCE, this method works excitingly well. I use this because I have my own dialog class that can be shown in multiple different Activities so I don't always know which Activity it's being shown in. With this method you don't need to change the AndroidManifest, worry about Activity references, and you don't need a custom dialog (as I have). You do need, however, a custom content view so you can detect the orientation changes using that particular view. Here's my example:

    Setup

    public class MyContentView extends View{
        public MyContentView(Context context){
            super(context);
        }
    
        @Override
        public void onConfigurationChanged(Configuration newConfig){
            super.onConfigurationChanged(newConfig);
    
            //DO SOMETHING HERE!! :D
        }
    }
    

    Implementation 1 - Dialog

    Dialog dialog = new Dialog(context);
    //set up dialog
    dialog.setContentView(new MyContentView(context));
    dialog.show();
    

    Implementation 2 - AlertDialog.Builder

    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    //set up dialog builder
    builder.setView(new MyContentView(context));        //Can use this method
    builder.setCustomTitle(new MycontentView(context)); // or this method
    builder.build().show();
    

    Implementation 3 - ProgressDialog / AlertDialog

    ProgressDialog progress = new ProgressDialog(context);
    //set up progress dialog
    progress.setView(new MyContentView(context));        //Can use this method
    progress.setCustomTitle(new MyContentView(context)); // or this method
    progress.show();
    
    0 讨论(0)
  • 2020-11-22 07:49

    I have done it like this:

        package com.palewar;
        import android.app.Activity;
        import android.app.ProgressDialog;
        import android.os.Bundle;
        import android.os.Handler;
        import android.os.Message;
    
        public class ThreadActivity extends Activity {
    
    
            static ProgressDialog dialog;
            private Thread downloadThread;
            final static Handler handler = new Handler() {
    
                @Override
                public void handleMessage(Message msg) {
    
                    super.handleMessage(msg);
    
                    dialog.dismiss();
    
                }
    
            };
    
            protected void onDestroy() {
        super.onDestroy();
                if (dialog != null && dialog.isShowing()) {
                    dialog.dismiss();
                    dialog = null;
                }
    
            }
    
            /** Called when the activity is first created. */
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
    
                downloadThread = (Thread) getLastNonConfigurationInstance();
                if (downloadThread != null && downloadThread.isAlive()) {
                    dialog = ProgressDialog.show(ThreadActivity.this, "",
                            "Signing in...", false);
                }
    
                dialog = ProgressDialog.show(ThreadActivity.this, "",
                        "Signing in ...", false);
    
                downloadThread = new MyThread();
                downloadThread.start();
                // processThread();
            }
    
            // Save the thread
            @Override
            public Object onRetainNonConfigurationInstance() {
                return downloadThread;
            }
    
    
            static public class MyThread extends Thread {
                @Override
                public void run() {
    
                    try {
                        // Simulate a slow network
                        try {
                            new Thread().sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        handler.sendEmptyMessage(0);
    
                    } finally {
    
                    }
                }
            }
    
        }
    

    You can also try and let me know it works for you or not

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

    I came up with a rock-solid solution for these issues that conforms with the 'Android Way' of things. I have all my long-running operations using the IntentService pattern.

    That is, my activities broadcast intents, the IntentService does the work, saves the data in the DB and then broadcasts sticky intents. The sticky part is important, such that even if the Activity was paused during during the time after the user initiated the work and misses the real time broadcast from the IntentService we can still respond and pick up the data from the calling Activity. ProgressDialogs can work with this pattern quite nicely with onSaveInstanceState().

    Basically, you need to save a flag that you have a progress dialog running in the saved instance bundle. Do not save the progress dialog object because this will leak the entire Activity. To have a persistent handle to the progress dialog, I store it as a weak reference in the application object. On orientation change or anything else that causes the Activity to pause (phone call, user hits home etc.) and then resume, I dismiss the old dialog and recreate a new dialog in the newly created Activity.

    For indefinite progress dialogs this is easy. For progress bar style, you have to put the last known progress in the bundle and whatever information you're using locally in the activity to keep track of the progress. On restoring the progress, you'll use this information to re-spawn the progress bar in the same state as before and then update based on the current state of things.

    So to summarize, putting long-running tasks into an IntentService coupled with judicious use of onSaveInstanceState() allows you to efficiently keep track of dialogs and restore then across the Activity life-cycle events. Relevant bits of Activity code are below. You'll also need logic in your BroadcastReceiver to handle Sticky intents appropriately, but that is beyond the scope of this.

    public void doSignIn(View view) {
        waiting=true;
        AppClass app=(AppClass) getApplication();
        String logingon=getString(R.string.signon);
        app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
        ...
    }
    
    @Override
    protected void onSaveInstanceState(Bundle saveState) {
        super.onSaveInstanceState(saveState);
        saveState.putBoolean("waiting",waiting);
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(savedInstanceState!=null) {
            restoreProgress(savedInstanceState);    
        }
        ...
    }
    
    private void restoreProgress(Bundle savedInstanceState) {
        waiting=savedInstanceState.getBoolean("waiting");
        if (waiting) {
            AppClass app=(AppClass) getApplication();
            ProgressDialog refresher=(ProgressDialog) app.Dialog.get();
            refresher.dismiss();
            String logingon=getString(R.string.signon);
            app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
        }
    }
    
    0 讨论(0)
  • 2020-11-22 07:50

    The original perceived problem was that the code would not survive a screen orientation change. Apparently this was "solved" by having the program handle the screen orientation change itself, instead of letting the UI framework do it (via calling onDestroy)).

    I would submit that if the underlying problem is that the program will not survive onDestroy(), then the accepted solution is just a workaround that leaves the program with serious other problems and vulnerabilities. Remember that the Android framework specifically states that your activity is at risk for being destroyed almost at any time due to circumstances outside your control. Therefore, your activity must be able to survive onDestroy() and subsequent onCreate() for any reason, not just a screen orientation change.

    If you are going to accept handling screen orientation changes yourself to solve the OP's problem, you need to verify that other causes of onDestroy() do not result in the same error. Are you able to do this? If not, I would question whether the "accepted" answer is really a very good one.

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