¿Cómo manejar el cambio de orientación de la pantalla cuando el diálogo de progreso y el hilo de fondo están activos?

Resuelto Heikki Toivonen asked hace 54 años • 28 respuestas

Mi programa realiza alguna actividad de red en un hilo en segundo plano. Antes de comenzar, aparece un cuadro de diálogo de progreso. El diálogo se cierra en el controlador. Todo esto funciona bien, excepto cuando la orientación de la pantalla cambia mientras el cuadro de diálogo está abierto (y el hilo de fondo está activo). En este punto, la aplicación falla, se bloquea o entra en una etapa extraña en la que la aplicación no funciona en absoluto hasta que se hayan eliminado todos los subprocesos.

¿Cómo puedo manejar el cambio de orientación de la pantalla con elegancia?

El siguiente código de muestra coincide aproximadamente con lo que hace mi programa real:

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

Pila:

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)

Intenté descartar el cuadro de diálogo de progreso en onSaveInstanceState, pero eso solo evita un bloqueo inmediato. El hilo en segundo plano aún continúa y la interfaz de usuario está parcialmente dibujada. Es necesario cerrar toda la aplicación antes de que comience a funcionar nuevamente.

Heikki Toivonen avatar Jan 01 '70 08:01 Heikki Toivonen
Aceptado

Editar: los ingenieros de Google no recomiendan este enfoque, como lo describe Dianne Hackborn (también conocida como hackbod ) en esta publicación de StackOverflow . Consulte esta publicación de blog para obtener más información.


Tienes que agregar esto a la declaración de actividad en el manifiesto:

android:configChanges="orientation|screenSize"

entonces parece

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

La cuestión es que el sistema destruye la actividad cuando se produce un cambio en la configuración. Consulte Cambios de configuración .

Entonces, poner eso en el archivo de configuración evita que el sistema destruya su actividad. En lugar de eso, invoca el onConfigurationChanged(Configuration)método.

sonxurxo avatar Mar 10 '2010 16:03 sonxurxo

Cuando cambias de orientación, Android creará una nueva Vista. Probablemente estés teniendo fallas porque tu hilo en segundo plano está intentando cambiar el estado del anterior. (También puede estar teniendo problemas porque su hilo en segundo plano no está en el hilo de la interfaz de usuario)

Sugeriría hacer que mHandler sea volátil y actualizarlo cuando cambie la orientación.

haseman avatar Jul 12 '2009 19:07 haseman

Se me ocurrió una solución sólida para estos problemas que se ajusta al 'estilo Android' de las cosas. Tengo todas mis operaciones de larga duración utilizando el patrón IntentService.

Es decir, mis actividades transmiten intenciones, IntentService hace el trabajo, guarda los datos en la base de datos y luego transmite intenciones fijas . La parte adhesiva es importante, de modo que incluso si la Actividad se pausó durante el tiempo posterior a que el usuario inició el trabajo y se pierde la transmisión en tiempo real desde IntentService, aún podemos responder y recoger los datos de la Actividad que llama. ProgressDialogs puede funcionar bastante bien con este patrón onSaveInstanceState().

Básicamente, debe guardar una marca de que tiene un cuadro de diálogo de progreso ejecutándose en el paquete de instancias guardado. No guarde el objeto de diálogo de progreso porque esto filtrará toda la actividad. Para tener un control persistente del cuadro de diálogo de progreso, lo almaceno como una referencia débil en el objeto de la aplicación. Al cambiar de orientación o cualquier otra cosa que haga que la Actividad se detenga (llamada telefónica, el usuario llega a casa, etc.) y luego se reanude, descarto el cuadro de diálogo anterior y vuelvo a crear un nuevo cuadro de diálogo en la Actividad recién creada.

Para diálogos de progreso indefinido, esto es fácil. Para el estilo de la barra de progreso, debes colocar el último progreso conocido en el paquete y cualquier información que estés usando localmente en la actividad para realizar un seguimiento del progreso. Al restaurar el progreso, utilizará esta información para volver a generar la barra de progreso en el mismo estado que antes y luego actualizarla según el estado actual de las cosas.

Entonces, para resumir, colocar tareas de larga duración en un IntentService junto con un uso sensato onSaveInstanceState()le permite realizar un seguimiento eficiente de los cuadros de diálogo y restaurarlos a lo largo de los eventos del ciclo de vida de la actividad. Los fragmentos relevantes del código de actividad se encuentran a continuación. También necesitarás lógica en tu BroadcastReceiver para manejar los intentos Sticky de manera adecuada, pero eso está más allá del alcance de esto.

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));
    }
}
jfelectron avatar Dec 19 '2010 02:12 jfelectron