samsung - Fragmentos de Android. Retener una AsyncTask durante la rotación de la pantalla o el cambio de configuración
porque no gira la pantalla de mi celular (12)
Creo que disfrutarás de mi extremadamente exhaustivo y práctico ejemplo detallado a continuación.
- La rotación funciona, y el diálogo sobrevive.
- Puede cancelar la tarea y el 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.
- Hay una descarga completa del código fuente y una APK precompilada para que pueda ver si el comportamiento es el que desea.
Editar
Según lo solicitado por Brad Larson, he reproducido la mayoría de las soluciones vinculadas a continuación. También desde que lo AsyncTaskLoader
me han señalado AsyncTaskLoader
. No estoy seguro de que sea totalmente aplicable a los mismos problemas, pero debe verificarlo de todos modos.
Usar AsyncTask
con diálogos de progreso y rotación de dispositivos.
Una solución de trabajo!
Finalmente tengo todo para trabajar. Mi código tiene las siguientes características:
- Un
Fragment
cuyo diseño cambia con la orientación. - Una
AsyncTask
en la que puedes hacer un poco de trabajo. - Un
DialogFragment
que muestra el progreso de la tarea en una barra de progreso (no solo un spinner indeterminado). - La rotación funciona sin interrumpir la tarea o descartar el diálogo.
- El botón Atrás cierra el diálogo y cancela la tarea (aunque puedes modificar este comportamiento bastante fácilmente).
No creo que esa combinación de trabajo se pueda encontrar en ningún otro lado.
La idea básica es la siguiente. Hay una clase MainActivity
que contiene un solo fragmento: MainFragment
. MainFragment
tiene diferentes diseños para la orientación horizontal y vertical, y setRetainInstance()
es falso para que el diseño pueda cambiar. Esto significa que cuando se cambia la orientación del dispositivo, tanto MainActivity
como MainFragment
se destruyen por completo y se MainFragment
crear.
Por separado tenemos MyTask
(extendido desde AsyncTask
) que hace todo el trabajo. No podemos almacenarlo en MainFragment
porque eso será destruido, y Google ha setRetainNonInstanceConfiguration()
usar algo como setRetainNonInstanceConfiguration()
. Eso no siempre está disponible de todos modos y es un hack feo en el mejor de los casos. En su lugar, almacenaremos MyTask
en otro fragmento, un DialogFragment
llamado TaskFragment
. Este fragmento tendrá setRetainInstance()
establecido en verdadero, por lo que a medida que el dispositivo gira, este fragmento no se destruye y MyTask
se conserva.
Finalmente, necesitamos decirle a TaskFragment
quién informar cuando se termine, y lo hacemos usando setTargetFragment(<the MainFragment>)
cuando lo creamos. Cuando se gira el dispositivo y se destruye el MainFragment
y se crea una nueva instancia, usamos el FragmentManager
para encontrar el diálogo (basado en su etiqueta) y hacemos setTargetFragment(<the new MainFragment>)
. Eso es practicamente todo.
Había dos cosas más que tenía que hacer: primero cancelar la tarea cuando se descarta el diálogo, y segundo establecer el mensaje de descarte en nulo, de lo contrario el diálogo se descarta de forma extraña cuando se gira el dispositivo.
El código
No enumeraré los diseños, son bastante obvios y puedes encontrarlos en la descarga del proyecto a continuación.
Actividad principal
Esto es bastante sencillo. Agregué una devolución de llamada a esta actividad para que sepa cuándo finalizó la tarea, pero es posible que no la necesite. Principalmente, solo quería mostrar el mecanismo de devolución de llamada por actividad de fragmento porque es bastante ordenado y es posible que no lo haya visto antes.
public class MainActivity extends Activity implements MainFragment.Callbacks
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onTaskFinished()
{
// Hooray. A toast to our success.
Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
// NB: I''m going to blow your mind again: the "int duration" parameter of makeText *isn''t*
// the duration in milliseconds. ANDROID Y U NO ENUM?
}
}
MainFragment
¡Es largo pero vale la pena!
public class MainFragment extends Fragment implements OnClickListener
{
// This code up to onDetach() is all to get easy callbacks to the Activity.
private Callbacks mCallbacks = sDummyCallbacks;
public interface Callbacks
{
public void onTaskFinished();
}
private static Callbacks sDummyCallbacks = new Callbacks()
{
public void onTaskFinished() { }
};
@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
if (!(activity instanceof Callbacks))
{
throw new IllegalStateException("Activity must implement fragment''s callbacks.");
}
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach()
{
super.onDetach();
mCallbacks = sDummyCallbacks;
}
// Save a reference to the fragment manager. This is initialised in onCreate().
private FragmentManager mFM;
// Code to identify the fragment that is calling onActivityResult(). We don''t really need
// this since we only have one fragment to deal with.
static final int TASK_FRAGMENT = 0;
// Tag so we can find the task fragment again, in another instance of this fragment after rotation.
static final String TASK_FRAGMENT_TAG = "task";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// At this point the fragment may have been recreated due to a rotation,
// and there may be a TaskFragment lying around. So see if we can find it.
mFM = getFragmentManager();
// Check to see if we have retained the worker fragment.
TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);
if (taskFragment != null)
{
// Update the target fragment so it goes to this fragment instead of the old one.
// This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
// keeps a reference to. Note that I looked in the code and setTargetFragment() doesn''t
// use weak references. To be sure you aren''t leaking, you may wish to make your own
// setTargetFragment() which does.
taskFragment.setTargetFragment(this, TASK_FRAGMENT);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
// Callback for the "start task" button. I originally used the XML onClick()
// but it goes to the Activity instead.
view.findViewById(R.id.taskButton).setOnClickListener(this);
}
@Override
public void onClick(View v)
{
// We only have one click listener so we know it is the "Start Task" button.
// We will create a new TaskFragment.
TaskFragment taskFragment = new TaskFragment();
// And create a task for it to monitor. In this implementation the taskFragment
// executes the task, but you could change it so that it is started here.
taskFragment.setTask(new MyTask());
// And tell it to call onActivityResult() on this fragment.
taskFragment.setTargetFragment(this, TASK_FRAGMENT);
// Show the fragment.
// I''m not sure which of the following two lines is best to use but this one works well.
taskFragment.show(mFM, TASK_FRAGMENT_TAG);
// mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
{
// Inform the activity.
mCallbacks.onTaskFinished();
}
}
TaskFragment
// This and the other inner class can be in separate files if you like.
// There''s no reason they need to be inner classes other than keeping everything together.
public static class TaskFragment extends DialogFragment
{
// The task we are running.
MyTask mTask;
ProgressBar mProgressBar;
public void setTask(MyTask task)
{
mTask = task;
// Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment.
mTask.setFragment(this);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Retain this instance so it isn''t destroyed when MainActivity and
// MainFragment change configuration.
setRetainInstance(true);
// Start the task! You could move this outside this activity if you want.
if (mTask != null)
mTask.execute();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_task, container);
mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar);
getDialog().setTitle("Progress Dialog");
// If you''re doing a long task, you probably don''t want people to cancel
// it just by tapping the screen!
getDialog().setCanceledOnTouchOutside(false);
return view;
}
// This is to work around what is apparently a bug. If you don''t have it
// here the dialog will be dismissed on rotation, so tell it not to dismiss.
@Override
public void onDestroyView()
{
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
// Also when we are dismissed we need to cancel the task.
@Override
public void onDismiss(DialogInterface dialog)
{
super.onDismiss(dialog);
// If true, the thread is interrupted immediately, which may do bad things.
// If false, it guarantees a result is never returned (onPostExecute() isn''t called)
// but you have to repeatedly call isCancelled() in your doInBackground()
// function to check if it should exit. For some tasks that might not be feasible.
if (mTask != null) {
mTask.cancel(false);
}
// You don''t really need this if you don''t want.
if (getTargetFragment() != null)
getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
}
@Override
public void onResume()
{
super.onResume();
// This is a little hacky, but we will see if the task has finished while we weren''t
// in this activity, and then we can dismiss ourselves.
if (mTask == null)
dismiss();
}
// This is called by the AsyncTask.
public void updateProgress(int percent)
{
mProgressBar.setProgress(percent);
}
// This is also called by the AsyncTask.
public void taskFinished()
{
// Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
// after the user has switched to another app.
if (isResumed())
dismiss();
// If we aren''t resumed, setting the task to null will allow us to dimiss ourselves in
// onResume().
mTask = null;
// Tell the fragment that we are done.
if (getTargetFragment() != null)
getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null);
}
}
Mi tarea
// This is a fairly standard AsyncTask that does some dummy work.
public static class MyTask extends AsyncTask<Void, Void, Void>
{
TaskFragment mFragment;
int mProgress = 0;
void setFragment(TaskFragment fragment)
{
mFragment = fragment;
}
@Override
protected Void doInBackground(Void... params)
{
// Do some longish task. This should be a task that we don''t really
// care about continuing
// if the user exits the app.
// Examples of these things:
// * Logging in to an app.
// * Downloading something for the user to view.
// * Calculating something for the user to view.
// Examples of where you should probably use a service instead:
// * Downloading files for the user to save (like the browser does).
// * Sending messages to people.
// * Uploading data to a server.
for (int i = 0; i < 10; i++)
{
// Check if this has been cancelled, e.g. when the dialog is dismissed.
if (isCancelled())
return null;
SystemClock.sleep(500);
mProgress = i * 10;
publishProgress();
}
return null;
}
@Override
protected void onProgressUpdate(Void... unused)
{
if (mFragment == null)
return;
mFragment.updateProgress(mProgress);
}
@Override
protected void onPostExecute(Void unused)
{
if (mFragment == null)
return;
mFragment.taskFinished();
}
}
}
Descargue el proyecto de ejemplo
Aquí está el código fuente y el APK . Lo siento, el ADT insistió en agregar la biblioteca de soporte antes de que me permitiera hacer un proyecto. Estoy seguro de que puedes eliminarlo.
Estoy trabajando en una aplicación para teléfono inteligente / tableta, usando solo una APK y cargando recursos según sea necesario según el tamaño de la pantalla, la mejor opción de diseño parece ser el uso de Fragmentos a través de la ACL.
Esta aplicación ha estado funcionando bien hasta ahora, solo se basa en actividades. Esta es una clase simulada de cómo manejo AsyncTasks y ProgressDialogs en las Actividades para que funcionen incluso cuando se gira la pantalla o cuando se produce un cambio de configuración en la comunicación media.
No cambiaré el manifiesto para evitar la recreación de la actividad, hay muchas razones por las que no quiero hacerlo, pero principalmente porque los documentos oficiales dicen que no es recomendable y lo he logrado sin llegar tan lejos, así que por favor no lo recomiendo. ruta.
public class Login extends Activity {
static ProgressDialog pd;
AsyncTask<String, Void, Boolean> asyncLoginThread;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.login);
//SETUP UI OBJECTS
restoreAsyncTask();
}
@Override
public Object onRetainNonConfigurationInstance() {
if (pd != null) pd.dismiss();
if (asyncLoginThread != null) return (asyncLoginThread);
return super.onRetainNonConfigurationInstance();
}
private void restoreAsyncTask();() {
pd = new ProgressDialog(Login.this);
if (getLastNonConfigurationInstance() != null) {
asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
if (asyncLoginThread != null) {
if (!(asyncLoginThread.getStatus()
.equals(AsyncTask.Status.FINISHED))) {
showProgressDialog();
}
}
}
}
public class LoginThread extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... args) {
try {
//Connect to WS, recieve a JSON/XML Response
//Place it somewhere I can use it.
} catch (Exception e) {
return true;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result) {
pd.dismiss();
//Handle the response. Either deny entry or launch new Login Succesful Activity
}
}
}
}
Este código funciona bien, tengo alrededor de 10.000 usuarios sin quejas, por lo que parecía lógico copiar esta lógica en el nuevo diseño basado en fragmentos, pero, por supuesto, no está funcionando.
Aquí está el LoginFragment:
public class LoginFragment extends Fragment {
FragmentActivity parentActivity;
static ProgressDialog pd;
AsyncTask<String, Void, Boolean> asyncLoginThread;
public interface OnLoginSuccessfulListener {
public void onLoginSuccessful(GlobalContainer globalContainer);
}
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
//Save some stuff for the UI State
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setRetainInstance(true);
//If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
parentActivity = getActivity();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
return loginLayout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//SETUP UI OBJECTS
if(savedInstanceState != null){
//Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
}
}
public class LoginThread extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... args) {
try {
//Connect to WS, recieve a JSON/XML Response
//Place it somewhere I can use it.
} catch (Exception e) {
return true;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result) {
pd.dismiss();
//Handle the response. Either deny entry or launch new Login Succesful Activity
}
}
}
}
}
No puedo utilizar onRetainNonConfigurationInstance()
ya que tiene que ser llamado desde la Actividad y no el Fragmento, lo mismo ocurre con getLastNonConfigurationInstance()
. He leído algunas preguntas similares aquí sin respuesta.
Entiendo que podría requerir un poco de trabajo para organizar todo esto adecuadamente en fragmentos, dicho esto, me gustaría mantener la misma lógica de diseño básico.
¿Cuál sería la forma correcta de retener el AsyncTask durante un cambio de configuración, y si aún se está ejecutando, mostrar un progressDialog, teniendo en cuenta que AsyncTask es una clase interna del Fragment y es el propio Fragment el que invoca el AsyncTask.execute ()?
Echa un vistazo here .
There is a solution based on Timmmm''s solution.
But I improved it:
Now the solution is extendable - you only need to extend
FragmentAbleToStartTask
You able to keep running several tasks at the same time.
Y en mi opinión es tan fácil como startActivityForResult y recibir resultado
También puede detener una tarea en ejecución y verificar si se está ejecutando determinada tarea
Lo siento por mi ingles
Los fragmentos pueden hacer esto mucho más fácil. Simplemente use el método Fragment.setRetainInstance(boolean) para tener su instancia de fragmento retenida en los cambios de configuración. Tenga en cuenta que este es el reemplazo recomendado para Activity.onRetainnonConfigurationInstance() en los documentos.
Si por alguna razón realmente no desea utilizar un fragmento retenido, existen otros enfoques que puede tomar. Tenga en cuenta que cada fragmento tiene un identificador único devuelto por Fragment.getId() . También puede averiguar si un fragmento se está derribando para un cambio de configuración a través de Fragment.getActivity().isChangingConfigurations() . Entonces, en el punto donde decidiría detener su AsyncTask (en onStop () o onDestroy () lo más probable), podría, por ejemplo, verificar si la configuración está cambiando y si es así pegarla en un SparseArray estático bajo el identificador del fragmento, y luego en onCreate () o onStart () mira para ver si tienes una AsyncTask en la dispersa matriz disponible.
Mi enfoque es utilizar un patrón de diseño de delegación; en general, podemos aislar la lógica empresarial real (leer datos de Internet o de base de datos o de cualquier otra forma) de AsyncTask (el delegador) a BusinessDAO (el delegado), en su método AysncTask.doInBackground () , delegue la tarea real a BusinessDAO, luego implemente un mecanismo de proceso singleton en BusinessDAO, de modo que una llamada múltiple a BusinessDAO.doSomething () simplemente desencadene una tarea real ejecutándose cada vez y esperando el resultado de la tarea. La idea es retener el delegado (es decir, BusinessDAO) durante el cambio de configuración, en lugar del delegador (es decir, AsyncTask).
Crear / Implementar nuestra propia Aplicación, el propósito es crear / inicializar BusinessDAO aquí, para que nuestro ciclo de vida de BusinessDAO tenga un alcance de aplicación, no alcance de actividad, tenga en cuenta que necesita cambiar AndroidManifest.xml para usar MyApplication:
public class MyApplication extends android.app.Application { private BusinessDAO businessDAO; @Override public void onCreate() { super.onCreate(); businessDAO = new BusinessDAO(); } pubilc BusinessDAO getBusinessDAO() { return businessDAO; } }
Nuestra Actividad / Fragmentación existente no se ha modificado, todavía implementa AsyncTask como clase interna e implica AsyncTask.execute () de Activity / Fragement, la diferencia ahora es que AsyncTask delegará la tarea real en BusinessDAO, por lo que durante el cambio de configuración, una segunda AsyncTask se inicializará y se ejecutará, y llamará a BusinessDAO.doSomething () por segunda vez; sin embargo, la segunda llamada a BusinessDAO.doSomething () no desencadenará una nueva tarea en ejecución, en cambio, esperando a que la tarea en ejecución finalice:
public class LoginFragment extends Fragment { ... ... public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> { // get a reference of BusinessDAO from application scope. BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO(); @Override protected Boolean doInBackground(String... args) { businessDAO.doSomething(); return true; } protected void onPostExecute(Boolean result) { //Handle task result and update UI stuff. } } ... ... }
Dentro de BusinessDAO, implemente el mecanismo de proceso singleton, por ejemplo:
public class BusinessDAO { ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1)); Future<MyTask> myFutureTask = null; public void doSomething() { if (myFutureTask == null) { // nothing running at the moment, submit a new callable task to run. MyTask myTask = new MyTask(); myFutureTask = completionExecutor.submit(myTask); } // Task already submitted and running, waiting for the running task to finish. myFutureTask.get(); } // If you''ve never used this before, Callable is similar with Runnable, with ability to return result and throw exception. private class MyTask extends Callable<MyTask> { public MyAsyncTask call() { // do your job here. return this; } } }
No estoy 100% seguro de que esto funcione; además, el fragmento de código de muestra se debe considerar como un seudocódigo. Solo intento darte alguna pista desde el nivel de diseño. Cualquier comentario o sugerencia son bienvenidos y apreciados.
Mi primera sugerencia es evitar las AsyncTasks internas , puedes leer una pregunta que hice acerca de esto y las respuestas: Android: recomendaciones de AsyncTask: clase privada o clase pública?
Después de eso comencé a usar no interna y ... ahora veo MUCHOS beneficios.
El segundo es mantener una referencia de su ejecución de AsyncTask en la clase de Application
: http://developer.android.com/reference/android/app/Application.html
Cada vez que inicie un AsyncTask, configúrelo en la Aplicación y cuando termine, configúrelo como nulo.
Cuando se inicia un fragmento / actividad, puede verificar si se está ejecutando alguna AsyncTask (comprobando si es nula o no en la Aplicación) y luego establecer la referencia dentro de lo que desee (actividad, fragmento, etc. para que pueda hacer devoluciones de llamada).
Esto resolverá su problema: si solo tiene 1 AsyncTask ejecutándose en un momento determinado, puede agregar una referencia simple:
AsyncTask<?,?,?> asyncTask = null;
De lo contrario, tenga en la Aplicación un HashMap con referencias a ellos.
El diálogo de progreso puede seguir exactamente el mismo principio.
Recientemente publiqué un artículo que describe cómo manejar cambios de configuración utilizando Fragment
retenidos. Resuelve muy bien el problema de conservar una AsyncTask
en un cambio de rotación.
El TL; DR debe usar el host de su AsyncTask
dentro de un Fragment
, invocar setRetainInstance(true)
en el Fragment
e informar el progreso / los resultados de la AsyncTask
a su Activity
(o su Fragment
destino, si elige utilizar el enfoque descrito por @Timmmm) a través del Fragment
retenido.
Se me ocurrió un método para usar AsyncTaskLoaders para esto. Es bastante fácil de usar y requiere menos gastos generales.
Básicamente, usted crea un AsyncTaskLoader así:
public class MyAsyncTaskLoader extends AsyncTaskLoader {
Result mResult;
public HttpAsyncTaskLoader(Context context) {
super(context);
}
protected void onStartLoading() {
super.onStartLoading();
if (mResult != null) {
deliverResult(mResult);
}
if (takeContentChanged() || mResult == null) {
forceLoad();
}
}
@Override
public Result loadInBackground() {
SystemClock.sleep(500);
mResult = new Result();
return mResult;
}
}
Luego, en su actividad que utiliza el AsyncTaskLoader anterior cuando se hace clic en un botón:
public class MyActivityWithBackgroundWork extends FragmentActivity implements LoaderManager.LoaderCallbacks<Result> {
private String username,password;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.mylayout);
//this is only used to reconnect to the loader if it already started
//before the orientation changed
Loader loader = getSupportLoaderManager().getLoader(0);
if (loader != null) {
getSupportLoaderManager().initLoader(0, null, this);
}
}
public void doBackgroundWorkOnClick(View button) {
//might want to disable the button while you are doing work
//to prevent user from pressing it again.
//Call resetLoader because calling initLoader will return
//the previous result if there was one and we may want to do new work
//each time
getSupportLoaderManager().resetLoader(0, null, this);
}
@Override
public Loader<Result> onCreateLoader(int i, Bundle bundle) {
//might want to start a progress bar
return new MyAsyncTaskLoader(this);
}
@Override
public void onLoadFinished(Loader<LoginResponse> loginLoader,
LoginResponse loginResponse)
{
//handle result
}
@Override
public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader)
{
//remove references to previous loader resources
}
}
Esto parece manejar cambios de orientación bien y su tarea de fondo continuará durante la rotación.
Algunas cosas a tener en cuenta:
- Si en onCreate te vuelves a conectar al asynctaskloader, se te devolverá la llamada en onLoadFinished () con el resultado anterior (incluso si ya se te había informado que la solicitud se había completado). Esto es en realidad un buen comportamiento la mayor parte del tiempo, pero a veces puede ser difícil de manejar. Aunque imagino que hay muchas formas de manejar esto, lo que hice fue llamar a loader.abandon () en onLoadFinished. Luego agregué check in en Crear para solo volver a conectar al cargador si aún no estaba abandonado. Si necesita los datos resultantes nuevamente, no querrá hacer eso. En la mayoría de los casos, quieres los datos.
Tengo más detalles sobre cómo usar esto para llamadas http here
Si alguien encuentra su camino hacia este hilo, descubrí que un enfoque limpio era ejecutar la tarea Async desde una app.Service
(comenzada con START_STICKY) y luego volver a iterar sobre los servicios en ejecución para averiguar si el servicio (y por lo tanto la tarea asíncrona ) todavía se está ejecutando;
public boolean isServiceRunning(String serviceClassName) {
final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
for (RunningServiceInfo runningServiceInfo : services) {
if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
return true;
}
}
return false;
}
Si es así, vuelva a agregar el DialogFragment
(o lo que sea) y, si no es así, asegúrese de que el diálogo haya sido descartado.
Esto es particularmente pertinente si usa las bibliotecas v4.support.*
Ya que (en el momento de redactar este documento) tienen problemas conocidos con el método setRetainInstance
y visualizan la paginación. Furthermore, by not retaining the instance you can recreate your activity using a different set of resources (ie a different view layout for the new orientation)
AsyncTask
biblioteca de tareas en segundo plano de código abierto muy pequeña que se basa en gran medida en Marshmallow AsyncTask
pero con funcionalidades adicionales tales como:
- Retención automática de tareas a través de cambios de configuración;
- Devolución de llamada UI (oyentes);
- No reinicia ni cancela la tarea cuando el dispositivo gira (como lo haría el cargador);
La biblioteca usa internamente un Fragment
sin interfaz de usuario, que se conserva en los cambios de configuración ( setRetainInstance(true)
).
Puede encontrarlo en GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks
Ejemplo más básico (versión 0.2.0):
Este ejemplo retiene completamente la tarea, usando una cantidad muy limitada de código.
Tarea:
private class ExampleTask extends Task<Integer, String> {
public ExampleTask(String tag){
super(tag);
}
protected String doInBackground() {
for(int i = 0; i < 100; i++) {
if(isCancelled()){
break;
}
SystemClock.sleep(50);
publishProgress(i);
}
return "Result";
}
}
Actividad:
public class Main extends TaskActivityCompat implements Task.Callback {
@Override
public void onClick(View view){
ExampleTask task = new ExampleTask("activity-unique-tag");
getTaskManager().execute(task, this);
}
@Override
public Task.Callback onPreAttach(Task<?, ?> task) {
//Restore the user-interface based on the tasks state
return this; //This Activity implements Task.Callback
}
@Override
public void onPreExecute(Task<?, ?> task) {
//Task started
}
@Override
public void onPostExecute(Task<?, ?> task) {
//Task finished
Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show();
}
}
Have a look at below example , how to use retained fragment to retain background task:
public class NetworkRequestFragment extends Fragment {
// Declare some sort of interface that your AsyncTask will use to communicate with the Activity
public interface NetworkRequestListener {
void onRequestStarted();
void onRequestProgressUpdate(int progress);
void onRequestFinished(SomeObject result);
}
private NetworkTask mTask;
private NetworkRequestListener mListener;
private SomeObject mResult;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Try to use the Activity as a listener
if (activity instanceof NetworkRequestListener) {
mListener = (NetworkRequestListener) activity;
} else {
// You can decide if you want to mandate that the Activity implements your callback interface
// in which case you should throw an exception if it doesn''t:
throw new IllegalStateException("Parent activity must implement NetworkRequestListener");
// or you could just swallow it and allow a state where nobody is listening
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this Fragment so that it will not be destroyed when an orientation
// change happens and we can keep our AsyncTask running
setRetainInstance(true);
}
/**
* The Activity can call this when it wants to start the task
*/
public void startTask(String url) {
mTask = new NetworkTask(url);
mTask.execute();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// If the AsyncTask finished when we didn''t have a listener we can
// deliver the result here
if ((mResult != null) && (mListener != null)) {
mListener.onRequestFinished(mResult);
mResult = null;
}
}
@Override
public void onDestroy() {
super.onDestroy();
// We still have to cancel the task in onDestroy because if the user exits the app or
// finishes the Activity, we don''t want the task to keep running
// Since we are retaining the Fragment, onDestroy won''t be called for an orientation change
// so this won''t affect our ability to keep the task running when the user rotates the device
if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) {
mTask.cancel(true);
}
}
@Override
public void onDetach() {
super.onDetach();
// This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity)
// When the orientation change occurs, onDetach will be called and since the Activity is being destroyed
// we don''t want to keep any references to it
// When the Activity is being re-created, onAttach will be called and we will get our listener back
mListener = null;
}
private class NetworkTask extends AsyncTask<String, Integer, SomeObject> {
@Override
protected void onPreExecute() {
if (mListener != null) {
mListener.onRequestStarted();
}
}
@Override
protected SomeObject doInBackground(String... urls) {
// Make the network request
...
// Whenever we want to update our progress:
publishProgress(progress);
...
return result;
}
@Override
protected void onProgressUpdate(Integer... progress) {
if (mListener != null) {
mListener.onRequestProgressUpdate(progress[0]);
}
}
@Override
protected void onPostExecute(SomeObject result) {
if (mListener != null) {
mListener.onRequestFinished(result);
} else {
// If the task finishes while the orientation change is happening and while
// the Fragment is not attached to an Activity, our mListener might be null
// If you need to make sure that the result eventually gets to the Activity
// you could save the result here, then in onActivityCreated you can pass it back
// to the Activity
mResult = result;
}
}
}
}
I write samepl code to solve this problem
First step is make Application class:
public class TheApp extends Application {
private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();
@Override
public void onCreate() {
super.onCreate();
sTheApp = this;
}
public static TheApp get() {
return sTheApp;
}
public void registerTask(String tag, AsyncTask<?,?,?> task) {
tasks.put(tag, task);
}
public void unregisterTask(String tag) {
tasks.remove(tag);
}
public AsyncTask<?,?,?> getTask(String tag) {
return tasks.get(tag);
}
}
In AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:name="com.example.tasktest.TheApp">
Code in activity:
public class MainActivity extends Activity {
private Task1 mTask1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTask1 = (Task1)TheApp.get().getTask("task1");
}
/*
* start task is not running jet
*/
public void handletask1(View v) {
if (mTask1 == null) {
mTask1 = new Task1();
TheApp.get().registerTask("task1", mTask1);
mTask1.execute();
} else
Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show();
}
/*
* cancel task if is not finished
*/
public void handelCancel(View v) {
if (mTask1 != null)
mTask1.cancel(false);
}
public class Task1 extends AsyncTask<Void, Void, Void>{
@Override
protected Void doInBackground(Void... params) {
try {
for(int i=0; i<120; i++) {
Thread.sleep(1000);
Log.i("tests", "loop=" + i);
if (this.isCancelled()) {
Log.e("tests", "tssk cancelled");
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onCancelled(Void result) {
TheApp.get().unregisterTask("task1");
mTask1 = null;
}
@Override
protected void onPostExecute(Void result) {
TheApp.get().unregisterTask("task1");
mTask1 = null;
}
}
}
When activity orientation changes variable mTask is inited from app context. When task is finished variable is set to null and remove from memory.
For me its enough.
You could make the AsyncTask a static field. If you need a context, you should ship your application context. This will avoid memory leaks, otherwise you''d keep a reference to your entire activity.