móviles - manual de programacion android pdf
Práctica recomendada: AsyncTask durante el cambio de orientación (9)
AsyncTask
es una gran cosa para ejecutar tareas complejas en otro hilo.
Pero cuando hay un cambio de orientación u otra configuración cambia mientras AsyncTask
aún se está ejecutando, la Activity
actual se destruye y se reinicia. Y como la instancia de AsyncTask
está conectada a esa actividad, falla y causa una ventana de mensaje de "cierre forzado".
Por lo tanto, estoy buscando algún tipo de "mejores prácticas" para evitar estos errores y evitar que AsyncTask falle.
Lo que he visto hasta ahora es:
- Deshabilite los cambios de orientación. (Por supuesto, no de la forma en que debe manejar esto).
- Dejar que la tarea sobreviva y actualizarla con la nueva instancia de actividad mediante
onRetainNonConfigurationInstance
- Simplemente cancela la tarea cuando se destruye la
Activity
y la reinicia cuando laActivity
se crea nuevamente. - Vinculando la tarea a la clase de aplicación en lugar de a la instancia de actividad.
- Algún método utilizado en el proyecto "estanterías" (a través deRestoreInstanceState)
Algunos ejemplos de código:
Android AsyncTasks durante una rotación de pantalla, Parte I y Parte II
¿Puede ayudarme a encontrar el mejor enfoque que resuelva mejor el problema y también sea fácil de implementar? El código en sí también es importante, ya que no sé cómo resolverlo correctamente.
TRABAJO RÁPIDO (no recomendado)
Evitar que una actividad se destruya y se cree a sí misma es declarar su actividad en el archivo de manifiesto: android: configChanges = "orientation | keyboardHidden | screenSize
<activity
android:name=".ui.activity.MyActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name">
Como se menciona en los docs
La orientación de la pantalla ha cambiado: el usuario ha girado el dispositivo.
Nota: Si su aplicación se dirige al nivel API 13 o superior (según lo declarado por los atributos minSdkVersion y targetSdkVersion), también debe declarar la configuración de "tamaño de pantalla", porque también cambia cuando un dispositivo cambia entre orientación vertical y paisaje.
Aquí hay otro ejemplo de una AsyncTask que usa un Fragment
para manejar los cambios de configuración de tiempo de ejecución (como cuando el usuario gira la pantalla) con setRetainInstance(true)
. También se demuestra una barra de progreso determinada (regularmente actualizada).
El ejemplo se basa en parte en los documentos oficiales, Retener un objeto durante un cambio de configuración .
En este ejemplo, el trabajo que requiere un hilo de fondo es la mera carga de una imagen de Internet en la interfaz de usuario.
Parece que Alex Lockwood tiene razón cuando se trata de manejar cambios en la configuración del tiempo de ejecución con AsyncTasks usando un "Fragmento Retenido" es la mejor práctica. onRetainNonConfigurationInstance()
queda obsoleto en Lint, en Android Studio. Los documentos oficiales nos advierten usando android:configChanges
, desde Manejo de la configuración Change Yourself , ...
Manejar el cambio de configuración usted mismo puede hacer que sea mucho más difícil usar recursos alternativos, porque el sistema no los aplica automáticamente. Esta técnica debe considerarse un último recurso cuando debe evitar reinicios debido a un cambio de configuración y no se recomienda para la mayoría de las aplicaciones.
Luego está la cuestión de si uno debe usar una AsyncTask para el hilo de fondo.
La referencia oficial para AsyncTask advierte ...
AsyncTasks debería utilizarse idealmente para operaciones cortas (unos segundos como máximo). Si necesita mantener los hilos en ejecución durante largos periodos de tiempo, se recomienda encarecidamente que utilice las diversas API proporcionadas por el paquete java.util.concurrent, como Ejecutor, ThreadPoolExecutor y FutureTask.
Alternativamente, uno podría usar un servicio, un cargador (usando un CursorLoader o AsyncTaskLoader) o un proveedor de contenido para realizar operaciones asincrónicas.
Rompo el resto de la publicación en:
- El procedimiento; y
- Todo el código para el procedimiento anterior.
El procedimiento
Comience con una AsyncTask básica como una clase interna de una actividad (no necesita ser una clase interna, pero probablemente sea conveniente serlo). En esta etapa, AsyncTask no maneja los cambios en la configuración del tiempo de ejecución.
public class ThreadsActivity extends ActionBarActivity { private ImageView mPictureImageView; private class LoadImageFromNetworkAsyncTask extends AsyncTask<String, Void, Bitmap> { @Override protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls[0]); } @Override protected void onPostExecute(Bitmap bitmap) { mPictureImageView.setImageBitmap(bitmap); } } /** * Requires in AndroidManifext.xml * <uses-permission android:name="android.permission.INTERNET" /> */ private Bitmap loadImageFromNetwork(String url) { Bitmap bitmap = null; try { bitmap = BitmapFactory.decodeStream((InputStream) new URL(url).getContent()); } catch (Exception e) { e.printStackTrace(); } return bitmap; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_threads); mPictureImageView = (ImageView) findViewById(R.id.imageView_picture); } public void getPicture(View view) { new LoadImageFromNetworkAsyncTask() .execute("http://i.imgur.com/SikTbWe.jpg"); } }
Agregue una clase anidada RetainedFragment que amplíe la clase Fragement y no tenga su propia IU. Agregue setRetainInstance (verdadero) al evento onCreate de este Fragmento. Proporcione procedimientos para establecer y obtener sus datos.
public class ThreadsActivity extends Activity { private ImageView mPictureImageView; private RetainedFragment mRetainedFragment = null; ... public static class RetainedFragment extends Fragment { private Bitmap mBitmap; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // The key to making data survive // runtime configuration changes. setRetainInstance(true); } public Bitmap getData() { return this.mBitmap; } public void setData(Bitmap bitmapToRetain) { this.mBitmap = bitmapToRetain; } } private class LoadImageFromNetworkAsyncTask extends AsyncTask<String, Integer,Bitmap> { ....
En el extremo externo de la Clase de Actividad, onCreate () maneja el Fragmento Retenido: Referenciarlo si ya existe (en caso de que la Actividad se reinicie); crear y agregar si no existe; Luego, si ya existió, obtenga datos de RetainedFragment y configure su UI con esa información.
public class ThreadsActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_threads); final String retainedFragmentTag = "RetainedFragmentTag"; mPictureImageView = (ImageView) findViewById(R.id.imageView_picture); mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading); // Find the RetainedFragment on Activity restarts FragmentManager fm = getFragmentManager(); // The RetainedFragment has no UI so we must // reference it with a tag. mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(retainedFragmentTag); // if Retained Fragment doesn''t exist create and add it. if (mRetainedFragment == null) { // Add the fragment mRetainedFragment = new RetainedFragment(); fm.beginTransaction() .add(mRetainedFragment, retainedFragmentTag).commit(); // The Retained Fragment exists } else { mPictureImageView .setImageBitmap(mRetainedFragment.getData()); } }
Iniciar el AsyncTask desde la interfaz de usuario
public void getPicture(View view) { new LoadImageFromNetworkAsyncTask().execute( "http://i.imgur.com/SikTbWe.jpg"); }
Agregue y codifique una barra de progreso determinada:
- Agregue una barra de progreso al diseño de la interfaz de usuario;
- Obtenga una referencia en la Actividad oncreate ();
- Hazlo visible e invisible al comienzo y al final del proceso;
- Defina el progreso para informar a la IU en onProgressUpdate.
- Cambie el parámetro AsyncTask 2nd Generic de Void a un tipo que pueda gestionar las actualizaciones de progreso (por ejemplo, Integer).
- publique el progreso en puntos regulares en doInBackground ().
Todo el código para el procedimiento anterior
Diseño de la actividad.
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.mysecondapp.ThreadsActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<ImageView
android:id="@+id/imageView_picture"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@android:color/black" />
<Button
android:id="@+id/button_get_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@id/imageView_picture"
android:onClick="getPicture"
android:text="Get Picture" />
<Button
android:id="@+id/button_clear_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/button_get_picture"
android:layout_toEndOf="@id/button_get_picture"
android:layout_toRightOf="@id/button_get_picture"
android:onClick="clearPicture"
android:text="Clear Picture" />
<ProgressBar
android:id="@+id/progressBar_loading"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/button_get_picture"
android:progress="0"
android:indeterminateOnly="false"
android:visibility="invisible" />
</RelativeLayout>
</ScrollView>
La actividad con: clase interna AsyncTask subclasificada; clase interna RetainedFragment subclasificada que maneja los cambios de configuración de tiempo de ejecución (por ejemplo, cuando el usuario gira la pantalla); y una barra de progreso determinada que se actualiza a intervalos regulares. ...
public class ThreadsActivity extends Activity {
private ImageView mPictureImageView;
private RetainedFragment mRetainedFragment = null;
private ProgressBar mLoadingProgressBar;
public static class RetainedFragment extends Fragment {
private Bitmap mBitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The key to making data survive runtime configuration changes.
setRetainInstance(true);
}
public Bitmap getData() {
return this.mBitmap;
}
public void setData(Bitmap bitmapToRetain) {
this.mBitmap = bitmapToRetain;
}
}
private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
Integer, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) {
// Simulate a burdensome load.
int sleepSeconds = 4;
for (int i = 1; i <= sleepSeconds; i++) {
SystemClock.sleep(1000); // milliseconds
publishProgress(i * 20); // Adjust for a scale to 100
}
return com.example.standardapplibrary.android.Network
.loadImageFromNetwork(
urls[0]);
}
@Override
protected void onProgressUpdate(Integer... progress) {
mLoadingProgressBar.setProgress(progress[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
publishProgress(100);
mRetainedFragment.setData(bitmap);
mPictureImageView.setImageBitmap(bitmap);
mLoadingProgressBar.setVisibility(View.INVISIBLE);
publishProgress(0);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threads);
final String retainedFragmentTag = "RetainedFragmentTag";
mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);
// Find the RetainedFragment on Activity restarts
FragmentManager fm = getFragmentManager();
// The RetainedFragment has no UI so we must reference it with a tag.
mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
retainedFragmentTag);
// if Retained Fragment doesn''t exist create and add it.
if (mRetainedFragment == null) {
// Add the fragment
mRetainedFragment = new RetainedFragment();
fm.beginTransaction().add(mRetainedFragment,
retainedFragmentTag).commit();
// The Retained Fragment exists
} else {
mPictureImageView.setImageBitmap(mRetainedFragment.getData());
}
}
public void getPicture(View view) {
mLoadingProgressBar.setVisibility(View.VISIBLE);
new LoadImageFromNetworkAsyncTask().execute(
"http://i.imgur.com/SikTbWe.jpg");
}
public void clearPicture(View view) {
mRetainedFragment.setData(null);
mPictureImageView.setImageBitmap(null);
}
}
En este ejemplo, la función de la biblioteca (a la que se hace referencia anteriormente con el prefijo explícito del paquete com.example.standardapplibrary.android.Network) que hace un trabajo real ...
public static Bitmap loadImageFromNetwork(String url) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
.getContent());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
Agregue los permisos que necesita su tarea de fondo al AndroidManifest.xml ...
<manifest>
...
<uses-permission android:name="android.permission.INTERNET" />
Agregue su actividad a AndroidManifest.xml ...
<manifest>
...
<application>
<activity
android:name=".ThreadsActivity"
android:label="@string/title_activity_threads"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.mysecondapp.MainActivity" />
</activity>
Basado en @Alex Lockwood answer y en @William & @quickdraw mcgraw respuestas en esta publicación: Cómo manejar los mensajes de Handler cuando actividad / fragmento está en pausa , escribí una solución genérica.
De esta forma se maneja la rotación, y si la actividad pasa a segundo plano durante la ejecución de la tarea asincrónica, la actividad recibirá las devoluciones de llamada (onPreExecute, onProgressUpdate, onPostExecute & onCancelled) una vez reasumido, por lo que no se lanzará IllegalStateException (consulte Cómo manejar Handler mensajes cuando actividad / fragmento está en pausa ).
Sería genial tener el mismo pero con tipos de argumentos genéricos, como una AsyncTask (por ejemplo: AsyncTaskFragment <Params, Progress, Result>), pero no logré hacerlo rápidamente y no tengo tiempo en este momento. Si alguien quiere mejorar, ¡siéntase libre!
El código:
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
public class AsyncTaskFragment extends Fragment {
/* ------------------------------------------------------------------------------------------ */
// region Classes & Interfaces
public static abstract class Task extends AsyncTask<Object, Object, Object> {
private AsyncTaskFragment _fragment;
private void setFragment(AsyncTaskFragment fragment) {
_fragment = fragment;
}
@Override
protected final void onPreExecute() {
// Save the state :
_fragment.setRunning(true);
// Send a message :
sendMessage(ON_PRE_EXECUTE_MESSAGE, null);
}
@Override
protected final void onPostExecute(Object result) {
// Save the state :
_fragment.setRunning(false);
// Send a message :
sendMessage(ON_POST_EXECUTE_MESSAGE, result);
}
@Override
protected final void onProgressUpdate(Object... values) {
// Send a message :
sendMessage(ON_PROGRESS_UPDATE_MESSAGE, values);
}
@Override
protected final void onCancelled() {
// Save the state :
_fragment.setRunning(false);
// Send a message :
sendMessage(ON_CANCELLED_MESSAGE, null);
}
private void sendMessage(int what, Object obj) {
Message message = new Message();
message.what = what;
message.obj = obj;
Bundle data = new Bundle(1);
data.putString(EXTRA_FRAGMENT_TAG, _fragment.getTag());
message.setData(data);
_fragment.handler.sendMessage(message);
}
}
public interface AsyncTaskFragmentListener {
void onPreExecute(String fragmentTag);
void onProgressUpdate(String fragmentTag, Object... progress);
void onCancelled(String fragmentTag);
void onPostExecute(String fragmentTag, Object result);
}
private static class AsyncTaskFragmentPauseHandler extends PauseHandler {
@Override
final protected void processMessage(Activity activity, Message message) {
switch (message.what) {
case ON_PRE_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPreExecute(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
case ON_POST_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPostExecute(message.getData().getString(EXTRA_FRAGMENT_TAG), message.obj); break; }
case ON_PROGRESS_UPDATE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onProgressUpdate(message.getData().getString(EXTRA_FRAGMENT_TAG), ((Object[])message.obj)); break; }
case ON_CANCELLED_MESSAGE : { ((AsyncTaskFragmentListener)activity).onCancelled(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
}
}
}
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Attributes
private Task _task;
private AsyncTaskFragmentListener _listener;
private boolean _running = false;
private static final String EXTRA_FRAGMENT_TAG = "EXTRA_FRAGMENT_TAG";
private static final int ON_PRE_EXECUTE_MESSAGE = 0;
private static final int ON_POST_EXECUTE_MESSAGE = 1;
private static final int ON_PROGRESS_UPDATE_MESSAGE = 2;
private static final int ON_CANCELLED_MESSAGE = 3;
private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Getters
public AsyncTaskFragmentListener getListener() { return _listener; }
public boolean isRunning() { return _running; }
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Setters
public void setTask(Task task) {
_task = task;
_task.setFragment(this);
}
public void setListener(AsyncTaskFragmentListener listener) { _listener = listener; }
private void setRunning(boolean running) { _running = running; }
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Fragment lifecycle
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onResume() {
super.onResume();
handler.resume(getActivity());
}
@Override
public void onPause() {
super.onPause();
handler.pause();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
_listener = (AsyncTaskFragmentListener) activity;
}
@Override
public void onDetach() {
super.onDetach();
_listener = null;
}
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Utils
public void execute(Object... params) {
_task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
}
public void cancel(boolean mayInterruptIfRunning) {
_task.cancel(mayInterruptIfRunning);
}
public static AsyncTaskFragment getRetainedOrNewFragment(AppCompatActivity activity, String fragmentTag) {
FragmentManager fm = activity.getSupportFragmentManager();
AsyncTaskFragment fragment = (AsyncTaskFragment) fm.findFragmentByTag(fragmentTag);
if (fragment == null) {
fragment = new AsyncTaskFragment();
fragment.setListener( (AsyncTaskFragmentListener) activity);
fm.beginTransaction().add(fragment, fragmentTag).commit();
}
return fragment;
}
// endregion
/* ------------------------------------------------------------------------------------------ */
}
Necesitarás PauseHandler:
import android.app.Activity;
import android.os.Handler;
import android.os.Message;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Message Handler class that supports buffering up of messages when the activity is paused i.e. in the background.
*
* https://.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
*/
public abstract class PauseHandler extends Handler {
/**
* Message Queue Buffer
*/
private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());
/**
* Flag indicating the pause state
*/
private Activity activity;
/**
* Resume the handler.
*/
public final synchronized void resume(Activity activity) {
this.activity = activity;
while (messageQueueBuffer.size() > 0) {
final Message msg = messageQueueBuffer.get(0);
messageQueueBuffer.remove(0);
sendMessage(msg);
}
}
/**
* Pause the handler.
*/
public final synchronized void pause() {
activity = null;
}
/**
* Store the message if we have been paused, otherwise handle it now.
*
* @param msg Message to handle.
*/
@Override
public final synchronized void handleMessage(Message msg) {
if (activity == null) {
final Message msgCopy = new Message();
msgCopy.copyFrom(msg);
messageQueueBuffer.add(msgCopy);
} else {
processMessage(activity, msg);
}
}
/**
* Notification message to be processed. This will either be directly from
* handleMessage or played back from a saved message when the activity was
* paused.
*
* @param activity Activity owning this Handler that isn''t currently paused.
* @param message Message to be handled
*/
protected abstract void processMessage(Activity activity, Message message);
}
Uso de muestra:
public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {
private final static String ASYNC_TASK_FRAGMENT_A = "ASYNC_TASK_FRAGMENT_A";
private final static String ASYNC_TASK_FRAGMENT_B = "ASYNC_TASK_FRAGMENT_B";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button testButton = (Button) findViewById(R.id.test_button);
final AsyncTaskFragment fragment = AsyncTaskFragment.getRetainedOrNewFragment(TestActivity.this, ASYNC_TASK_FRAGMENT_A);
testButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(!fragment.isRunning()) {
fragment.setTask(new Task() {
@Override
protected Object doInBackground(Object... objects) {
// Do your async stuff
return null;
}
});
fragment.execute();
}
}
});
}
@Override
public void onPreExecute(String fragmentTag) {}
@Override
public void onProgressUpdate(String fragmentTag, Float percent) {}
@Override
public void onCancelled(String fragmentTag) {}
@Override
public void onPostExecute(String fragmentTag, Object result) {
switch (fragmentTag) {
case ASYNC_TASK_FRAGMENT_A: {
// Handle ASYNC_TASK_FRAGMENT_A
break;
}
case ASYNC_TASK_FRAGMENT_B: {
// Handle ASYNC_TASK_FRAGMENT_B
break;
}
}
}
}
He implementado una library que puede resolver problemas con pausa de actividad y recreación mientras se ejecuta su tarea.
Debe implementar AsmykPleaseWaitTask
y AsmykBasicPleaseWaitActivity
. Su actividad y su tarea en segundo plano funcionarán bien incluso si gira la pantalla y cambia entre las aplicaciones
Normalmente resuelvo esto haciendo que mis AsyncTasks transmitan Intenciones en la devolución de llamada .onPostExecute (), por lo que no modifican la Actividad que las inició directamente. Las Actividades escuchan estas transmisiones con BroadcastReceivers dinámicos y actúan en consecuencia.
De esta forma, las AsyncTasks no tienen que preocuparse por la instancia de actividad específica que maneja su resultado. Simplemente "gritan" cuando terminan, y si una Actividad está alrededor de ese momento (está activa y enfocada / está en su estado reanudado) que está interesada en los resultados de la tarea, entonces se manejará.
Esto implica un poco más sobrecarga, ya que el tiempo de ejecución necesita manejar la transmisión, pero normalmente no me importa. Creo que usar el LocalBroadcastManager en lugar del sistema predeterminado de ancho acelera un poco las cosas.
Para aquellos que quieren esquivar Fragmentos, puede retener la AsyncTask ejecutándose en los cambios de orientación usando onRetainCustomNonConfigurationInstance() y algo de cableado.
(Tenga en cuenta que este método es la alternativa a la obsoleta onRetainNonConfigurationInstance() ).
Parece que esta solución no se menciona con frecuencia. Escribí un ejemplo de ejecución simple para ilustrar.
¡Aclamaciones!
public class MainActivity extends AppCompatActivity {
private TextView result;
private Button run;
private AsyncTaskHolder asyncTaskHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
result = (TextView) findViewById(R.id.textView_result);
run = (Button) findViewById(R.id.button_run);
asyncTaskHolder = getAsyncTaskHolder();
run.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
asyncTaskHolder.execute();
}
});
}
private AsyncTaskHolder getAsyncTaskHolder() {
if (this.asyncTaskHolder != null) {
return asyncTaskHolder;
}
//Not deprecated. Get the same instance back.
Object instance = getLastCustomNonConfigurationInstance();
if (instance == null) {
instance = new AsyncTaskHolder();
}
if (!(instance instanceof ActivityDependant)) {
Log.e("", instance.getClass().getName() + " must implement ActivityDependant");
}
return (AsyncTaskHolder) instance;
}
@Override
//Not deprecated. Save the object containing the running task.
public Object onRetainCustomNonConfigurationInstance() {
return asyncTaskHolder;
}
@Override
protected void onStart() {
super.onStart();
if (asyncTaskHolder != null) {
asyncTaskHolder.attach(this);
}
}
@Override
protected void onStop() {
super.onStop();
if (asyncTaskHolder != null) {
asyncTaskHolder.detach();
}
}
void updateUI(String value) {
this.result.setText(value);
}
interface ActivityDependant {
void attach(Activity activity);
void detach();
}
class AsyncTaskHolder implements ActivityDependant {
private Activity parentActivity;
private boolean isRunning;
private boolean isUpdateOnAttach;
@Override
public synchronized void attach(Activity activity) {
this.parentActivity = activity;
if (isUpdateOnAttach) {
((MainActivity) parentActivity).updateUI("done");
isUpdateOnAttach = false;
}
}
@Override
public synchronized void detach() {
this.parentActivity = null;
}
public synchronized void execute() {
if (isRunning) {
Toast.makeText(parentActivity, "Already running", Toast.LENGTH_SHORT).show();
return;
}
isRunning = true;
new AsyncTask<Void, Integer, Void>() {
@Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < 100; i += 10) {
try {
Thread.sleep(500);
publishProgress(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (parentActivity != null) {
((MainActivity) parentActivity).updateUI(String.valueOf(values[0]));
}
}
@Override
protected synchronized void onPostExecute(Void aVoid) {
if (parentActivity != null) {
((MainActivity) parentActivity).updateUI("done");
} else {
isUpdateOnAttach = true;
}
isRunning = false;
}
}.execute();
}
}
Puede usar los cargadores para esto. Marque Doc aquí
Recientemente, he encontrado una buena solución here . Se basa en guardar un objeto de tarea a través de RetainConfiguration. Desde mi punto de vista, la solución es muy elegante y en cuanto a mí, he comenzado a usarla. Solo necesita anidar su asynctask desde la base de tareas y eso es todo.
NO use android:configChanges
para solucionar este problema. Esta es una muy mala práctica.
NO use Activity#onRetainNonConfigurationInstance()
tampoco. Esto es menos modular y no es adecuado para aplicaciones basadas en Fragment
.
Puede leer mi artículo describiendo cómo manejar los cambios de configuración utilizando Fragment
retenidos. Resuelve muy bien el problema de conservar una AsyncTask
en un cambio de rotación. Básicamente, necesitas alojar tu AsyncTask
dentro de un Fragment
, invocar setRetainInstance(true)
en el Fragment
e informar el progreso / los resultados de la AsyncTask
a su Activity
través del Fragment
retenido.