Pergunta

Eu estou trabalhando em um aplicativo de Smartphone / Tablet, usando apenas um APK e o carregamento de recursos, como é necessário, dependendo do tamanho da tela, melhor escolha de design que parecia estar usando Fragmentos através da ACL.

Esta aplicação tem sido funcionando bem até agora a ser apenas baseada em actividades.Esta é uma simulação de uma aula de como lidar com AsyncTasks e ProgressDialogs nas Atividades para que eles funcionam mesmo quando a tela é girada ou uma alteração de configuração ocorre de meados de comunicação.

Eu não vou mudar o manifesto para evitar a recriação da Atividade, há muitas razões por que eu não quero fazer isso, mas principalmente porque o oficial docs dizer que não é recomendado e eu consegui sem ele esta longe, então por favor, não recomendo essa rota.

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 é um trabalho muito bem, eu tenho em torno de 10.000 usuários, sem queixa, então, parecia lógico basta copiar essa lógica para o novo Fragmento de Base de Projeto, mas, claro, não de trabalho.

Aqui é o 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
                }
            }
        }
    }
}

Eu não posso usar onRetainNonConfigurationInstance() desde então ele tem que ser chamado a partir da Atividade e não o Fragmento, mesmo se passa com o getLastNonConfigurationInstance().Eu li algumas perguntas semelhantes aqui sem resposta.

Eu entendo que ele possa ter algum trabalho para se obter este material devidamente organizado em fragmentos, que está sendo dito, eu gostaria de manter o mesmo projeto básico de lógica.

Qual seria a forma adequada para manter o AsyncTask durante uma alteração de configuração, e se a sua ainda runing, mostrar um progressDialog, levando em consideração que o AsyncTask é uma classe interna para o Fragmento e é o Fragmento em si, que invoca o AsyncTask.execute()?

Foi útil?

Solução

Fragmentos realmente pode fazer isso muito mais fácil.Basta usar o método de Fragmento.setRetainInstance(boolean) para ter o seu fragmento de instância mantidos através de alterações de configuração.Note que esta é a substituição recomendada para A atividade.onRetainnonConfigurationInstance() no docs.

Se por algum motivo você realmente não deseja usar um mantidas fragmento, há outras abordagens que você pode tomar.Note que cada fragmento tem um único identificador retornado pelo Fragmento.getId().Você também pode descobrir se um fragmento está sendo demolido para uma mudança de configuração através de Fragmento.getActivity().isChangingConfigurations().Assim, no ponto em que você decidir parar o seu AsyncTask (em onStop() ou onDestroy() mais provável), você poderia, por exemplo, verifique se a configuração está mudando e se assim colocá-lo em uma estático SparseArray sob o fragmento identificador e, em seguida, em seu onCreate() ou onStart() olhar para ver se você tem uma AsyncTask na matriz esparsa disponíveis.

Outras dicas

Eu acho que você vai curtir minha extremamente abrangente e exemplo de trabalho detalhado a seguir.

  1. Rotação de obras, e o diálogo sobrevive.
  2. Você pode cancelar a tarefa e a caixa de diálogo pressionando o botão voltar (se você deseja que este comportamento).
  3. Ele usa fragmentos.
  4. O layout do fragmento abaixo as mudanças de actividade corretamente quando o dispositivo gira.
  5. Há uma completa download do código fonte e um ficheiro APK assim você pode ver se o comportamento é o que você quer.

Editar

Conforme solicitado por Sérgio Larson tenho reproduzido a maioria dos links a solução abaixo.Também desde que eu postei eu ter sido apontado AsyncTaskLoader.Eu não tenho certeza de que ele é totalmente aplicável para os mesmos problemas, mas você deve verificar para fora de qualquer maneira.

Usando AsyncTask com caixas de diálogo de progresso e dispositivo de rotação.

Uma solução de trabalho!

Eu finalmente tenho tudo a funcionar.Meu código tem as seguintes características:

  1. Um Fragment cujas mudanças de layout de orientação.
  2. Um AsyncTask em que você pode fazer algum trabalho.
  3. Um DialogFragment o que mostra o progresso da tarefa em uma barra de progresso (e não apenas indeterminado giratório).
  4. Rotação funciona sem interromper a tarefa ou descartar a caixa de diálogo.
  5. O botão voltar descarta o diálogo e cancela a tarefa (você pode alterar este comportamento bastante facilidade embora).

Eu não acho que a combinação de workingness pode ser encontrado em qualquer outro lugar.

A idéia básica é a seguinte.Há um MainActivity classe que contém um único fragmento - MainFragment. MainFragment tem layouts diferentes para horizontal e orientação vertical, e setRetainInstance() é falso, para que o layout pode mudar.Isso significa que, quando a orientação do dispositivo é alterada, tanto MainActivity e MainFragment são completamente destruído e recriado.

Separadamente, temos MyTask (prorrogado a partir de AsyncTask) que faz todo o trabalho.Não podemos armazená-lo em MainFragment porque o que vai ser destruído, e o Google tem depreciado usando qualquer coisa como setRetainNonInstanceConfiguration().Que nem sempre é disponível de qualquer maneira e é um feio corte na melhor das hipóteses.Em vez disso, vamos guardar MyTask em outro fragmento, um DialogFragment chamado TaskFragment. Este fragmento de vai tem setRetainInstance() definido como true, assim como o dispositivo gira este fragmento não é destruído, e MyTask é mantida.

Finalmente, precisamos saber o TaskFragment a quem informar quando ele for concluído, e fazemos isso usando setTargetFragment(<the MainFragment>) quando a gente cria.Quando o dispositivo é girada e a MainFragment é destruída e uma nova instância é criada, usamos o FragmentManager para localizar a caixa de diálogo (baseado em seu tag) e fazer setTargetFragment(<the new MainFragment>).Isso é muito bonito.

Havia duas outras coisas que eu precisava fazer:primeiro cancelar a tarefa quando a caixa de diálogo é descartada, e o segundo conjunto descartar a mensagem para nulo, caso contrário, o diálogo é muito estranho, dispensada quando o aparelho é girado.

O código

Eu não lista os layouts, eles são bastante óbvias e você pode encontrá-los no projeto de download abaixo.

MainActivity

Isso é muito simples.Eu adicionei uma chamada de retorno para esta atividade, portanto, ele sabe que quando a tarefa é concluída, mas você pode não precisar disso.Principalmente, eu só queria mostrar o fragmento-atividade mecanismo de retorno de chamada, porque é bem arrumado e talvez você não tenha 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

É longo mas vale a 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();
        }
    }
}

Download do projeto de exemplo

Aqui é o código-fonte e o APK.Desculpe, o ADT insistiu sobre como adicionar a biblioteca de suporte antes de ele me deixar fazer um projeto.Tenho certeza de que você pode removê-lo.

Eu recentemente postou um artigo descrevendo como lidar com as alterações de configuração usando retidos Fragments.Ele resolve o problema de retenção de um AsyncTask através de uma mudança de rotação muito bem.

TL;DR é para usar o host de seu AsyncTask dentro de um Fragment, chamada de setRetainInstance(true) no Fragment, e o relatório AsyncTask's de progresso/resultados de volta para ele Activity (ou é alvo Fragment, se você optar por utilizar o método descrito por @Timmmm) através retidos Fragment.

Minha primeira sugestão é evitar interior AsyncTasks, você pode ler uma pergunta que eu perguntei sobre isso e as respostas: Android:AsyncTask recomendações:aula particular ou público de classe?

Depois que comecei a usar não-interior e...agora eu vejo UM MONTE de benefícios.

A segunda é manter uma referência de sua execução AsyncTask no Application Classe - http://developer.android.com/reference/android/app/Application.html

Toda vez que você iniciar uma AsyncTask, coloque-o sobre o Aplicativo e quando terminar de definir-null.

Quando um fragmento/atividade começa, você pode verificar se qualquer AsyncTask está em execução (por verificar se é nulo ou não no Aplicativo) e, em seguida, defina a referência dentro de tudo o que você quiser (atividade, fragmento etc então, você pode fazer chamadas de retorno).

Isso vai resolver o seu problema:Se você tiver apenas 1 AsyncTask em execução num determinado tempo, você pode adicionar uma referência simples:

AsyncTask<?,?,?> asyncTask = null;

Outra coisa, têm na Verdade um HashMap com referências a eles.

A caixa de diálogo de progresso pode seguir exatamente o mesmo princípio.

Eu vim com um método de utilização AsyncTaskLoaders para isso.É muito fácil de usar e requer menos sobrecarga IMO..

Basicamente, você cria um AsyncTaskLoader 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;
    }
}

Em seguida, na sua atividade, que utiliza o acima AsyncTaskLoader quando um botão é clicado:

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

    }
}

Isso parece para lidar com as alterações de orientação de multa e de sua tarefa de plano de fundo irá continuar durante a rotação.

Algumas coisas a observar:

  1. Se no onCreate você volte para a asynctaskloader você será chamado de volta em onLoadFinished() com o resultado anterior (mesmo se você já tinha sido dito que o pedido foi concluído).Este é, na verdade, o bom comportamento a maior parte do tempo, mas às vezes pode ser difícil de lidar.Embora eu imagine que há muitas maneiras de lidar com isso que eu fiz foi chamado de carregador.abandonar() em onLoadFinished.Em seguida, verifique no onCreate só volte a ligar o carregador se já não fosse abandonada.Se você precisa de dados resultante de novo, você não quer fazer isso.Na maioria dos casos, você deseja que os dados.

Eu tenho mais detalhes sobre a utilização deste para chamadas http aqui

Eu criei uma minúscula open-source tarefa de plano de fundo da biblioteca, que é fortemente baseada no Marshmallow AsyncTask mas com funcionalidades adicionais, tais como:

  1. Automaticamente a retenção de tarefas através de alterações de configuração;
  2. INTERFACE de retorno de chamada (ouvintes);
  3. Não reiniciar ou cancelar a tarefa quando o dispositivo gira (como Carregadores de faria);

A biblioteca utiliza internamente um Fragment sem qualquer interface do usuário, o que é mantida através de alterações de configuração (setRetainInstance(true)).

Você pode encontrá-lo no GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks

A maioria exemplo básico (versão 0.2.0):

Este exemplo totalmente retém a tarefa, usando uma quantidade muito limitada de código.

Tarefa:

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

Atividade:

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

O meu método é o uso de delegação de padrão de design, em geral, pode-se isolar o real lógica de negócios (leitura de dados a partir da internet ou de banco de dados ou de qualquer natureza) da AsyncTask (delegante) para BusinessDAO (o delegado), em seu AysncTask.doInBackground() método, delegar a tarefa real para BusinessDAO, em seguida, implementar um singleton processo de mecanismo de BusinessDAO, de modo que vários chamada para BusinessDAO.doSomething() só vai desencadear um real de execução de tarefas de cada vez e esperando o resultado de uma tarefa.A ideia é manter o delegado (i.e.BusinessDAO) durante a alteração de configuração, em vez de o delegante (i.e.AsyncTask).

  1. Criar/Implementar nosso próprio Aplicativo, o propósito é o de criar/inicializar BusinessDAO aqui, para que a nossa BusinessDAO do ciclo de vida da aplicação de escopo, não a atividade de escopo, note que você precisa mudar 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. Nossa Atividade existente/Fragement são praticamente inalterada, ainda implementar AsyncTask como uma classe interna e envolver AsyncTask.execute() de Atividade/Fragement, a diferença agora é AsyncTask irá delegar a tarefa real para BusinessDAO, por isso, durante a alteração da configuração, um segundo AsyncTask será inicializado e executado, e a chamada BusinessDAO.doSomething() segundo tempo, no entanto, segunda chamada para BusinessDAO.doSomething() não irão desencadear uma nova tarefa em execução, em vez disso, espera atual para a execução de tarefas para concluir:

    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 BusinessDAO, implementar singleton processo de mecanismo, por exemplo:

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

Não estou 100% de certeza se isso vai funcionar, além disso, a exemplo de trecho de código deve ser considerado como pseudocódigo.Estou apenas tentando dar alguma pista de design de nível.Quaisquer comentários ou sugestões são bem-vindas e apreciadas.

Você pode fazer o AsyncTask um campo estático.Se você precisa de um contexto, você deve enviar o seu contexto de aplicação.Isto irá evitar vazamentos de memória, caso contrário, você teria que manter uma referência para toda a sua atividade.

Se alguém encontra seu caminho para este segmento, em seguida, encontrei uma limpeza abordagem para executar a tarefa Assíncrona a partir de um app.Service (começou com START_STICKY) e, em seguida, em recriar iterar sobre os serviços em execução para descobrir se o serviço (e, portanto, tarefa assíncrona) ainda está em execução;

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

Se for, voltar a adicionar o DialogFragment (ou o que seja) e se ele não é garantir o diálogo tem sido dispensada.

Isto é particularmente pertinente se você estiver usando o v4.support.* bibliotecas, uma vez que (no momento da escrita) eles têm problemas com o saber setRetainInstance método e visão de paginação. Além disso, por não reter o exemplo, você pode recriar a sua actividade com um conjunto diferente de recursos (por exemplo,um diferente modo de exibição de layout para a nova orientação)

Eu escrevo samepl código para resolver este problema

O primeiro passo é fazer a Aplicação de classe:

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

No 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 da atividade:

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

}

Quando a atividade de orientação mudança de variável mTask é inited de contexto de aplicativo.Quando a tarefa é concluída variável é definida para null e remover da memória.

Para mim é o suficiente.

Dê uma olhada no exemplo abaixo , como usar mantidas fragmento de manter tarefa de plano de fundo:

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

    }
}

Ter um olhar aqui.

Há uma solução baseada no Timmmm do solução.

Mas eu melhorei-a:

  • Agora a solução é extensível - você só precisa estender FragmentAbleToStartTask

  • Você é capaz de manter a executar várias tarefas ao mesmo tempo.

    E na minha opinião é tão fácil como startActivityForResult e receber o resultado

  • Você também pode parar uma tarefa em execução e verificar se determinada tarefa está em execução

Desculpe pelo meu inglês

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top