Як впоратися зі зміною орієнтації екрана, коли активовано діалог прогресу та фоновий потік?


524

Моя програма виконує деяку мережеву діяльність у фоновому потоці. Перш ніж почати, в ньому з'являється діалогове вікно прогресу. Діалогове вікно закривається на обробник. Це все добре працює, за винятком випадків, коли орієнтація екрана змінюється, коли діалогове вікно працює (і фонова нитка йде). У цей момент програма або виходить з ладу, або заходить у глухий кут, або потрапляє у дивну стадію, коли додаток взагалі не працює, поки всі потоки не будуть вбиті.

Як я можу витончено керувати зміною орієнтації екрана?

Зразок коду нижче відповідає приблизно тому, що робить моя реальна програма:

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$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
Враховуючи отримані відповіді, ви повинні змінити прийняту відповідь на користь кращих, чи не так?
rds

Дивіться також попереднє запитання stackoverflow.com/questions/456211/…
rds

3
Все, отримав дійсно чудове пояснення та можливі рішення цієї проблеми. Перейдіть за посиланням http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ Lemme знаю, чи допомогло це.
arcamax

2
У цій публікації блогу є досить повне пояснення щодо збереження асинхронних фонових завдань для орієнтацій екрана . Перевір!
Адріан Монк

Просто встановіть android: configChanges = "орієнтація | screenSize" на активність у маніфесті. Він зупинить андроїд, щоб відтворити свою діяльність
Jawad Zeb

Відповіді:


155

Під час перемикання орієнтацій Android створить новий вид. Ви, ймовірно, отримуєте збої, тому що ваш фоновий потік намагається змінити стан старого. (Можливо, також виникнуть проблеми, оскільки фонова нитка не на нитці інтерфейсу користувача)

Я б запропонував зробити цей mHandler мінливим і оновити його, коли орієнтація зміниться.


14
Можливо, ви визначили причину аварії. Я позбувся аварії, але досі не зрозумів, як відновити інтерфейс користувача до стану, в якому він був, перш ніж орієнтація змінилася. Але ваша відповідь зворушила мене вперед, тому нагородивши її як відповідь.
Heikki Toivonen

4
Ви повинні отримати старт у своїй діяльності, коли орієнтація зміниться. По суті, вам доведеться переналаштувати подання за допомогою старих даних. Тож я б запропонував запитати числовий статус уптатів з панелі прогресу та відновити новий вигляд, коли ви отримаєте цей новий "onStart", я не можу пригадати заздалегідь, якщо ви також отримаєте нову діяльність, але деяке полювання через документацію повинно допомогти.
хасеман

6
Погравши з цим нещодавно, я можу передати, що ви отримуєте нову активність, коли додаток змінює орієнтацію. (Ви також отримаєте новий вигляд) Якщо ви спробуєте оновити старий вигляд, ви отримаєте виняток, оскільки старий погляд має недійсний контекст програми (ваша стара активність). замість вказівника на саму діяльність.
хасеман

1
Чи може хтось пояснити використання / користь непостійних у цьому контексті
Jawad Zeb

2
@Nepster Так, мені теж було цікаво про це. Було б чудово, якби хтось пояснив про непостійне.
RestInPeace

261

Редагувати: Інженери Google не рекомендують такий підхід, як описав Діанна Хакборн (він же хакбод ) у цій публікації про StackOverflow . Перегляньте це повідомлення в блозі для отримання додаткової інформації.


Ви повинні додати це до декларації про діяльність у маніфесті:

android:configChanges="orientation|screenSize"

так це схоже

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

Справа в тому, що система знищує активність, коли відбувається зміна конфігурації. Див. Конфігураційні зміни .

Таким чином, якщо помістити це у файл конфігурації, ви уникаєте знищити вашу діяльність. Натомість він викликає onConfigurationChanged(Configuration)метод.


21
Це, безумовно, найкраще рішення; оскільки він просто обертає макет (поведінка, яку ви очікуєте в першу чергу). Просто не забудьте поставити android: configChanges = "орієнтація | клавіатураHidden" (через телефони, які мають ландшафтну клавіатуру)
nikib3ro

24
Це, мабуть, така поведінка, яку я очікую. Однак документація вказує на те, що діяльність знищується "тому, що будь-який ресурс програми, включаючи файли макета, може змінюватися на основі будь-якого значення конфігурації. Таким чином, єдиний безпечний спосіб впоратися зі зміною конфігурації - це відновлення всіх ресурсів". Крім того orientation, є ще багато причин для зміни конфігурації: keyboardHidden(я вже відредагував відповідь на вікі), uiMode(наприклад, вхід або вихід з режиму автомобіля; зміна нічного режиму) тощо. Зараз мені цікаво, чим це насправді хороша відповідь.
rds

116
Це не прийнятне рішення. Це просто маскує справжнє питання.
rf43

18
Працює, але не рекомендується Google.
Ед Бернетт

21
Будь ласка, не дотримуйтесь цього підходу тут. DDosAttack абсолютно прав. Уявіть, що ви створюєте діалог прогресу для завантаження чи чогось іншого, що потребує тривалого часу. Як користувач, ви не будете зупинятися на цій діяльності і не дивитесь на неї. Ви перейдете на головний екран або на інший додаток, наприклад, гра чи телефонний дзвінок, або щось інше, що голодне, що згодом знищить вашу активність. А що тоді? Ви стикаєтеся з тим самим старим питанням, яке НЕ вирішується цією акуратною маленькою хитрістю. Діяльність буде відтворена знову, коли користувач повернеться.
tiguchi

68

Я придумав настільки рішуче рішення цих питань, яке відповідає "Android Way" речей. У всіх своїх довготривалих операціях я використовую шаблон IntentService.

Тобто, мої дії транслюють наміри, IntentService виконує роботу, зберігає дані в БД, а потім передає липкі наміри. Важлива клейка частина, яка навіть якщо Активність була призупинена протягом часу після того, як користувач ініціював роботу та пропускає трансляцію в реальному часі з IntentService, ми все одно можемо відповісти та забрати дані з Діяльності виклику. ProgressDialogs може досить добре працювати з цим малюнком onSaveInstanceState().

По суті, вам потрібно зберегти прапор, який у вас є діалоговим вікном ходу, що працює в пакеті збереженого примірника. Не зберігайте об'єкт діалогу прогресу, оскільки це витіче всю активність. Щоб мати стійку ручку до діалогового вікна прогресу, я зберігаю це як слабке посилання в об’єкт програми. Після зміни орієнтації чи будь-чого іншого, що спричиняє активність паузи (телефонний дзвінок, користувач звертається додому тощо), а потім поновлююсь, я відхиляю старе діалогове вікно і відтворюю новий діалог у новоствореній діяльності.

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

Отже, підсумовуючи, введення тривалих завдань у IntentService, поєднане з розумним використанням, onSaveInstanceState()дозволяє ефективно відслідковувати діалоги та відновлювати всі події життєвого циклу діяльності. Відповідні біти коду діяльності наведені нижче. Також вам знадобиться логіка у вашому 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));
    }
}

здається приємним рішенням
Дерекий

"У всіх своїх тривалих операціях я використовую шаблон IntentService." Це не ідеальне рішення, тому що це як вистрілити з гармати в горобців і багато кодового коду, більше ви можете подивитися youtube.com/watch?v=NJsq0TU0qeg
Каміль Неканович

28

Я зустрів ту саму проблему. Моя діяльність потребує аналізу деяких даних з 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's already ready.
        return mDataObject;
    }

    // use Activity.showDialog instead of ProgressDialog.show
    // so the dialog can be automatically managed across config change
    @Override
    protected Dialog onCreateDialog(int id) {
        // show progress dialog here
    }

    // inner class of MyActivity
    private class MyHandler extends Handler {
        public void handleMessage(msg) {
            mDataObject = mParserThread.getDataObject();
            populateUI();
            dismissDialog(DIALOG_PROGRESS);
        }
    }
}

class MyThread extends Thread {
    Handler mHandler;
    MyDataObject mDataObject;

    // constructor with handler param
    public MyHandler(..., Handler h) {
        ...
        mHandler = h;
    }

    public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
    public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller

    public void run() {
        mDataObject = new MyDataObject();
        // do the lengthy task to fill mDataObject with data
        lengthyTask(mDataObject);
        // done. notify activity
        mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
    }
}

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

Повідомте мене, якщо ви бачите проблему в моєму коді. Як було сказано вище, я не знаю, чи є побічні ефекти.


1
Дуже дякую! Підказка onRetainNonConfigurationInstance()і getLastNonConfigurationInstance()допомогла мені вирішити свою проблему. Пальці вгору!
sven

15

Первісна сприйнята проблема полягала в тому, що код не переживе зміни орієнтації екрана. Мабуть, це було вирішено через те, що програма сама керувала зміною орієнтації екрану, замість того, щоб дозволяти рамкам інтерфейсу робити це (за допомогою виклику onDestroy)).

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

Якщо ви збираєтесь самостійно приймати зміни орієнтації екрана, щоб вирішити проблему ОП, вам потрібно переконатися, що інші причини OnDestroy () не призводять до тієї ж помилки. Чи можете ви це зробити? Якщо ні, то я б запитав, чи справді "прийнята" відповідь дуже хороша.


14

Моє рішення було розширити 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);
}

}

8

Я зіткнувся з цією самою проблемою, і я придумав рішення, яке не вимагає використання 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, то ця відповідь не для вас.


Це рішення є елегантним у простому випадку використання, але має недоліки. Потрібно відновити повний перегляд вмісту. setContentView(R.layout.my_layout);недостатня; вам потрібно встановити всіх слухачів, скинути дані тощо
rds

@rds ви праві. Це дійсно лише рішення простого випадку, або якщо вам потрібно здійснити важкий підйом у вашому методі onCreate перед тим, як показувати свій погляд.
Пзанно

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

7

Я знайшов рішення для цього, якого я ще не бачив в іншому місці. Ви можете використовувати спеціальний об’єкт додатка, який знає, чи є у вас фонові завдання, замість того, щоб намагатися робити це в тій діяльності, яка знищується і відтворюється при зміні орієнтації. Я про це блогував тут .


1
Створення користувацького Applicationзазвичай використовується для підтримки глобального стану додатків. Я не кажу, що це не працює, але це здається занадто складним. З документа "Зазвичай додаток у підклас не потрібно." Я багато в чому віддаю перевагу відповіді sonxurxo.
rds

7

Я збираюся внести свій підхід до вирішення цього питання обертання. Це може не стосуватися ОП, оскільки він не використовує 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, тому сміливо коментуйте.


1
Цікаво! Особливо для тих, хто використовує AsyncTask. Ви просто спробували своє рішення, і, здається, в основному це спрацює. Є одна проблема: здається, що ProgressDialog закінчується трохи рано після обертання, поки ProgressDialog все ще активний. Я збираюся пограти, щоб побачити, що саме відбувається і як виправити це. Але я більше не отримую цих збоїв!
Скотт Біггс

1
Знайшли виправлення. Схоже, проблема тут - статичний ProgressDialog. Коли обертання перериває ProgressDialog, він іноді отримує його метод .dismiss (), викликаний після його перезавантаження в новій діяльності. Створюючи ProgressDialog, створений з кожною діяльністю, ми гарантуємо, що цей новий ProgressDialog не буде вбито разом зі старою активністю. Я також переконався, що ProgressDialog встановлений на нуль кожного разу, коли його викидають (для сприяння збору сміття). Тож у нас тут є рішення! Привіт тим, хто використовує AsyncTask!
Скотт Біггс

4

Перенесіть довге завдання на окремий клас. Реалізовуйте це як шаблон суб'єкта-спостерігача. Щоразу, коли діяльність створена, реєструйтесь і під час закриття відреєстрації з класом завдань. Клас завдань може використовувати AsyncTask.


1
Я не бачу, як це могло б допомогти. Чи можете ви пояснити більш детально, як це запобігає проблемам, які я бачу.
Heikki Toivonen

1
Як сказав Хасеман, він перешкоджає бекенду отримати доступ до елементів інтерфейсу, і ми можемо відокремити інтерфейс користувача від бекенда, бекенд працює в окремій потоці, і він продовжує працювати навіть після того, як екран буде переорієнтований і Зареєструватися-UnRegister із завданням Backend для оновлення статусу. . Справжній приклад, який я вирішив, використовуючи це, - це те, що у мене є задача "Завантажити", я перемістив її до окремої нитки, кожного разу, коли створений потік, я реєструюсь-скасовувати з ним.
Vinay

Гаразд, я переглядаю це питання, і не думаю, що все ще повністю розумію цю відповідь. Припустимо, у нас основною діяльністю є запуск AsyncTask, щоб зробити тривалу мережну операцію, яку ми не хочемо переривати під час зміни орієнтації екрана. Я не бачу, як нова активність може надіслати повідомлення AsyncTask, розпочате старою діяльністю. Чи можете ви навести приклад коду?
Хейкі Тойвонен

@Heikki, це моя реалізація нижче того, що ти маєш на увазі?
beetstra

4

Трюк полягає в тому, щоб показати / відхилити діалогове вікно в 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;
        }       
    }
}

4

Я зробив це так:

    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 {

                }
            }
        }

    }

Ви також можете спробувати, і дайте мені знати, працює він для вас чи ні


Код onDestroy може взагалі не виконуватися зі сторінки розробника : "Зверніть увагу на стовпець" Killable "у наведеній вище таблиці - для тих методів, які позначені як підлягають забороні, після цього методу повертається процес, що займає діяльність, може бути знищений система в будь-який час, не
виконуючи

Я твердо вірю, що "onRetainNonConfigurationInstance ()" - метод, який застосовується для таких випадків ... nyc робота
Нітін Бансал,

Я зіткнувся з подібною проблемою, як і Сачін Гурнані, використовуючи статичну декларацію, щоб вирішити свою проблему. stackoverflow.com/questions/12058774/…
Стівен Дю

2

Якщо ви створюєте фон, Serviceякий виконує всі важкі підйоми (tcp запити / відповіді, розблокування), Viewі Activityїх можна знищити та створити заново без протікання вікна або втрати даних. Це дозволяє рекомендувати поведінку Android, яка полягає у знищенні активності при кожній зміні конфігурації (наприклад, для кожної зміни орієнтації).

Це трохи складніше, але це найкращий спосіб викликати запит сервера, попередню / після обробки даних тощо.

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

Посібник із розробників містить повну главуServices .


A Service- це більше роботи, ніж, AsyncTaskале може бути кращим підходом у деяких ситуаціях. Це не обов’язково краще, чи не так? Якщо говорити, я не розумію, як це вирішує проблему того, ProgressDialogщо витікає з головного Activity. Де ви створюєте інстанцію ProgressDialog? Куди ти його відкидаєш?
rds

2

У мене є реалізація, яка дозволяє знищити активність при зміні орієнтації екрана, але все-таки зруйнує діалог у відтвореній діяльності. Я використовую ...NonConfigurationInstanceдля приєднання фонового завдання до відтвореної діяльності. Звичайна система Android обробляє відтворення самого діалогу, нічого там не змінюється.

Я підкласифікував AsyncTask, додавши поле для 'володіння' активністю та метод оновлення цього власника.

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посилається на "власність" фонового завдання, і оновлюю це поле за допомогою 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інтерфейс та / або колекцію, щоб дозволити виконувати різні типи завдань з однієї і тієї ж діяльності володіння.

2

Якщо ви підтримуєте два макети, усі потоки інтерфейсу повинні бути завершені.

Якщо ви використовуєте 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 може бути виконаний лише один раз.


2

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

Використовує функцію IntentService, запущену з активності для виконання тривалого завдання на окремому потоці. Служба повертає клейкі широкомовні наміри до дії, яка оновлює діалогове вікно. Діяльність використовує showDialog (), onCreateDialog () та onPrepareDialog () для усунення необхідності передачі стійких даних в об’єкт програми або пакет збереженого старту. Це має працювати незалежно від того, як буде перервано вашу заявку.

Клас діяльності:

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

Записи файлів Manifest:

перед розділом програми:

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

всередині розділу додатків

service android:name=".MyService"

2

Це моє запропоноване рішення:

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

Ідеальне рішення, але не розумію, чому ця відповідь затьмарюється від інших !!!
blackkara

2

Я зіткнувся з тією ж ситуацією. Що я зробив, це отримати лише один екземпляр для мого діалогу прогресу у всій програмі.

По-перше, я створив клас 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);

Я сподіваюся, що це допомагає.


2

Це дуже старе питання, яке виникло на бічній панелі чомусь.

Якщо фонове завдання потрібно лише пережити, поки активність на першому плані, "новим" рішенням є розміщення фонової нитки (або, бажано, AsyncTask) у збереженому фрагменті , як описано в цьому посібнику розробника та численних запитаннях і відповідях .

Збережений фрагмент зберігається, якщо активність знищена для зміни конфігурації, але не тоді, коли активність знищена у фоновому режимі чи задній стек. Тому фонове завдання все-таки слід перервати, якщо isChangingConfigurations()в ньому помилково onPause().


2

Я свіжий андроїд, і я спробував це, і це спрацювало.

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

1

Я спробував ВСЕ. Провели експерименти. Я не хотів блокувати акти обертання. Мій сценарій:

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

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

Єдине рішення, яке працювало для мене, - фокус "Діяльність / Діалог". Це просто і геніально, і це все підтвердження обертання:

  1. Замість того, щоб створити діалог і попросити його показати, створіть активність, встановлену в маніфесті за допомогою android: theme = "@ android: style / Theme.Dialog". Отже, це просто виглядає як діалог.

  2. Замініть showDialog (DIALOG_ID) на startActivityForResult (yourActivityDialog, yourCode);

  3. Використовуйте onActivityResult у виклику Activity, щоб отримати результати з потоку виконання (навіть помилки) та оновити інтерфейс користувача.

  4. У своєму "ActivityDialog" використовуйте потоки або AsyncTask для виконання довгих завдань, а OnRetainNonConfigurationInstance для збереження "діалогового" стану під час обертання екрана.

Це швидко і працює чудово. Я все ще використовую діалоги для інших завдань і AsyncTask для чогось, що не потребує постійного діалогу на екрані. Але при такому сценарії я завжди переходжу до схеми "Діяльність / Діалог".

І я не пробував цього, але навіть можна активувати / Діалог блокувати, щоб він обертався, коли нитка працює, прискорюючи роботу, дозволяючи повороту активності виклику.


Добре за винятком того, що параметри повинні бути передані через Intent, що є більш обмеженим, ніж ObjectдозволеноAsyncTask
rds

@Rui Я теж використовував цей метод останній рік. Зараз мені спало на думку, що це теж неправильно. Чому Google навіть мав діалог, якщо це спосіб «виправити» цю проблему? Проблема, яку я бачу, полягає в тому, що якщо ви відкриєте ActivityB (Theme.Dialog) з ActivityA, тоді ActivityA переміщується вниз на стек Activity, і таким чином позначається як готовий до вбивства ОС, якщо це необхідно. Тому, якщо у вас тривалий процес і ви показуєте якийсь "діалог" про штучний прогрес, і це зайняло занадто багато часу, і пам'ять вичерпалася ... ActivityA вбивається, і після завершення прогресу нічого не повертається.
rf43

1

У наші дні існує набагато чіткіший спосіб вирішення подібних питань. Типовий підхід:

1. Переконайтесь, що ваші дані належним чином відокремлені від інтерфейсу користувача:

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

Див Handling час виконання Changes розроблено посібник для отримання інформації про це правильно і чому це кращий варіант.

2. Переконайтесь, що ви правильно та безпечно взаємодієте між фоновими процесами та інтерфейсом користувача:

Ви повинні змінити процес зв’язку. На даний момент ваш фоновий процес приєднується до View- замість цього ви Viewповинні приєднуватися до фонового процесу. Це має більше сенсу правильно? В Viewдії «S залежить від фонового процесу, в той час як фоновий процес не залежить від Viewзасобів .Ети змінюються посилання на стандартний Listenerінтерфейс. Скажіть, що ваш процес (незалежно від класу - чи це AsyncTask, Runnableчи будь-який) визначає "a" 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();
        }
    });
 }

Див. Життєвий цикл "Фрагмент", щоб визначити, де налаштування слухача найкраще підходить для вашої індивідуальної реалізації.

Це загальний підхід до забезпечення надійного та повного вирішення загальної проблеми, заданої в цьому питанні. У цій відповіді, мабуть, бракує декількох незначних фрагментів залежно від вашого індивідуального сценарію, але це, як правило, найбільш правильний підхід для правильного поводження з подіями зміни орієнтації.


1

Я знайшов і простіше рішення для обробки потоків при зміні орієнтації. Ви можете просто зберегти статичну посилання на свою діяльність / фрагмент і перевірити, чи є його нуль, перш ніж діяти на інтерфейс користувача. Я пропоную також використати спробу лову:

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


}

1

Якщо ви боретеся з виявленням подій зміни орієнтації діалогу НЕЗАЛЕЖНОСТІ ДІЯЛЬНОСТІ , цей метод працює надзвичайно добре. Я використовую це, оскільки у мене є власний діалоговий клас, який можна відображати в декількох різних видах діяльності, тому я не завжди знаю, у якій діяльності він відображається. За допомогою цього методу вам не потрібно змінювати 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();

1

Це моє рішення, коли я зіткнувся з ним: 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();
        }
    }
}

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

Для вирішення цього питання є два підходи:

Перший підхід: зробіть активність, яка використовує діалог для збереження стану під час зміни конфігурації у файлі маніфесту:

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

0

Здається, занадто "швидким і брудним", щоб бути правдою, тому, будь ласка, вкажіть на недоліки, але те, що я знайшов, працювало -

У методі onPostExecute мого AsyncTask я просто загорнув діалогове вікно ". Відхилити" для діалогу прогресу в блок "спробу / ловити" (з порожнім уловом), а потім просто проігнорував виняток, який було порушено. Здається, це неправильно робити, але, схоже, немає негативних наслідків (принаймні, для того, що я роблю згодом, а саме - почати іншу діяльність, що проходить в результаті мого тривалого запиту як додаткового)


Ти кажеш, що насправді немає витоку пам'яті, коли платформи Android повідомляють, що вікно просочилося?
rds

Мій діалог прогресу - це змінна категорія класу активності, тому я припустив, що коли діяльність буде знищена та відтворена, то буде зібрано сміття і не буде витоку. Я помиляюся?
Саймон

Так, я думаю, що це неправильно. Як ви кажете, цей Activityпосилання має посилання на Dialog. Коли зміна конфігурації, перше Activityзнищується, що означає, що всі поля встановлені на null. Але низький рівень WindowManagerтакож має посилання на Dialog(оскільки він ще не був звільнений). Новий Activityнамагається створити новий Dialog(in preExecute()), і менеджер вікон створює фатальний виняток, заважаючи вам це зробити. Дійсно, якщо це буде Dialogзроблено , не було б способу чисто знищити, отже, зберігаючи посилання на початкове Activity. Чи правий я?
rds

0

Найпростішим і гнучким рішенням є використання 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();
    }
}

0

Коли ви змінюєте орієнтації, Android знищує цю активність і створює нову активність. Я пропоную використовувати модернізацію з Rx java. які обробляють збої автоматично.

Використовуйте ці способи під час виклику за доповненням.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.