android - screensize - Tarea en segundo plano, diálogo de progreso, cambio de orientación: ¿existe alguna solución que funcione al 100%?
set orientation android tv (8)
¿Alguien de Google proporcionó alguna "solución oficial"?
Sí.
La solución es más una propuesta de arquitectura de aplicación que un código .
Propusieron 3 patrones de diseño que permiten que una aplicación funcione en sincronización con un servidor, independientemente del estado de la aplicación (funcionará incluso si el usuario finaliza la aplicación, la pantalla de cambios del usuario, la aplicación se cancela, cualquier otro estado posible donde una operación de datos de fondo podría ser interrumpida, esto lo cubre)
La propuesta se explica en el discurso de las aplicaciones cliente REST de Android durante Google I / O 2010 por Virgil Dobjanschi. Es de 1 hora de duración, pero merece la pena verlo.
La base de esto es abstraer las operaciones de red a un Service
que funciona independientemente de cualquier Activity
en la aplicación. Si está trabajando con bases de datos, el uso de ContentResolver
y Cursor
le daría un patrón de observador ContentResolver
usar que es conveniente para actualizar la interfaz de usuario sin ninguna lógica adicional, una vez que actualice su base de datos local con los datos remotos obtenidos. Cualquier otro código posterior a la operación se ejecutaría a través de una devolución de llamada al Service
(uso una subclase ResultReceiver
para esto).
De todos modos, mi explicación es en realidad bastante vaga, definitivamente debes ver el discurso.
AsyncTask
algunos datos de Internet en un hilo de fondo (uso AsyncTask
) y AsyncTask
un diálogo de progreso mientras descargo. La orientación cambia, la actividad se reinicia y luego se completa mi AsyncTask. Quiero descartar el cuadro de diálogo de progreso y comenzar una nueva actividad. Pero al llamar a despedirse, el diálogo a veces lanza una excepción (probablemente porque la Actividad fue destruida y la nueva Actividad aún no se ha iniciado).
¿Cuál es la mejor manera de manejar este tipo de problema (actualizar la interfaz de usuario desde el hilo de fondo que funciona incluso si el usuario cambia la orientación)? ¿Alguien de Google proporcionó alguna "solución oficial"?
Después de 4 años, Google resolvió el problema simplemente llamando a setRetainInstance (true) en Activity onCreate. Conservará su instancia de actividad durante la rotación del dispositivo. También tengo una solución simple para Android más antiguo.
Esta es mi solución: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
Básicamente los pasos son:
- Utilizo
onSaveInstanceState
para guardar la tarea si aún se está procesando. - En
onCreate
me sale la tarea si se guardó. - En
onPause
descarto elProgressDialog
si se muestra. - En
onResume
muestro elProgressDialog
si la tarea aún se está procesando.
He trabajado durante una semana para encontrar una solución a este dilema sin recurrir a la edición del archivo de manifiesto. Los supuestos para esta solución son:
- Siempre necesitas utilizar un diálogo de progreso.
- Solo se realiza una tarea a la vez
- Necesita que la tarea persista cuando se gire el teléfono y el cuadro de diálogo de progreso se cierre automáticamente.
Implementación
Deberá copiar los dos archivos que se encuentran al final de esta publicación en su área de trabajo. Sólo asegúrese de que:
Toda tu
Activity
s debe extenderBaseActivity
En
onCreate()
, se debe llamar asuper.onCreate()
después de que inicialice a los miembros a los que deba acceder suASyncTask
s. Además, anulegetContentViewId()
para proporcionar el ID de diseño de formulario.Sobrescriba
onCreateDialog()
como de costumbre para crear diálogos administrados por la actividad.Vea el código a continuación para obtener una muestra interna estática de muestra para hacer sus AsyncTasks. Puede almacenar su resultado en mResultado para acceder más tarde.
final static class MyTask extends SuperAsyncTask<Void, Void, Void> {
public OpenDatabaseTask(BaseActivity activity) {
super(activity, MY_DIALOG_ID); // change your dialog ID here...
// and your dialog will be managed automatically!
}
@Override
protected Void doInBackground(Void... params) {
// your task code
return null;
}
@Override
public boolean onAfterExecute() {
// your after execute code
}
}
Y finalmente, para lanzar tu nueva tarea:
mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();
¡Eso es! Espero que esta solución robusta ayude a alguien.
BaseActivity.java (organiza las importaciones tu mismo)
protected abstract int getContentViewId();
public abstract class BaseActivity extends Activity {
protected SuperAsyncTask<?, ?, ?> mCurrentTask;
public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentViewId());
mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
if (mCurrentTask != null) {
mCurrentTask.attach(this);
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
mCurrentTask.postExecution();
}
}
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
mDialogMap.put(id, true);
}
@Override
public Object onRetainNonConfigurationInstance() {
if (mCurrentTask != null) {
mCurrentTask.detach();
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
return mCurrentTask;
}
}
return super.onRetainNonConfigurationInstance();
}
public void cleanupTask() {
if (mCurrentTask != null) {
mCurrentTask = null;
System.gc();
}
}
}
SuperAsyncTask.java
public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected BaseActivity mActivity = null;
protected Result mResult;
public int dialogId = -1;
protected abstract void onAfterExecute();
public SuperAsyncTask(BaseActivity activity, int dialogId) {
super();
this.dialogId = dialogId;
attach(activity);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mActivity.showDialog(dialogId); // go polymorphism!
}
protected void onPostExecute(Result result) {
super.onPostExecute(result);
mResult = result;
if (mActivity != null &&
mActivity.mDialogMap.get((Integer) dialogId) != null
&& mActivity.mDialogMap.get((Integer) dialogId)) {
postExecution();
}
};
public void attach(BaseActivity activity) {
this.mActivity = activity;
}
public void detach() {
this.mActivity = null;
}
public synchronized boolean postExecution() {
Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
if (dialogExists != null || dialogExists) {
onAfterExecute();
cleanUp();
}
public boolean cleanUp() {
mActivity.removeDialog(dialogId);
mActivity.mDialogMap.remove((Integer) dialogId);
mActivity.cleanupTask();
detach();
return true;
}
}
La respuesta aceptada fue muy útil, pero no tiene un diálogo de progreso.
Afortunadamente para usted, lector, ¡he creado un ejemplo extremadamente completo y funcional de una AsyncTask con un diálogo de progreso !
- La rotación funciona, y el diálogo sobrevive.
- Puede cancelar la tarea y el cuadro de diálogo presionando el botón Atrás (si desea este comportamiento).
- Utiliza fragmentos.
- El diseño del fragmento debajo de la actividad cambia correctamente cuando el dispositivo gira.
Paso # 1: Convierta su AsyncTask
una clase anidada static
, o en una clase completamente separada, pero no en una clase interna (no anidada estática).
Paso # 2: Haga que AsyncTask
mantenga la Activity
través de un miembro de datos, configurada a través del constructor y un configurador.
Paso # 3: Al crear la AsyncTask
, suministre la Activity
actual al constructor.
Paso # 4: en onRetainNonConfigurationInstance()
, devuelva la AsyncTask
, después de AsyncTask
de la actividad original, que ahora se va.
Paso # 5: En onCreate()
, si getLastNonConfigurationInstance()
no es null
, AsyncTask
a tu clase de AsyncTask
y llama a tu AsyncTask
para asociar tu nueva actividad con la tarea.
Paso # 6: No te refieras al miembro de datos de actividad de doInBackground()
.
Si sigues la receta anterior, todo funcionará. onProgressUpdate()
y onPostExecute()
se suspenden entre el inicio de onRetainNonConfigurationInstance()
y el final del siguiente onCreate()
.
Aquí hay un proyecto de muestra que demuestra la técnica.
Otro enfoque es AsyncTask
la AsyncTask
y mover su trabajo a un IntentService
. Esto es particularmente útil si el trabajo a realizar puede ser largo y debe continuar independientemente de lo que el usuario haga en términos de actividades (por ejemplo, descargar un archivo grande). Puede usar una Intent
transmisión ordenada para que la actividad responda al trabajo que se está realizando (si aún está en primer plano) o para enviar una Notification
para que el usuario sepa si se ha realizado el trabajo. Aquí hay una entrada de blog con más sobre este patrón.
Si bien la respuesta de Mark (CommonsWare) sí funciona para los cambios de orientación, falla si la Actividad se destruye directamente (como en el caso de una llamada telefónica).
Puede manejar los cambios de orientación Y los raros eventos de Actividad destruidos usando un objeto de Aplicación para hacer referencia a su ASyncTask.
Hay una excelente explicación del problema y la solución here :
El crédito va completamente a Ryan por resolver esto.
debe llamar a todas las acciones de actividad utilizando el controlador de actividad. Entonces, si estás en algún hilo, deberías crear un Runnable y publicarlo usando el Controlador de Activitie. De lo contrario, su aplicación se bloqueará a veces con una excepción fatal.