Pregunta

Estoy trabajando en un Smartphone / Tablet de la aplicación, utilizando sólo una APK, y la carga de recursos como sea necesario en función del tamaño de pantalla, la mejor opción de diseño que parecía ser el uso de Fragmentos a través de la ACL.

Esta aplicación ha estado trabajando muy bien hasta ahora sólo la actividad de base.Este es un simulacro de clase de cómo manejar AsyncTasks y ProgressDialogs en las Actividades para el trabajo, incluso cuando la pantalla está rota o un cambio en la configuración se produce a mediados de la comunicación.

No voy a cambiar el manifiesto para evitar la recreación de la Actividad, hay muchas razones por las que no quiero hacerlo, pero principalmente porque el oficial docs dicen que no es recomendable y me las he arreglado sin ella esta lejos, así que por favor no recomiendo esa 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 está funcionando bien, tengo alrededor de 10.000 usuarios sin queja, por lo que parece lógico que se acaba de copiar esta lógica en el nuevo Fragmento de Base de Diseño, pero, por supuesto, no es trabajo.

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() porque tiene que ser llamado a partir de la Actividad y no el Fragmento, lo mismo sucede con las getLastNonConfigurationInstance().He leído algunas preguntas similares aquí con ninguna respuesta.

Entiendo que esto puede requerir algo de trabajo para obtener este material se organiza debidamente en fragmentos, que se dice, me gustaría mantener el mismo diseño básico de la lógica.

¿Cuál sería la forma adecuada para retener el AsyncTask durante un cambio en la configuración, y si aun runing, muestran un progressDialog, tomando en consideración que el AsyncTask es una clase interna para el Fragmento y es el Fragmento de sí mismo que invoca el AsyncTask.execute()?

¿Fue útil?

Solución

Los fragmentos pueden hacer esto mucho más fácil. Simplemente use el método Fragment.SetRetainInstance (Boolean) para tener su fragmento instancia retenida en los cambios de configuración. Tenga en cuenta que este es el reemplazo recomendado para Activity.onRetainNonconConfigurationInstance () en los docs.

Si por alguna razón, realmente no quieres usar un fragmento retenido, hay otros enfoques que puede tomar. Tenga en cuenta que cada fragmento tiene un identificador único devuelto por fragment.getid () . También puede averiguarlo si se está desgargando un fragmento para un cambio de configuración a través de fragmento .getactivity (). isCschangingConfigurations () . Por lo tanto, en el punto donde decidiría detener su asintsk (en OnStop () o OnDestroy (), lo más probable es que, por ejemplo, podría verificar si la configuración está cambiando y, si es así, pegarlo en un SparseArray estático bajo el identificador de Fragment, y luego en su OnCreate () o OnStart () Busque ver si tiene una SASTCASK en la matriz SPARE disponible.

Otros consejos

Creo que va a disfrutar mi muy completo y ejemplo de trabajo se detalla a continuación.

  1. La rotación de las obras, y el diálogo sobrevive.
  2. Usted puede cancelar la tarea y de diálogo pulsando el botón atrás (si se desea este comportamiento).
  3. Utiliza fragmentos.
  4. El diseño del fragmento debajo de los cambios de actividad correctamente cuando el dispositivo se gira.
  5. Hay un código fuente completo de descarga y un precompilado APK así que usted puede ver si el comportamiento es lo que quieres.

Editar

Según lo solicitado por Brad Larson he reproducido más de la solución a continuación.También desde que me envió, él me ha señalado AsyncTaskLoader.No estoy seguro de que es totalmente aplicable a los mismos problemas, pero usted debe comprobar fuera de todos modos.

El uso de AsyncTask con cuadros de diálogo de progreso y la rotación del dispositivo.

Una solución de trabajo!

Por fin he conseguido que todo funcione.Mi código tiene las siguientes características:

  1. Un Fragment cuyos cambios en el diseño con la orientación.
  2. Un AsyncTask en el que puede hacer algo de trabajo.
  3. Un DialogFragment que muestra el progreso de la tarea en una barra de progreso (no sólo un indeterminado spinner).
  4. La rotación funciona sin interrumpir la tarea o descartar el cuadro de diálogo.
  5. El botón atrás cierra el cuadro de diálogo y se cancela la tarea (puede modificar este comportamiento con bastante facilidad, aunque).

No creo que la combinación de workingness se puede encontrar en cualquier otro lugar.

La idea básica es la siguiente.Hay un MainActivity clase que contiene un único fragmento - MainFragment. MainFragment tiene diferentes diseños para la orientación horizontal y vertical, y setRetainInstance() es falso por lo que el diseño puede cambiar.Esto significa que cuando la orientación del dispositivo es cambiado, tanto MainActivity y MainFragment son completamente destruido y recreado.

Por separado tenemos MyTask (extendido a partir de la AsyncTask) que hace todo el trabajo.No podemos almacenarlo en MainFragment debido a que será destruido, y Google ha dejado de utilizar el uso de algo como setRetainNonInstanceConfiguration().Que no siempre está disponible de todos modos y es un feo hack en el mejor.En su lugar vamos a la tienda MyTask en otro fragmento, un DialogFragment llama TaskFragment. Este fragmento se han setRetainInstance() se establece en true, así como el dispositivo de gira este fragmento no es destruida, y MyTask se conserva.

Por último, necesitamos decirle a la TaskFragment que informar cuando está terminado, y hacemos que el uso de setTargetFragment(<the MainFragment>) cuando la creamos.Cuando se gira el dispositivo y el MainFragment es destruido y una nueva instancia es creada, hacemos uso de la FragmentManager para encontrar el cuadro de diálogo (basado en su etiqueta) y hacer setTargetFragment(<the new MainFragment>).Eso es prácticamente todo.

Hay otras dos cosas que necesitaba hacer:primero cancelar la tarea cuando el diálogo es despedido, y el segundo set descartar mensaje a nulo, de lo contrario el diálogo es extrañamente despedidos cuando se gira el dispositivo.

El código

No voy a enumerar los diseños, que son bastante evidentes y se pueden encontrar en el proyecto de descarga a continuación.

MainActivity

Esto es bastante sencillo.He añadido una devolución de llamada en esta actividad por lo que se sabe cuando la tarea está terminada, pero puede que no necesite que.Principalmente yo sólo quería mostrar el fragmento de la actividad del mecanismo de devolución de llamada porque es bastante claro y es posible que no lo han 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);
        }
    }

MyTask

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

Descargar el proyecto de ejemplo

Aquí está el código fuente y el APK.Lo sentimos, el ADT insistió en sumar el apoyo de la biblioteca antes de que se me permitiera hacer un proyecto.Estoy seguro de que usted puede eliminar.

Recientemente he publicado un artículo describir cómo manejar los cambios de configuración utilizando retenido Fragments.Se resuelve el problema de la retención de un AsyncTask a través de una rotación de cambio muy bien.

El TL;DR es el uso de host de su AsyncTask en el interior de un Fragment, llame setRetainInstance(true) en el Fragment, y el informe de la AsyncTaskprogreso y resultados de volver a la Activity (o es blanco Fragment, si usted elige utilizar el método descrito por @Timmmm) a través de la conserva Fragment.

Mi primera sugerencia es para Evitar la OSNTCASTAS DE ASInctas internas , puede leer una pregunta que le pregunté sobre esto y las respuestas: Android: Asynctask Recomendaciones: Clase privada o clase pública?

Después de eso, comencé a usar no interno y ... ahora veo muchos beneficios.

El segundo es, mantenga una referencia a su ejecución de asincpresa en la clase de Application, http://developer.android.com/reference/android/app/application.html

Cada vez que inicia una Sescask, configúrela en la aplicación y cuando termine, configúrela en NULL.

Cuando se inicia un fragmento / actividad, puede verificar si se ejecuta alguna SInctCask (comprobando si es nula o no en la aplicación) y luego configura 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 asincrask en cualquier 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 cuadro de diálogo de progreso puede seguir exactamente el mismo principio.

Se me ocurrió un método para usar AsintsTaskleskleskles para esto. Es bastante fácil de usar y requiere menos Overhead imo ..

Básicamente, creas un ASInctaskloader como este:

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 usa el cargador AsintsPaster 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 los cambios de orientación multa y su tarea de antecedentes continuará durante la rotación.

Algunas cosas para notar:

  1. Si en OnCreate te reincide con el ASInctaskloader, volverá a llamar en OnLloadfinished () con el resultado anterior (incluso si ya se le había dicho que la solicitud estaba completa). Este es en realidad un buen comportamiento la mayor parte del tiempo, pero a veces puede ser complicado para manejar. Mientras me imagino que hay muchas maneras de manejar esto lo que hice fue que llamé a loader.abandon () en OnLoadfinished. Luego agregué check in Oncreate para que solo teattach al cargador si aún no estuviera abandonado. Si necesita los datos resultantes nuevamente, no querrá hacerlo. En la mayoría de los casos desea los datos.

    Tengo más detalles sobre el uso de esto para las llamadas HTTP aquí

He creado una pequeña fuente abierta de la tarea en segundo plano de la biblioteca que está fuertemente basado en el Malvavisco AsyncTask pero con funciones adicionales tales como:

  1. Automáticamente la retención de tareas a través de los cambios de configuración;
  2. Interfaz de usuario de devolución de llamada (oyentes);
  3. No reiniciar o cancelar la tarea cuando el dispositivo se gira (como Cargadores haría);

La biblioteca utiliza internamente un Fragment sin ninguna interfaz de usuario, que se conserva a través de los cambios de configuración (setRetainInstance(true)).

Usted puede encontrar en GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks

Ejemplo más básico (versión 0.2.0):

Este ejemplo totalmente conserva la tarea, el uso de 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();
    }
}

Mi enfoque es usar el patrón de diseño de la delegación, en general, podemos aislar la lógica de negocios real (leer datos de Internet o la base de datos o en absoluto) de AsyncTask (el delegador) a BusinessDAO (el delegado), en su Aysncask.doinbackground () Método, delegue la tarea real a BusinessDAO, luego implemente un mecanismo de proceso de Singleton en el BusinessDAO, de modo que la llamada múltiple a BusinessDAO.DOSA ALIGHT () simplemente activará una tarea real en funcionamiento cada vez y esperando el resultado de la tarea. La idea es retener al delegado (es decir, BusinessDAO) durante el cambio de configuración, en lugar del delegador (es decir, asintsk).

  1. Crear / implementar nuestra propia aplicación, el propósito es crear / inicializar BusinessDAO aquí, de modo que el ciclo de vida de nuestro BusinessDAO se encuentre al alcance de la aplicación, no la actividad al alcance de la 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;
      }
    
    }
    

  2. Nuestra actividad / fragmacia existente se mantiene sin cambios, aún implementa Asincasques como una clase interna e involucra a asinctast.Execute () de la actividad / frontal, la diferencia ahora es Asintsk delegará la tarea real a BusinessDAO, por lo que durante el CAMBIO DE CONFIGURACIÓN, se inicializará y se ejecutará una segunda máscara de asintset, y llamará a BusinessDAO.DoSomething () Segunda vez, sin embargo, la segunda llamada a BusinessDao.Dosomething () no activará una nueva tarea de ejecución, en cambio, esperando que finalice la tarea de ejecución actual:

    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.
        }
      }
    
      ... ...
    }
    

  3. Dentro de BusinessDAO, implemente un mecanismo de proceso de 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 funcionará, además, el fragmento del código de muestra debe considerarse como Pseudocódigo. Solo estoy tratando de darte una pista del nivel de diseño. Cualquier retroalimentación o sugerencia es bienvenida y apreciada.

Podrías hacer el campo estático Asynctask.Si necesita un contexto, debe enviar su contexto de solicitud.Esto evitará las fugas de la memoria, de lo contrario, mantendría una referencia a toda su actividad.

Si alguien encuentra su camino a este hilo, a continuación, me encontré con un limpio enfoque para ejecutar la tarea Asíncrona de un app.Service (comenzó con START_STICKY) y, a continuación, en recrear iterar a través de la ejecución de servicios para averiguar si el servicio (y por lo tanto asíncrona de la tarea), 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 la de garantizar el diálogo ha sido despedido.

Esto es especialmente importante si usted está utilizando el v4.support.* las bibliotecas ya que (en el momento de la escritura) que tienen problemas con el setRetainInstance método y de la vista de paginación. Además, al no retener la instancia puede volver a su actividad utilizando un conjunto diferente de los recursos (es decir,una visión diferente de diseño para la nueva orientación)

Escribo el código SIGUIENTE para resolver este problema

El primer paso es hacer clase de aplicación:

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

en 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">

Código en la actividad:

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

}

Cuando la orientación de la actividad cambia la mtask de la variable se encuentra con el contexto de la aplicación.Cuando la tarea se termina la variable está configurada en NULL y retire de la memoria.

para mí es suficiente.

Eche un vistazo a continuación, cómo usar el fragmento retenido para retener la tarea de fondo:

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

    }
}

Tener una apariencia aquí .

Hay una solución basada en la solución de Timmmmm.

pero lo mejoré:

  • Ahora la solución es extensible: solo necesita extender FragmentAbleToStartTask

  • Puede seguir ejecutando varias tareas al mismo tiempo.

    Y en mi opinión, es tan fácil como StartactivityForResult y recibir el resultado

  • También puede detener una tarea en ejecución y verificar si la tarea particular se está ejecutando

    Perdón por mi inglés

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top