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

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

Как я могу обрабатывать ориентацию экрана грациозно измениться?

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

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

стопка:

E/WindowManager(  244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 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.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, но это просто предотвращает немедленный сбой. Фоновый поток все еще продолжается, и пользовательский интерфейс находится в частично нарисованном состоянии. Нужно убить все приложение, прежде чем он начнет работать снова.

26 ответов


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

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


Edit: инженеры Google не рекомендуют этот подход, как описано Дайанн Хакборн (a.к. a. hackbod) в сообщение StackOverflow. Проверьте этот блог для получения дополнительной информации.


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

android:configChanges="orientation|screenSize"

похоже

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

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

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


Я придумал твердое решение для этих проблем, которое соответствует "Android-способу" вещей. У меня есть все мои длительные операции с использованием шаблона IntentService.
То есть мои действия транслируют намерения, IntentService выполняет работу, сохраняет данные в БД, а затем транслирует липкие намерения.
Липкая часть важна, так что даже если активность была приостановлена во время в течение времени после того, как пользователь инициировал работу и пропускает вещание в реальном времени от ItentService мы все еще можем ответить и забрать данные из вызывающего действия.
ProgressDialogs может работать в этом шаблоне довольно хорошо с onSaveInstanceSate().
В принципе, вам нужно сохранить флаг, который у вас есть диалог выполнения, запущенный в пакете сохраненных экземпляров. Не сохраняйте объект диалога выполнения, так как это приведет к утечке всего действия.
Чтобы иметь постоянный дескриптор диалога выполнения, я сохраняю его как слабую ссылку в объекте приложения.
На изменение ориентации или что-либо еще, что заставляет действие приостановить (телефонный звонок, пользователь попадает домой и т. д.), а затем возобновить, я закрываю старый диалог и воссоздаю новый диалог во вновь созданной активности.
Для неопределенных диалогов прогресса это легко. Для стиля индикатора выполнения вы должны поместить последний известный прогресс в пакет и любую информацию, которую вы используете локально в действии, чтобы отслеживать прогресс.
При восстановлении прогресса вы будете использовать эту информацию для повторно создайте индикатор выполнения в том же состоянии, что и раньше, а затем обновите его на основе текущего состояния.
Итак, подводя итог, помещая долгосрочные задачи в IntentService в сочетании с juidcious использованием onSaveInstanceState() позволяет эффективно отслеживать диалоги и восстанавливать события жизненного цикла активности. Соответствующие биты кода деятельности приведены ниже. Вам также понадобится логика в вашем BroadcastReceiver для обработки липких намерений соответствующим образом, но это выходит за рамки этого.

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



        }

    }

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

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

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

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;

    public MyHandler(..., Handler h) {...; mHandler = h;} // constructor with handler param
    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 - они утверждают, что это" уничтожить/воссоздать активность во время вращения экрана " на самом деле делает все легче, так что я думаю, это не должно быть слишком сложно.

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


моим решением было расширить 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);
}

}

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

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

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


я столкнулся с этой же проблемой, и я придумал решение, которое не вызывало использование 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, то этот ответ не для вас.


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


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

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

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

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

это для обработки случай, когда фоновая задача завершается, когда Activity не отображается и, следовательно, диалог прогресса уже уволен onPause() метод.

далее, в моем родителе Activity класс, я создаю глобальные статические дескрипторы для моего AsyncTask класс и моя ProgressDialog (the 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, поэтому не стесняйтесь комментировать.


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


трюк состоит в том, чтобы показать/закрыть диалог в 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;
        }       
    }
}

Я сделал так:

    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 {

                }
            }
        }

    }

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


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

это немного сложнее, но это лучший способ для вызова запроса сервера, данных pre / post-processing, etc.

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

руководство dev имеет полный глава Services.


у меня есть реализация, которая позволяет уничтожить действие при изменении ориентации экрана, но все равно успешно уничтожает диалог в воссозданной активности. Я использую ...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 перед уничтожением действия, если оно не будет воссоздано немедленно.
  • создать BackgroundTask интерфейс и / или коллекция, чтобы различные типы задач запускались из одного и того же действия владельца.

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

Если вы используете 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, подробнее в разделе "отмена задачи" в здесь.

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


пытались реализовать jfelectronрешение, потому что это"rock-solid решение этих вопросов, которое соответствует "Android путь" вещей

использует IntentService, запущенный из действия для выполнения длительной задачи в отдельном потоке. Услуга возвращает липкие широковещательные намерения к действию, которое обновляет диалог. Действие использует 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"


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

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

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

  1. диалог выполнения, показывающий динамическую информацию пользователю. Е. Г.: "Подключение к серверу...", "Загрузить данные...", п.
  2. поток делает тяжелый материал и обновляет диалог
  3. обновление пользовательского интерфейса с результатами в конце.

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

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

  1. вместо того, чтобы создавать диалог и просить показать его, создайте действие, которое было установлено в манифесте с android:theme="@android:style/Theme.Диалог." Так что это выглядит как диалог.

  2. заменить showDialog (DIALOG_ID) на startActivityForResult (yourActivityDialog, yourCode);

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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


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

во-первых, я создал класс 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);

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


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

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

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


Я свежее в 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);
        }
}

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

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


самое простое и гибкое решение-использовать AsyncTask со статической ссылкой на ProgressBar. Это обеспечивает инкапсулированное и, следовательно, многоразовое решение проблем изменения ориентации. Это решение хорошо послужило мне для различных асинхронных задач, включая загрузку в интернет, общение с услуги, и сканирование файловой системы. Решение было хорошо протестировано на нескольких версиях 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();
    }
}

Я нашел и простое решение для обработки потоков при изменении ориентации. Вы можете просто сохранить статическую ссылку на свою активность / фрагмент и проверить, если его null, прежде чем действовать на пользовательском интерфейсе. Я предлагаю также использовать 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;
    }


}

если вы боретесь с обнаружением событий изменения ориентации диалога НЕЗАВИСИМО ОТ ССЫЛКИ НА АКТИВНОСТЬ этот метод работает возбуждающе хорошо. Я использую это, потому что у меня есть собственный класс dialog, который может быть показан в нескольких разных действиях, поэтому я не всегда знаю, в каком действии он отображается. С помощью этого метода вам не нужно изменять 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.Строитель!--9-->
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();