Как обрабатывать изменение ориентации экрана, когда диалог выполнения и фоновый поток активны?

Моя программа выполняет некоторую сетевую активность в фоновом потоке. Перед запуском открывается диалоговое окно прогресса. Диалог отклоняется в обработчике. Все это работает отлично, за исключением случаев, когда ориентация экрана изменяется, когда диалог вверх (и фоновый поток идет). На этом этапе приложение либо сбой, либо взаимоблокировки, либо попадает в странную стадию, когда приложение не работает вообще до тех пор, пока все потоки не будут убиты.

Как я могу обработать изменение ориентации экрана изящно?

Пример кода ниже соответствует примерно тому, что делает моя настоящая программа:

public class MyAct extends Activity implements Runnable {
    public ProgressDialog mProgress;

    // UI has a button that when pressed calls send

    public void send() {
         mProgress = ProgressDialog.show(this, "Please wait", 
                      "Please wait", 
                      true, true);
        Thread thread = new Thread(this);
        thread.start();
    }

    public void run() {
        Thread.sleep(10000);
        Message msg = new Message();
        mHandler.sendMessage(msg);
    }

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mProgress.dismiss();
        }
    };
}

Stack

E/WindowManager(  244): Activity MyAct has leaked window [email protected] that was originally added here
E/WindowManager(  244): android.view.WindowLeaked: Activity MyAct has leaked window [email protected] that was originally added here
E/WindowManager(  244):     at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager(  244):     at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager(  244):     at android.app.Dialog.show(Dialog.java:212)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager(  244):     at MyAct.send(MyAct.java:294)
E/WindowManager(  244):     at MyAct$4.onClick(MyAct.java:174)
E/WindowManager(  244):     at android.view.View.performClick(View.java:2129)
E/WindowManager(  244):     at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager(  244):     at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager(  244):     at android.view.View.dispatchTouchEvent(View.java:3198)

Я попытался отклонить диалог прогресса в onSaveInstanceState, но это просто предотвращает немедленный сбой. Фоновый поток все еще продолжается, и пользовательский интерфейс находится в частично вытянутом состоянии. Вам нужно убить все приложение, прежде чем он снова начнет работать.

Ответ 1

Когда вы переключаете ориентации, Android создаст новый вид. Вероятно, вы получаете сбои, потому что ваш фоновый поток пытается изменить состояние на старом. (Это может также иметь проблемы, потому что ваш фоновый поток не находится в потоке пользовательского интерфейса)

Я бы предложил сделать этот mHandler неустойчивым и обновить его при изменении ориентации.

Ответ 2

Изменить: Инженеры Google не рекомендуют этот подход, как описано Dianne Hackborn (aka hackbod) в этом fooobar.com/questions/11649/.... Подробнее см. это сообщение в блоге.


Вы должны добавить это в объявление активности в манифесте:

android:configChanges="orientation|screenSize"

поэтому он выглядит как

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

Дело в том, что система разрушает действие, когда происходит изменение конфигурации. См. ConfigurationChanges.

Таким образом, если в файле конфигурации вы удалите систему, чтобы уничтожить вашу активность. Вместо этого он вызывает метод onConfigurationChanged(Configuration).

Ответ 3

Я придумал надежное решение для этих проблем, которое соответствует "Android Way" вещей. У меня есть все мои длительные операции с использованием шаблона IntentService.

То есть, моя деятельность передает намерения, IntentService выполняет работу, сохраняет данные в БД, а затем передает липкие намерения. Важная часть важна, так что даже если действие было приостановлено в течение времени после того, как пользователь начал работу и пропускает трансляцию в реальном времени от IntentService, мы все равно можем ответить и забрать данные из вызывающего действия. ProgressDialog может прекрасно работать с этим шаблоном с onSaveInstanceState().

По сути, вам нужно сохранить флаг, что у вас есть запущенный диалог в сохраненном экземпляре пакета. Не сохраняйте объект диалога прогресса, поскольку это приведет к утечке всей активности. Чтобы иметь постоянный дескриптор диалога прогресса, я храню его как слабую ссылку в объекте приложения. В случае изменения ориентации или чего-либо еще, что приводит к приостановке действия (телефонный звонок, посещение пользователем и т.д.), А затем возобновлению, я закрываю старый диалог и заново создаю новый диалог во вновь созданном действии.

Для неопределенных диалогов прогресса это легко. Для стиля индикатора выполнения вы должны поместить в пакет последний известный прогресс и любую информацию, которую вы используете локально в упражнении, чтобы отслеживать прогресс. При восстановлении прогресса вы будете использовать эту информацию, чтобы повторно вызывать индикатор выполнения в том же состоянии, что и раньше, а затем обновлять его в зависимости от текущего состояния.

Итак, подведем итог: размещение длительных задач в IntentService в сочетании с разумным использованием onSaveInstanceState() позволяет эффективно отслеживать диалоги и затем восстанавливать их в событиях жизненного цикла Activity. Соответствующие биты кода активности приведены ниже. Вам также понадобится логика в BroadcastReceiver для правильной обработки намерений Sticky, но это выходит за рамки этого.

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));
    }
}

Ответ 4

Я встретил ту же проблему. Моя деятельность должна анализировать некоторые данные из URL, и это медленно. Поэтому я создаю поток для этого, а затем показываю диалог прогресса. Я позволил ветке опубликовать сообщение обратно в ветку пользовательского интерфейса через Handler, когда он закончил. В Handler.handleMessage я получаю объект данных (готовый сейчас) из потока и заполняю его в пользовательском интерфейсе. Так что это очень похоже на ваш пример.

После многих проб и ошибок, похоже, я нашел решение. По крайней мере, теперь я могу поворачивать экран в любой момент, до или после завершения потока. Во всех тестах диалоговое окно правильно закрывается, и все поведение соответствует ожидаемому.

То, что я сделал, показано ниже. Цель состоит в том, чтобы заполнить мою модель данных (mDataObject) и затем заполнить ее для пользовательского интерфейса. Следует разрешить поворот экрана в любой момент без сюрпризов.

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 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.
    }
}

Это то, что работает для меня. Я не знаю, является ли это "правильным" методом, разработанным Android, - они утверждают, что это "уничтожить/воссоздать действие во время поворота экрана" на самом деле облегчает ситуацию, поэтому я думаю, что это не должно быть слишком сложно.

Дайте мне знать, если вы видите проблему в моем коде. Как сказано выше, я не знаю, есть ли побочный эффект.

Ответ 5

Первоначальная воспринимаемая проблема заключалась в том, что код не выдержал бы изменения ориентации экрана. По-видимому, это было "решено", когда программа управляла изменением ориентации экрана, вместо того, чтобы позволить инфраструктуре пользовательского интерфейса сделать это (посредством вызова onDestroy)).

Я бы сказал, что если основная проблема заключается в том, что программа не выдержит onDestroy(), то принятое решение является всего лишь обходным решением, которое оставляет программу серьезными другими проблемами и уязвимостями. Помните, что в системе Android определенно указано, что ваша деятельность подвержена риску уничтожения почти в любое время из-за не зависящих от вас обстоятельств. Поэтому ваша деятельность должна быть способна выжить onDestroy() и последующим onCreate() по любой причине, а не только изменение ориентации экрана.

Если вы решите, что сами решаете проблему с настройкой ориентации экрана, чтобы решить проблему OP, вам нужно убедиться, что другие причины onDestroy() не приводят к той же ошибке. Можете ли вы это сделать? Если нет, я бы поставил под вопрос, действительно ли "принятый" ответ действительно очень хороший.

Ответ 6

Мое решение состояло в том, чтобы расширить класс ProgressDialog, чтобы получить собственный MyProgressDialog.
Я переопределил методы show() и dismiss(), чтобы заблокировать ориентацию, прежде чем показывать Dialog и разблокировать ее, когда Dialog отклоняется. Поэтому, когда отображается Dialog и ориентация устройства изменяется, ориентация экрана остается до тех пор, пока не будет вызываться dismiss(), тогда изменение ориентации экрана в соответствии с значениями датчика/ориентацией устройства.

Вот мой код:

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);
}

}

Ответ 7

Я столкнулся с этой же проблемой, и я придумал решение, которое не активировало использование ProgressDialog, и я получаю более быстрые результаты.

Что я сделал, так это создать макет, в котором есть ProgressBar.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ProgressBar
    android:id="@+id/progressImage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    />
</RelativeLayout>

Затем в методе onCreate сделайте следующее

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.progress);
}

Затем выполните длинную задачу в потоке, и когда это закончит, Runnable установит представление содержимого в реальный макет, который вы хотите использовать для этой операции.

Например:

mHandler.post(new Runnable(){

public void run() {
        setContentView(R.layout.my_layout);
    } 
});

Это то, что я сделал, и я обнаружил, что он работает быстрее, чем показывает ProgressDialog, и он менее навязчив и лучше выглядит на мой взгляд.

Однако, если вы хотите использовать ProgressDialog, этот ответ не для вас.

Ответ 8

Я нашел решение, которое я еще не видел в другом месте. Вы можете использовать пользовательский объект приложения, который знает, если у вас есть фоновые задачи, вместо того, чтобы пытаться сделать это в деятельности, которая уничтожается и воссоздается при изменении ориентации. Я написал об этом в здесь.

Ответ 9

Я собираюсь внести свой вклад в решение этой проблемы с ротацией. Это может быть не актуально для OP, поскольку он не использует AsyncTask, но, возможно, другие сочтут это полезным. Это довольно просто, но, похоже, для меня эта работа:

У меня есть активность входа с вложенным AsyncTask классом BackgroundLoginTask.

В моем BackgroundLoginTask я не делаю ничего необычного, кроме как добавить нулевую проверку при вызове ProgressDialog увольнение:

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

Это относится к случаю, когда фоновая задача заканчивается, а Activity не видна, и поэтому диалог выполнения уже отклонен методом onPause().

Далее, в моем родительском классе Activity я создаю глобальные статические дескрипторы для моего класса AsyncTask, а мой ProgressDialog (AsyncTask, будучи вложенным, может обращаться к этим переменным):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

Это служит двум целям: во-первых, он позволяет моему Activity всегда обращаться к объекту AsyncTask даже от нового, послевращенного действия. Во-вторых, он позволяет моему BackgroundLoginTask получить доступ и отклонить ProgressDialog даже после поворота.

Затем я добавляю это к onPause(), заставляя диалог прогресса исчезать, когда наш Activity выходит из переднего плана (предотвращая крушение "увольнения" ):

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

Наконец, в моем методе onResume() есть следующее:

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

Это позволяет Dialog снова появляться после воссоздания Activity.

Вот весь класс:

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();
            } 
        }
        }

    }
}

Я отнюдь не опытный разработчик Android, поэтому не стесняйтесь комментировать.

Ответ 10

Переместите длинную задачу в отдельный класс. Реализуйте его как образец субъекта-наблюдателя. Всякий раз, когда активность создается, регистрируется и при закрытии не регистрируется класс задачи. Класс Task может использовать AsyncTask.

Ответ 11

Хитрость заключается в том, чтобы показать/отменить диалог внутри AsyncTask во время onPreExecute/onPostExecute, как обычно, хотя в случае изменения ориентации создайте/покажите новый экземпляр диалогового окна в действии и передайте его ссылку на задачу.

public class MainActivity extends Activity {
    private Button mButton;
    private MyTask mTask = null;

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

        MyTask task = (MyTask) getLastNonConfigurationInstance();
        if(task != null){
            mTask = task;
            mTask.mContext = this;
            mTask.mDialog = ProgressDialog.show(this, "", "", true);        
        }

        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                mTask = new MyTask(MainActivity.this);
                mTask.execute();
            }
        });
    }


    @Override
    public Object onRetainNonConfigurationInstance() {
        String str = "null";
        if(mTask != null){
            str = mTask.toString();
            mTask.mDialog.dismiss();
        }
        Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
        return mTask;
    }



    private class MyTask extends AsyncTask<Void, Void, Void>{
        private ProgressDialog mDialog;
        private MainActivity mContext;


        public MyTask(MainActivity context){
            super();
            mContext = context;
        }


        protected void onPreExecute() {
            mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
        }

        protected void onPostExecute(Void result) {
            mContext.mTask = null;
            mDialog.dismiss();
        }


        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(5000);
            return null;
        }       
    }
}

Ответ 12

Я сделал это вот так:

    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 {

                }
            }
        }

    }

Вы также можете попробовать и сообщить мне, что это работает для вас или нет.

Ответ 13

Если вы создаете фон Service, который выполняет весь тяжелый подъем (запросы tcp/ответ, unmarshalling), View и Activity может быть уничтожен и повторно создан без утечки окна или потери данных. Это позволяет рекомендовать поведение Android, которое должно уничтожить действие при каждом изменении конфигурации (например, для каждого изменения ориентации).

Это немного сложнее, но это лучший способ для вызова запроса сервера, предварительной и последующей обработки данных и т.д.

Вы даже можете использовать Service для очередности каждого запроса на сервере, поэтому он позволяет легко и эффективно обрабатывать эти вещи.

В руководстве dev есть полная глава на Services.

Ответ 14

У меня есть реализация, которая позволяет уничтожать действие при изменении ориентации экрана, но все же успешно уничтожает диалог в воссозданной деятельности. Я использую ...NonConfigurationInstance для прикрепления фоновой задачи к воссозданной деятельности. Обычная платформа Android поддерживает воссоздание самого диалога, там ничего не меняется.

Я подклассифицировал AsyncTask, добавив поле для операции "owning" и метод для обновления этого владельца.

class MyBackgroundTask extends AsyncTask<...> {
  MyBackgroundTask (Activity a, ...) {
    super();
    this.ownerActivity = a;
  }

  public void attach(Activity a) {
    ownerActivity = a;
  }

  protected void onPostExecute(Integer result) {
    super.onPostExecute(result);
    ownerActivity.dismissDialog(DIALOG_PROGRESS);
  }

  ...
}

В моем классе активности я добавил поле backgroundTask, ссылающееся на "принадлежащую" backgroundtask, и обновляю это поле с помощью onRetainNonConfigurationInstance и getLastNonConfigurationInstance.

class MyActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    ...
    if (getLastNonConfigurationInstance() != null) {
      backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
      backgroundTask.attach(this);
    }
  }

  void startBackgroundTask() {
    backgroundTask = new MyBackgroundTask(this, ...);
    showDialog(DIALOG_PROGRESS);
    backgroundTask.execute(...);
  }

  public Object onRetainNonConfigurationInstance() {
    if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
      return backgroundTask;
    return null;
  }
  ...
}

Предложения по дальнейшему совершенствованию:

  • Очистите ссылку backgroundTask в действии после завершения задачи, чтобы освободить любую память или другие связанные с ней ресурсы.
  • Очистите ссылку ownerActivity в фонотеке до того, как действие будет уничтожено, если оно не будет воссоздано немедленно.
  • Создайте интерфейс и/или коллекцию backgroundTask, чтобы можно было запускать разные типы задач из одной и той же активности владения.

Ответ 15

Если вы поддерживаете два макета, весь поток пользовательского интерфейса должен быть завершен.

Если вы используете AsynTask, вы можете легко вызвать метод .cancel() внутри метода onDestroy() текущей активности.

@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();
}

Для AsyncTask читайте больше в разделе "Отмена задачи" в здесь.

Update: Добавлено условие проверки статуса, поскольку его можно отменить только в том случае, если он находится в рабочем состоянии. Также обратите внимание, что AsyncTask может выполняться только один раз.

Ответ 16

Пытался реализовать решение jfelectron, потому что это "твердое решение для этих проблем, которое соответствует" Android-способу "вещей", но потребовалось некоторое время, чтобы посмотреть и собрать все упомянутые элементы. Завершилось это немного по-другому, и я думаю, что более элегантное решение, размещенное здесь целиком.

Использует IntentService, запущенную из действия для выполнения задачи, выполняемой в течение длительного времени, в отдельном потоке. Служба возвращает обратно липкие трансляции Intents в действие, которое обновляет диалог. В Activity используются showDialog(), onCreateDialog() и onPrepareDialog(), чтобы исключить необходимость наличия постоянных данных, переданных в объекте приложения или пакете savedInstanceState. Это должно работать независимо от того, как прервано ваше приложение.

Класс действия:

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:

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);
    }
}}

Записи манифеста:

перед разделом приложения:

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

внутри раздела приложения

service android:name=".MyService"

Ответ 17

Это мое предлагаемое решение:

  • Переместите AsyncTask или Thread в сохраненный фрагмент, как описано здесь. Я считаю, что хорошей практикой является перенос всех сетевых вызовов на фрагменты. Если вы уже используете фрагменты, один из них может быть ответственным за вызовы. В противном случае вы можете создать фрагмент только для выполнения запроса, как предлагает связанная статья.
  • Фрагмент будет использовать интерфейс прослушивателя, чтобы сигнализировать о завершении/сбое задачи. Вам не нужно беспокоиться об изменениях ориентации там. Фрагмент всегда будет иметь правильную ссылку на текущую активность, а диалог прогресса можно безопасно возобновить.
  • Сделайте свой диалог прогресса членом вашего класса. На самом деле вы должны сделать это для всех диалогов. В методе onPause вы должны отклонить их, иначе вы будете просачивать окно при изменении конфигурации. Состояние занятости должно храниться в фрагменте. Когда фрагмент прикреплен к действию, вы можете снова открыть диалог прогресса, если вызов все еще запущен. Для этой цели можно добавить метод void showProgressDialog() для интерфейса прослушивателя фрагмента активности.

Ответ 18

Я очень разбираюсь в android, и я пробовал это, и это сработало.

public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> {
        ProgressDialog progressDialog = new ProgressDialog(Login.this);
        int ranSucess=0;
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            progressDialog.setTitle("");    
            progressDialog.isIndeterminate();
            progressDialog.setCancelable(false);
            progressDialog.show();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            return null;
        }
        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            progressDialog.dismiss();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
        }
}

Ответ 19

Я столкнулся с такой же ситуацией. Я сделал только один экземпляр для моего диалога прогресса во всем приложении.

Сначала я создал класс DialogSingleton, чтобы получить только один экземпляр (шаблон Singleton)

public class DialogSingleton
{
    private static Dialog dialog;

    private static final Object mLock = new Object();
    private static DialogSingleton instance;

    private DialogSingleton()
    {

    }

    public static DialogSingleton GetInstance()
    {
        synchronized (mLock)
        {
            if(instance == null)
            {
                instance = new DialogSingleton();
            }

            return instance;
        }
    }

    public void DialogShow(Context context, String title)
    {
        if(!((Activity)context).isFinishing())
        {
            dialog = new ProgressDialog(context, 2);

            dialog.setCanceledOnTouchOutside(false);

            dialog.setTitle(title);

            dialog.show();
        }
    }

    public void DialogDismiss(Context context)
    {
        if(!((Activity)context).isFinishing() && dialog.isShowing())
        {
            dialog.dismiss();
        }
    }
}

Как я показываю в этом классе, у меня есть диалог прогресса как атрибут. Каждый раз, когда мне нужно показывать диалог прогресса, я получаю уникальный экземпляр и создаю новый ProgressDialog.

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

Когда я закончил фоновое задание, я снова вызываю уникальный экземпляр и отклоняю его диалог.

DialogSingleton.GetInstance().DialogDismiss(this);

Я сохраняю статус фоновой задачи в своих общих настройках. Когда я поворачиваю экран, я спрашиваю, есть ли у меня задача для этой операции: (onCreate)

if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString()))
{
    DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!");
} // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running).

Когда я запускаю фоновое задание:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

Когда я закончу выполнение фоновой задачи:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");

DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);

Надеюсь, это поможет.

Ответ 20

Это очень старый вопрос, который почему-то появился на боковой панели.

Если фоновая задача должна выживать только в том случае, если действие находится на переднем плане, "новым" решением является размещение фонового потока (или, предпочтительно, AsyncTask) в сохраненном фрагменте, как описано в этом руководстве разработчика и множество Q & As.

Сохраненный фрагмент сохраняется, если активность уничтожается для изменения конфигурации, но не тогда, когда действие уничтожается в фоновом или обратном стеках. Поэтому фоновая задача все равно должна быть прервана, если isChangingConfigurations() false в onPause().

Ответ 21

Я пробовал ВСЕ. Проведенные эксперименты. Я не хотел блокировать активность от вращения. Мой сценарий:

  • Диалог прогресса, показывающий динамическую информацию для пользователя. Например: "Подключение к серверу...", "Загрузка данных..." и т.д.
  • Нить делает тяжелый материал и обновляет диалог
  • Обновление пользовательского интерфейса с результатами в конце.

Проблема заключалась в том, что при повороте экрана каждое решение в книге не удавалось. Даже с классом AsyncTask, который является правильным способом Android для решения этих ситуаций. При вращении экрана текущий Контекст, с которым работает начальный поток, исчез, и это испортится с отображаемым диалогом. Проблема заключалась всегда в диалоге, независимо от того, сколько трюков я добавил в код (передавая новые контексты для запуска потоков, сохраняя состояния потоков через вращения и т.д.). Сложность кода в конце была всегда огромной, и всегда было что-то, что могло пойти не так.

Единственным решением, которое сработало для меня, был трюк Activity/Dialog. Это просто и гениально, и все это доказательство вращения:

  • Вместо создания диалогового окна и попросите его показать, создайте действие, которое было установлено в манифесте с помощью android: theme = "@android: style/Theme.Dialog". Таким образом, он просто выглядит как диалог.

  • Заменить showDialog (DIALOG_ID) с помощью startActivityForResult (yourActivityDialog, yourCode);

  • Использовать onActivityResult в вызывающей активности для получения результатов из исполняемого потока (даже ошибок) и обновления пользовательского интерфейса.

  • В вашем "ActivityDialog" используйте потоки или AsyncTask для выполнения длинных задач и onRetainNonConfigurationInstance для сохранения состояния "диалога" при вращении экрана.

Это быстро и прекрасно работает. Я все еще использую диалоги для других задач и AsyncTask для чего-то, что не требует постоянного диалога на экране. Но при таком сценарии я всегда использую шаблон Activity/Dialog.

И я не пробовал, но даже возможно заблокировать действие Activity/Dialog при запуске потока, ускоряя работу, позволяя вращаться вызывающей Activity.

Ответ 22

В наши дни существует гораздо более четкий способ решения этих проблем. Типичный подход:

1. Убедитесь, что ваши данные правильно отделены от пользовательского интерфейса:

Все, что является фоновым процессом, должно быть в сохраненном Fragment (установите это с помощью Fragment.setRetainInstance(). Это станет вашим "постоянным хранилищем данных", в котором сохраняются все данные, которые вы хотите сохранить. После события изменения ориентации, этот Fragment по-прежнему будет доступен в исходном состоянии через вызов FragmentManager.findFragmentByTag() (при его создании вы должны указать ему тег не идентификатор, поскольку он не привязан к View).

См. "Обработка изменений времени выполнения" , разработанное руководство для получения информации об этом правильно и почему это лучший вариант.

2. Убедитесь, что вы правильно и безопасно взаимодействуете между фоновыми процессами и вашим пользовательским интерфейсом:

Вы должны перевернуть ваш процесс связывания. В настоящий момент ваш фоновый процесс присоединяется к View - вместо этого ваш View должен быть привязан к фоновому процессу. Это имеет смысл? Действие View зависит от фонового процесса, тогда как фоновый процесс не зависит от View. Это означает изменение ссылки на стандартный интерфейс Listener. Скажите, что ваш процесс (независимо от его класса - будь то AsyncTask, Runnable или что-то другое) определяет OnProcessFinishedListener, когда процесс выполняется, он должен вызывать этот прослушиватель, если он существует.

Этот ответ является хорошим кратким описанием того, как делать пользовательские прослушиватели.

3. Свяжите свой пользовательский интерфейс в процессе обработки данных при создании пользовательского интерфейса (включая изменения ориентации):

Теперь вам нужно беспокоиться о взаимодействии фоновой задачи с любой текущей структурой View. Если вы обрабатываете свои изменения ориентации правильно (а не configChanges хакеры всегда рекомендуют), то ваш Dialog будет воссоздан системой. Это важно, это означает, что при изменении ориентации все ваши методы жизненного цикла Dialog вызываются. Поэтому в любом из этих методов (onCreateDialog обычно является хорошим местом), вы можете сделать вызов следующим образом:

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

Смотрите жизненный цикл фрагмента для определения того, где настройка слушателя лучше всего подходит для вашей индивидуальной реализации.

Это общий подход к обеспечению надежного и полного решения общей проблемы, заданной в этом вопросе. Вероятно, в этом ответе есть несколько незначительных фрагментов, в зависимости от вашего индивидуального сценария, но это, как правило, самый правильный подход для правильной обработки событий изменения ориентации.

Ответ 23

Я нашел и упростил решение для обработки потоков при изменении ориентации. Вы можете просто сохранить статическую ссылку на свою активность/фрагмент и проверить, не имеет ли значение null перед действием на ui. Я также предлагаю использовать try catch:

 public class DashListFragment extends Fragment {
     private static DashListFragment ACTIVE_INSTANCE;

     @Override
     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ACTIVE_INSTANCE = this;

        new Handler().postDelayed(new Runnable() {
            public void run() {
                try {
                        if (ACTIVE_INSTANCE != null) {
                            setAdapter(); // this method do something on ui or use context
                        }
                }
                catch (Exception e) {}


            }
        }, 1500l);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        ACTIVE_INSTANCE = null;
    }


}

Ответ 24

Если вы боретесь с обнаружением событий изменения ориентации диалога НЕЗАВИСИМО ОТ ОТНОШЕНИЯ С ДЕЯТЕЛЬНОСТЬЮ, этот метод работает очень хорошо. Я использую это, потому что у меня есть свой собственный класс диалога, который может отображаться в нескольких разных действиях, поэтому я не всегда знаю, какую активность он показывает. С помощью этого метода вам не нужно менять AndroidManifest, беспокоиться о ссылках на действия, и вам не нужен специальный диалог (как и у меня). Однако вам нужно создать настраиваемый контент, чтобы вы могли определять изменения ориентации с помощью этого конкретного вида. Вот мой пример:

Настройка

public class MyContentView extends View{
    public MyContentView(Context context){
        super(context);
    }

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

        //DO SOMETHING HERE!! :D
    }
}

Реализация 1 - Диалог

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

Реализация 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();

Реализация 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();

Ответ 25

Это мое решение, когда я столкнулся с этим: ProgressDialog не является потомком Fragment, поэтому мой пользовательский класс "ProgressDialogFragment" может вместо этого расширить DialogFragment, чтобы сохранить диалоговое окно, отображаемое для изменений конфигурации.

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();
        }
    }
}

Задача состояла в том, чтобы сохранить заголовок диалога & сообщение во время экрана вращение, поскольку они возвращаются к пустой строке по умолчанию, хотя диалоговое окно по-прежнему отображается

Есть 2 подхода для решения этой проблемы:

Первый подход: Выполните действие, которое использует диалоговое окно для сохранения состояния во время изменения конфигурации в файле манифеста:

android:configChanges="orientation|screenSize|keyboardHidden"

Этот подход не является предпочтительным для Google.

Второй подход: в методе активности onCreate() вам нужно сохранить свой DialogFragment, заново перестроив ProgressDialogFragment с заголовком & сообщение следующее, если savedInstanceState не равно нулю:

@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");
      }
  }
}

Ответ 26

Кажется, слишком "быстро и грязно", чтобы быть правдой, поэтому, пожалуйста, укажите недостатки, но то, что я нашел, было...

В методе onPostExecute моего AsyncTask я просто завернул ".dismiss" для диалога прогресса в блоке try/catch (с пустым уловом), а затем просто проигнорировал исключение, которое было поднято. Кажется неправильным делать, но появляется, что нет никаких негативных последствий (по крайней мере, для того, что я делаю впоследствии, для запуска другого действия, проходящего в результате моего длинного запроса в качестве дополнительного)

Ответ 27

Самое простое и гибкое решение - использовать AsyncTask со статической ссылкой на ProgressBar. Это обеспечивает инкапсулированное и, следовательно, многоразовое решение проблем с изменением ориентации. Это решение послужило мне отличным решением для различных асинхронных задач, включая загрузку через Интернет, связь с Services и сканирование файловой системы. Решение было хорошо протестировано на нескольких версиях Android и моделях телефонов. Полная демо-версия здесь с особым интересом в DownloadFile.java

Я представляю следующее в качестве примера концепции

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();
        }
    }
}

Использование в Android-активности прост

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();
    }
}

Ответ 28

Когда вы меняете ориентацию, Android убивает это действие и создает новое действие. Я предлагаю использовать модификацию с Rx Java. который обрабатывает сбой автоматически.

Используйте этот метод при модификации вызова.

.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())