Pregunta

He leído mucho sobre la manera de salvar mi estado de la instancia o la forma de lidiar con mi actividad de ser destruido durante la rotación de la pantalla.

Parece que hay una gran cantidad de posibilidades pero no he descubierto cuál funciona mejor para recuperar resultados de una AsyncTask.

Tengo algunas AsyncTasks que simplemente se inician de nuevo y llamar al método isFinishing() de la actividad y si la actividad está terminando ellos no van actualizar nada.

El problema es que tengo una tarea que hace una petición a un servicio web que puede fallar o tener éxito y reiniciar la tarea resultaría en una pérdida financiera para el usuario.

¿Cómo solucionar esto? ¿Cuáles son las ventajas o desventajas de las posibles soluciones?

¿Fue útil?

Solución

Mi primera sugerencia sería la de asegurarse de que realmente necesitan su actividad a restablecerse en una rotación de la pantalla (el comportamiento por defecto). Cada vez que he tenido problemas con la rotación He añadido este atributo a la etiqueta <activity> en el AndroidManifest.xml, y ha estado muy bien.

android:configChanges="keyboardHidden|orientation"

Parece raro, pero lo que lo hace a mano de su método de onConfigurationChanged(), si no se proporciona uno simplemente lo hace otra cosa que volver a medir la distribución, lo que parece ser una forma perfectamente adecuada de manejar la rotación la mayor parte del tiempo.

Otros consejos

Se puede extraer de cómo manejo AsyncTasks y cambios de orientación en code.google.com/p/shelves . Hay varias maneras de hacerlo, el que yo elegí en esta aplicación es cancelar cualquier tarea actualmente en ejecución, guardar su estado y comenzar uno nuevo con el estado guardado cuando se crea el nuevo Activity. Es fácil de hacer, funciona bien y como un bono que se encarga de detener sus tareas cuando las hojas de los usuarios de la aplicación.

También puede utilizar onRetainNonConfigurationInstance() para pasar su AsyncTask a la nueva Activity (tenga cuidado de no filtrar el Activity anterior esta manera sin embargo.)

Esta es la pregunta más interesante que he visto con respecto a Android !!! De hecho, he estado ya en busca de la solución durante los últimos meses. Todavía no se han resuelto.

Tenga cuidado, simplemente reemplazando el

android:configChanges="keyboardHidden|orientation"

cosas no es suficiente.

Considere el caso cuando el usuario recibe una llamada telefónica mientras el AsyncTask se está ejecutando. Su petición ya está siendo procesado por servidor, por lo que el AsyncTask está a la espera de respuesta. En este momento en que su aplicación va en segundo plano, ya que la aplicación de teléfono acaba de llegar en primer plano. OS puede matar a su actividad ya que está en el fondo.

¿Por qué no siempre guarda una referencia a la AsyncTask actual en el Singleton proporcionada por Android?

Siempre que se inicia una tarea, en PreExecute o en el constructor, que definen:

((Application) getApplication()).setCurrentTask(asyncTask);

Cada vez que termine lo establece en nulo.

De esta manera siempre tiene una referencia que le permite hacer algo así, onCreate o onResume como apropiado para su lógica específica:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

Si es nula que saben que en la actualidad no hay ninguno en funcionamiento!

: -)

La forma más adecuada para esto es utilizar un fragmento de retener la instancia de la tarea asíncrona, más rotaciones.

Aquí hay un enlace a ejemplo muy simple por lo que es fácil de seguir integrar esta técnica en sus aplicaciones.

https://gist.github.com/daichan4649/2480065

En Pro android 4. autor tiene sugerir una manera agradable, que se debe utilizar weak reference.

débil nota de referencia

Para mi punto de vista, es mejor a la tienda a través de AsyncTask onRetainNonConfigurationInstance desacoplamiento desde el objeto de actividad actual y enlazarlo a un nuevo objeto de actividad después del cambio de orientación. aquí me encontré con un muy buen ejemplo de cómo trabajar con AsyncTask y ProgressDialog.

Android: procesamiento de fondo / asíncrono opeartion con cambio de configuración

Para mantener los estados de opeartion asíncrono durante el proceso de fondo: se puede tomar una ayuda de fragmentos.

Consulte los siguientes pasos:

. Paso 1: Crear un fragmento sin cabeceras permiten decir tarea de fondo y añadir una clase de tarea asíncrona privado con en ella

Paso 2 (Paso opcional): si usted quiere poner un cursor de carga en la parte superior para el uso por la actividad de código:

Paso 3: En su actividad principal implementar BackgroundTaskCallbacks interfaz definida en el paso 1

class BackgroundTask extends Fragment {
public BackgroundTask() {

}

// Add a static interface 

static interface BackgroundTaskCallbacks {
    void onPreExecute();

    void onCancelled();

    void onPostExecute();

    void doInBackground();
}

private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();

/**
 * Start the async operation.
 */
public void start() {
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
    if (!isRunning) {
        asyncOperation = new PerformAsyncOpeation();
        asyncOperation.execute();
        isRunning = true;
    }
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}

/**
 * Cancel the background task.
 */
public void cancel() {
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
    if (isRunning) {
        asyncOperation.cancel(false);
        asyncOperation = null;
        isRunning = false;
    }
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}

/**
 * Returns the current state of the background task.
 */
public boolean isRunning() {
    return isRunning;
}

/**
 * Android passes us a reference to the newly created Activity by calling
 * this method after each configuration change.
 */
public void onAttach(Activity activity) {
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
    super.onAttach(activity);
    if (!(activity instanceof BackgroundTaskCallbacks)) {
        throw new IllegalStateException(
                "Activity must implement the LoginCallbacks interface.");
    }

    // Hold a reference to the parent Activity so we can report back the
    // task's
    // current progress and results.
    callbacks = (BackgroundTaskCallbacks) activity;
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}

public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}

public void onDetach() {
    super.onDetach();
    callbacks = null;
}

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
    protected void onPreExecute() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPreExecute();
        }
        isRunning = true;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
    }

    protected Void doInBackground(Void... params) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
        if (callbacks != null) {
            callbacks.doInBackground();
        }
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
        return null;
    }

    protected void onCancelled() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
        if (callbacks != null) {
            callbacks.onCancelled();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
    }

    protected void onPostExecute(Void ignore) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPostExecute();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
    }
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
}

public void onStart() {
    super.onStart();
}

public void onResume() {
    super.onResume();
}

public void onPause() {
    super.onPause();
}

public void onStop() {
    super.onStop();
}

public class ProgressIndicator extends Dialog {

public ProgressIndicator(Context context, int theme) {
    super(context, theme);
}

private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.progress_indicator);
    this.setCancelable(false);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}

@Override
public void show() {
    super.show();
}

@Override
public void dismiss() {
    super.dismiss();
}

@Override
public void cancel() {
    super.cancel();
}

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{

private static final String KEY_CURRENT_PROGRESS = "current_progress";

ProgressIndicator progressIndicator = null;

private final static String TAG = MyActivity.class.getSimpleName();

private BackgroundTask task = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(//"set your layout here");
    initialize your views and widget here .............



    FragmentManager fm = getSupportFragmentManager();
    task = (BackgroundTask) fm.findFragmentByTag("login");

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (task == null) {
        task = new BackgroundTask();
        fm.beginTransaction().add(task, "login").commit();
    }

    // Restore saved state
    if (savedInstanceState != null) {
        Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
                + task.isRunning());
        if (task.isRunning()) {
            progressIndicator = new ProgressIndicator(this,
                    R.style.TransparentDialog);
            if (progressIndicator != null) {
                progressIndicator.show();
            }
        }
    }
}

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState);
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
            + task.isRunning());
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}


private void performOperation() {

            if (!task.isRunning() && progressIndicator == null) {
                progressIndicator = new ProgressIndicator(this,
                        R.style.TransparentDialog);
                progressIndicator.show();
            }
            if (task.isRunning()) {
                task.cancel();
            } else {
                task.start();
            }
        }


@Override
protected void onDestroy() {
    super.onDestroy();
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}

@Override
public void onPreExecute() {
    Log.i(TAG, "CALLING ON PRE EXECUTE");
}

@Override
public void onCancelled() {
    Log.i(TAG, "CALLING ON CANCELLED");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();

}

public void onPostExecute() {
    Log.i(TAG, "CALLING ON POST EXECUTE");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
        progressIndicator = null;
    }
}

@Override
public void doInBackground() {
    // put your code here for background operation
}

}

Una cosa a considerar es si el resultado de la AsyncTask debería estar disponible sólo a la actividad que se inició la tarea. Si es así, de Romain individuo respuesta es mejor. Si debe estar disponible para otras actividades de su aplicación, a continuación, en onPostExecute puede utilizar LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

También tendrá que asegurarse de que la actividad maneja correctamente situación en la difusión se envía mientras está en pausa actividad.

Tener un vistazo a este publicación . Esta función implica la realización de AsyncTask operación larga y pérdida de memoria cuando se ejecuta la rotación de pantalla sucede tanto en una aplicación de ejemplo. La aplicación de la muestra está disponible en el forja fuente

Mi solución.

En mi caso tengo una cadena de AsyncTasks con el mismo contexto. La actividad tuvo un acceso sólo a primera. Para cancelar cualquier tarea en ejecución hice lo siguiente:

public final class TaskLoader {

private static AsyncTask task;

     private TaskLoader() {
         throw new UnsupportedOperationException();
     }

     public static void setTask(AsyncTask task) {
         TaskLoader.task = task;
     }

    public static void cancel() {
         TaskLoader.task.cancel(true);
     }
}

doInBackground() Tarea:

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

onStop() Actividad o onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    final AddTask task = mAddTask;
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
        final String bookId = task.getBookId();
        task.cancel(true);

        if (bookId != null) {
            outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
            outState.putString(STATE_ADD_BOOK, bookId);
        }

        mAddTask = null;
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
        final String id = savedInstanceState.getString(STATE_ADD_BOOK);
        if (!BooksManager.bookExists(getContentResolver(), id)) {
            mAddTask = (AddTask) new AddTask().execute(id);
        }
    }
}

también se puede añadir Android: configChanges = "keyboardHidden | Orientación | ScreenSize"

a tu ejemplo manifiesta espero que ayuda

 <application
    android:name=".AppController"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:theme="@style/AppTheme">
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top