Prática recomendada: AsyncTask durante a mudança de orientação

151

AsyncTask é ótimo executar tarefas complexas em outro thread.

Mas quando há uma mudança de orientação ou outra configuração enquanto ela AsyncTaskainda está em execução, a corrente Activityé destruída e reiniciada. E como a instância de AsyncTaskestá conectada a essa atividade, ela falha e causa uma janela de mensagem "forçar fechamento".

Portanto, estou procurando algum tipo de "prática recomendada" para evitar esses erros e impedir que o AsyncTask falhe.

O que eu vi até agora é:

  • Desative as alterações de orientação (com certeza não da maneira que você deve lidar com isso).
  • Deixando a tarefa sobreviver e atualizando-a com a nova instância de atividade via onRetainNonConfigurationInstance
  • Apenas cancele a tarefa quando ela Activityfor destruída e reinicie-a quando ela Activityfor criada novamente.
  • Vinculando a tarefa à classe do aplicativo em vez da instância da atividade.
  • Algum método usado no projeto "prateleiras" (via onRestoreInstanceState)

Alguns exemplos de código:

Tarefas assíncronas do Android durante uma rotação de tela, parte I e parte II

ShelvesActivity.java

Você pode me ajudar a encontrar a melhor abordagem que resolva melhor o problema e também seja fácil de implementar? O código em si também é importante, pois não sei como resolver isso corretamente.

caw
fonte
Há uma duplicata, verifique este stackoverflow.com/questions/4584015/… .
TeaCupApp 20/08/11
Isto é do Blog de Mark Murphy ... AsyncTask e ScreenRotation podem ajudar ... link
Gopal
Embora este seja um post antigo, mas este IMO é uma abordagem muito mais fácil (e melhor?).
DroidDev
Estou apenas me perguntando por que a documentação não está falando sobre situações tão triviais.
Sreekanth Karumanaghat 27/12/19

Respostas:

140

Você NÃO utilizar android:configChangespara resolver este problema. Esta é uma prática muito ruim.

Você não usar Activity#onRetainNonConfigurationInstance()qualquer um. Isso é menos modular e não é adequado para Fragmentaplicativos baseados em.

Você pode ler meu artigo descrevendo como lidar com alterações de configuração usando Fragments retidos . Ele resolve o problema de manter uma AsyncTaskalteração de rotação agradável. Basicamente, você precisa hospedar o seu AsyncTaskdentro de Fragment, chamar setRetainInstance(true)o Fragmente reportar o AsyncTaskprogresso / resultados do mesmo Activityatravés dos retidos Fragment.

Alex Lockwood
fonte
26
Boa ideia, mas nem todo mundo usa fragmentos. Há muito código legado escrito muito antes dos Fragments serem uma opção.
22713 Scott Biggs
14
Os fragmentos do @ScottBiggs estão disponíveis na biblioteca de suporte desde o Android 1.6. E você poderia dar um exemplo de algum código legado que ainda está sendo usado ativamente e que teria problemas para usar os fragmentos da biblioteca de suporte? Porque sinceramente não acho que isso seja um problema.
Alex Lockwood
4
@tactoth Não senti a necessidade de resolver esses problemas em minha resposta, já que 99,9% das pessoas não usam mais TabActivity. Para ser sincero, não sei por que estamos falando sobre isso ... todos concordam que esse Fragmenté o caminho a seguir. :)
Alex Lockwood
2
E se o AsyncTask precisar ser chamado de um fragmento aninhado?
Eduardo Naveda
3
@AlexLockwood - "todo mundo concorda que os fragmentos são o caminho a percorrer". Os Devs do Squared discordariam!
JBeckton
36

Normalmente, eu resolvo isso fazendo meu AsyncTasks acionar Intents de difusão no retorno de chamada .onPostExecute (), para que eles não modifiquem a Atividade que os iniciou diretamente. As Atividades ouvem essas transmissões com BroadcastReceivers dinâmicos e agem de acordo.

Dessa forma, as AsyncTasks não precisam se preocupar com a instância Activity específica que lida com o resultado. Eles apenas "gritam" quando terminam e, se uma Atividade estiver por esse período (ativa e focada / no estado retomado) que estiver interessada nos resultados da tarefa, ela será tratada.

Isso envolve um pouco mais de sobrecarga, já que o tempo de execução precisa lidar com a transmissão, mas geralmente não me importo. Eu acho que usar o LocalBroadcastManager em vez do sistema padrão em geral acelera um pouco as coisas.

Zsombor Erdődy-Nagy
fonte
6
se u pode adicionar um exemplo para a resposta seria mais útil
Sankar V
1
Eu acho que esta é a solução que oferece menos acoplamento entre as atividades e fragmentos
Roger Garzon Nieto
7
Isso pode fazer parte de uma solução, mas não parece que resolveria o problema de o AsyncTask ser recriado após a alteração da orientação.
miguel
4
E se você tiver azar e nenhuma atividade estiver disponível durante a transmissão? (ou seja, você está rodando no meio)
Sam
24

Aqui está outro exemplo de AsyncTask que usa a Fragmentpara manipular alterações na configuração de tempo de execução (como quando o usuário gira a tela) com setRetainInstance(true). Uma barra de progresso determinada (atualizada regularmente) também é demonstrada.

O exemplo é parcialmente baseado nos documentos oficiais, Reter um objeto durante uma alteração na configuração .

Neste exemplo, o trabalho que requer um encadeamento em segundo plano é o mero carregamento de uma imagem da Internet na interface do usuário.

Alex Lockwood parece estar certo de que, quando se trata de lidar com alterações na configuração de tempo de execução com AsyncTasks, usar um "Fragmento Retido" é uma prática recomendada. onRetainNonConfigurationInstance()é preterido no Lint, no Android Studio. Os documentos oficiais nos alertam sobre o uso android:configChanges, de Manipulação da mudança de configuração , ...

O controle da alteração da configuração pode tornar muito mais difícil o uso de recursos alternativos, porque o sistema não os aplica automaticamente a você. Essa técnica deve ser considerada um último recurso quando você deve evitar reinicializações devido a uma alteração na configuração e não é recomendado para a maioria dos aplicativos.

Há a questão de saber se alguém deve usar um AsyncTask para o encadeamento em segundo plano.

A referência oficial do AsyncTask adverte ...

Idealmente, as AsyncTasks devem ser usadas para operações curtas (no máximo, alguns segundos). Se você precisar manter os threads em execução por longos períodos, é altamente recomendável usar as várias APIs fornecidas pelo pacakge java.util.concurrent, como Executor, ThreadPoolExecutor e FutureTask.

Como alternativa, pode-se usar um serviço, carregador (usando um CursorLoader ou AsyncTaskLoader) ou provedor de conteúdo para executar operações assíncronas.

Eu divido o resto do post em:

  • O procedimento; e
  • Todo o código para o procedimento acima.

O procedimento

  1. Comece com um AsyncTask básico como uma classe interna de uma atividade (ela não precisa ser uma classe interna, mas provavelmente será conveniente). Nesta fase, o AsyncTask não lida com alterações na configuração de tempo de execução.

    public class ThreadsActivity extends ActionBarActivity {
    
        private ImageView mPictureImageView;
    
        private class LoadImageFromNetworkAsyncTask
                              extends AsyncTask<String, Void, Bitmap> {
    
            @Override
            protected Bitmap doInBackground(String... urls) {
                return loadImageFromNetwork(urls[0]);
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                mPictureImageView.setImageBitmap(bitmap);
            }
        }
    
        /**
         * Requires in AndroidManifext.xml
         *  <uses-permission android:name="android.permission.INTERNET" />
         */
        private Bitmap loadImageFromNetwork(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeStream((InputStream)
                                              new URL(url).getContent());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            mPictureImageView =
                (ImageView) findViewById(R.id.imageView_picture);
        }
    
        public void getPicture(View view) {
            new LoadImageFromNetworkAsyncTask()
                .execute("http://i.imgur.com/SikTbWe.jpg");
        }
    
    }
  2. Adicione uma classe aninhada RetainedFragment que estenda a classe Fragement e não tenha sua própria interface do usuário. Adicione setRetainInstance (true) ao evento onCreate deste fragmento. Forneça procedimentos para definir e obter seus dados.

    public class ThreadsActivity extends Activity {
    
        private ImageView mPictureImageView;
        private RetainedFragment mRetainedFragment = null;
        ...
    
        public static class RetainedFragment extends Fragment {
    
            private Bitmap mBitmap;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
                // The key to making data survive
                // runtime configuration changes.
                setRetainInstance(true);
            }
    
            public Bitmap getData() {
                return this.mBitmap;
            }
    
            public void setData(Bitmap bitmapToRetain) {
                this.mBitmap = bitmapToRetain;
            }
        }
    
        private class LoadImageFromNetworkAsyncTask
                        extends AsyncTask<String, Integer,Bitmap> {
        ....
  3. No onCreate () da classe Activity mais externa, manipule RetainedFragment: referencie-o se ele já existir (no caso de a Atividade estar sendo reiniciada); crie e adicione-o se não existir; Em seguida, se ele já existir, obtenha dados do RetainedFragment e defina sua interface do usuário com esses dados.

    public class ThreadsActivity extends Activity {
    
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            final String retainedFragmentTag = "RetainedFragmentTag";
    
            mPictureImageView =
                      (ImageView) findViewById(R.id.imageView_picture);
            mLoadingProgressBar =
                    (ProgressBar) findViewById(R.id.progressBar_loading);
    
            // Find the RetainedFragment on Activity restarts
            FragmentManager fm = getFragmentManager();
            // The RetainedFragment has no UI so we must
            // reference it with a tag.
            mRetainedFragment =
              (RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);
    
            // if Retained Fragment doesn't exist create and add it.
            if (mRetainedFragment == null) {
    
                // Add the fragment
                mRetainedFragment = new RetainedFragment();
                fm.beginTransaction()
                    .add(mRetainedFragment, retainedFragmentTag).commit();
    
            // The Retained Fragment exists
            } else {
    
                mPictureImageView
                    .setImageBitmap(mRetainedFragment.getData());
            }
        }
  4. Iniciar o AsyncTask a partir da interface do usuário

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. Adicione e codifique uma barra de progresso determinada:

    • Adicione uma barra de progresso ao layout da interface do usuário;
    • Obtenha uma referência a ele na Activity oncreate ();
    • Torne-o visível e invisível no início e no final do processo;
    • Defina o progresso para relatar à interface do usuário no onProgressUpdate.
    • Altere o parâmetro AsyncTask 2nd Generic de Void para um tipo que possa lidar com atualizações de progresso (por exemplo, Inteiro).
    • publishProgress em pontos regulares em doInBackground ().

Todo o código para o procedimento acima

Layout da atividade.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.mysecondapp.ThreadsActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/imageView_picture"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:background="@android:color/black" />

        <Button
            android:id="@+id/button_get_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@id/imageView_picture"
            android:onClick="getPicture"
            android:text="Get Picture" />

        <Button
            android:id="@+id/button_clear_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/button_get_picture"
            android:layout_toEndOf="@id/button_get_picture"
            android:layout_toRightOf="@id/button_get_picture"
            android:onClick="clearPicture"
            android:text="Clear Picture" />

        <ProgressBar
            android:id="@+id/progressBar_loading"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/button_get_picture"
            android:progress="0"
            android:indeterminateOnly="false"
            android:visibility="invisible" />

    </RelativeLayout>
</ScrollView>

A atividade com: classe interna AsyncTask subclasse; classe interna RetainedFragment subclassed que lida com alterações na configuração de tempo de execução (por exemplo, quando o usuário gira a tela); e uma barra de progresso determinada que atualiza em intervalos regulares. ...

public class ThreadsActivity extends Activity {

    private ImageView mPictureImageView;
    private RetainedFragment mRetainedFragment = null;
    private ProgressBar mLoadingProgressBar;

    public static class RetainedFragment extends Fragment {

        private Bitmap mBitmap;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // The key to making data survive runtime configuration changes.
            setRetainInstance(true);
        }

        public Bitmap getData() {
            return this.mBitmap;
        }

        public void setData(Bitmap bitmapToRetain) {
            this.mBitmap = bitmapToRetain;
        }
    }

    private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
            Integer, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... urls) {
            // Simulate a burdensome load.
            int sleepSeconds = 4;
            for (int i = 1; i <= sleepSeconds; i++) {
                SystemClock.sleep(1000); // milliseconds
                publishProgress(i * 20); // Adjust for a scale to 100
            }

            return com.example.standardapplibrary.android.Network
                    .loadImageFromNetwork(
                    urls[0]);
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            mLoadingProgressBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            publishProgress(100);
            mRetainedFragment.setData(bitmap);
            mPictureImageView.setImageBitmap(bitmap);
            mLoadingProgressBar.setVisibility(View.INVISIBLE);
            publishProgress(0);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threads);

        final String retainedFragmentTag = "RetainedFragmentTag";

        mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
        mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);

        // Find the RetainedFragment on Activity restarts
        FragmentManager fm = getFragmentManager();
        // The RetainedFragment has no UI so we must reference it with a tag.
        mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
                retainedFragmentTag);

        // if Retained Fragment doesn't exist create and add it.
        if (mRetainedFragment == null) {

            // Add the fragment
            mRetainedFragment = new RetainedFragment();
            fm.beginTransaction().add(mRetainedFragment,
                                      retainedFragmentTag).commit();

            // The Retained Fragment exists
        } else {

            mPictureImageView.setImageBitmap(mRetainedFragment.getData());
        }
    }

    public void getPicture(View view) {
        mLoadingProgressBar.setVisibility(View.VISIBLE);
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }

    public void clearPicture(View view) {
        mRetainedFragment.setData(null);
        mPictureImageView.setImageBitmap(null);
    }
}

Neste exemplo, a função de biblioteca (mencionada acima com o prefixo explícito do pacote com.example.standardapplibrary.android.Network) que funciona de verdade ...

public static Bitmap loadImageFromNetwork(String url) {
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
                .getContent());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bitmap;
}

Adicione as permissões necessárias à sua tarefa em segundo plano ao AndroidManifest.xml ...

<manifest>
...
    <uses-permission android:name="android.permission.INTERNET" />

Adicione sua atividade ao AndroidManifest.xml ...

<manifest>
...
    <application>
        <activity
            android:name=".ThreadsActivity"
            android:label="@string/title_activity_threads"
            android:parentActivityName=".MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.mysecondapp.MainActivity" />
        </activity>
John Bentley
fonte
Ótimo. Você deve escrever um blog sobre isso.
Akh
2
@AKh. Você quer sugerir que minha resposta ocupe muito espaço no Stackoverflow?
John Bentley
1
Eu acho que ele apenas quer dizer que você tem uma resposta incrível e deve escrever um blog! =) @JohnBentley
Sandy D.
@ SandyD.yesterday Obrigado pela interpretação positiva. Eu espero que ela ou ele pretenda isso.
John
Eu também pensei que era uma resposta incrível e eu a interpretei assim também. Respostas muito completas como essa são ótimas !!
LeonardoSibela
3

Recentemente, encontrei uma boa solução aqui . É baseado no salvamento de um objeto de tarefa via RetainConfiguration. Do meu ponto de vista, a solução é muito elegante e, para mim, comecei a usá-la. Você precisa apenas aninhar sua asynctask a partir do basetask e isso é tudo.

Yury
fonte
Muito obrigado por esta resposta interessante. É uma boa solução, além das mencionadas na pergunta relacionada.
caw
5
Infelizmente, esta solução usa métodos obsoletos.
Damien
3

Com base nas respostas de @Alex Lockwood e nas respostas de @William & @quickdraw mcgraw nesta publicação: Como lidar com mensagens do manipulador quando a atividade / fragmento está em pausa , escrevi uma solução genérica.

Dessa forma, a rotação é manipulada e, se a atividade ficar em segundo plano durante a execução da tarefa assíncrona, a atividade receberá os retornos de chamada (onPreExecute, onProgressUpdate, onPostExecute & onCancelled) uma vez reiniciada, para que nenhuma IllegalStateException seja lançada (consulte Como lidar com o manipulador mensagens quando a atividade / fragmento está em pausa ).

Seria ótimo ter o mesmo, mas com tipos de argumentos genéricos, como um AsyncTask (por exemplo: AsyncTaskFragment <Params, Progress, Result>), mas não consegui fazê-lo rapidamente e não tenho tempo no momento. Se alguém quiser fazer a melhoria, sinta-se à vontade!

O código:

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class AsyncTaskFragment extends Fragment {

    /* ------------------------------------------------------------------------------------------ */
    // region Classes & Interfaces

    public static abstract class Task extends AsyncTask<Object, Object, Object> {

        private AsyncTaskFragment _fragment;

        private void setFragment(AsyncTaskFragment fragment) {

            _fragment = fragment;
        }

        @Override
        protected final void onPreExecute() {

            // Save the state :
            _fragment.setRunning(true);

            // Send a message :
            sendMessage(ON_PRE_EXECUTE_MESSAGE, null);
        }

        @Override
        protected final void onPostExecute(Object result) {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_POST_EXECUTE_MESSAGE, result);
        }

        @Override
        protected final void onProgressUpdate(Object... values) {

            // Send a message :
            sendMessage(ON_PROGRESS_UPDATE_MESSAGE, values);
        }

        @Override
        protected final void onCancelled() {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_CANCELLED_MESSAGE, null);
        }

        private void sendMessage(int what, Object obj) {

            Message message = new Message();
            message.what = what;
            message.obj = obj;

            Bundle data = new Bundle(1);
            data.putString(EXTRA_FRAGMENT_TAG, _fragment.getTag());
            message.setData(data);

            _fragment.handler.sendMessage(message);
        }
    }

    public interface AsyncTaskFragmentListener {

        void onPreExecute(String fragmentTag);
        void onProgressUpdate(String fragmentTag, Object... progress);
        void onCancelled(String fragmentTag);
        void onPostExecute(String fragmentTag, Object result);
    }

    private static class AsyncTaskFragmentPauseHandler extends PauseHandler {

        @Override
        final protected void processMessage(Activity activity, Message message) {

            switch (message.what) {

                case ON_PRE_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPreExecute(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
                case ON_POST_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPostExecute(message.getData().getString(EXTRA_FRAGMENT_TAG), message.obj); break; }
                case ON_PROGRESS_UPDATE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onProgressUpdate(message.getData().getString(EXTRA_FRAGMENT_TAG), ((Object[])message.obj)); break; }
                case ON_CANCELLED_MESSAGE : { ((AsyncTaskFragmentListener)activity).onCancelled(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
            }
        }
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Attributes

    private Task _task;
    private AsyncTaskFragmentListener _listener;
    private boolean _running = false;

    private static final String EXTRA_FRAGMENT_TAG = "EXTRA_FRAGMENT_TAG";
    private static final int ON_PRE_EXECUTE_MESSAGE = 0;
    private static final int ON_POST_EXECUTE_MESSAGE = 1;
    private static final int ON_PROGRESS_UPDATE_MESSAGE = 2;
    private static final int ON_CANCELLED_MESSAGE = 3;

    private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Getters

    public AsyncTaskFragmentListener getListener() { return _listener; }
    public boolean isRunning() { return _running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Setters

    public void setTask(Task task) {

        _task = task;
        _task.setFragment(this);
    }

    public void setListener(AsyncTaskFragmentListener listener) { _listener = listener; }
    private void setRunning(boolean running) { _running = running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Fragment lifecycle

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onResume() {

        super.onResume();
        handler.resume(getActivity());
    }

    @Override
    public void onPause() {

        super.onPause();
        handler.pause();
    }

    @Override
    public void onAttach(Activity activity) {

        super.onAttach(activity);
        _listener = (AsyncTaskFragmentListener) activity;
    }

    @Override
    public void onDetach() {

        super.onDetach();
        _listener = null;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Utils

    public void execute(Object... params) {

        _task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }

    public void cancel(boolean mayInterruptIfRunning) {

        _task.cancel(mayInterruptIfRunning);
    }

    public static AsyncTaskFragment getRetainedOrNewFragment(AppCompatActivity activity, String fragmentTag) {

        FragmentManager fm = activity.getSupportFragmentManager();
        AsyncTaskFragment fragment = (AsyncTaskFragment) fm.findFragmentByTag(fragmentTag);

        if (fragment == null) {

            fragment = new AsyncTaskFragment();
            fragment.setListener( (AsyncTaskFragmentListener) activity);
            fm.beginTransaction().add(fragment, fragmentTag).commit();
        }

        return fragment;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */
}

Você precisará do PauseHandler:

import android.app.Activity;
import android.os.Handler;
import android.os.Message;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Message Handler class that supports buffering up of messages when the activity is paused i.e. in the background.
 *
 * /programming/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
 */
public abstract class PauseHandler extends Handler {

    /**
     * Message Queue Buffer
     */
    private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());

    /**
     * Flag indicating the pause state
     */
    private Activity activity;

    /**
     * Resume the handler.
     */
    public final synchronized void resume(Activity activity) {
        this.activity = activity;

        while (messageQueueBuffer.size() > 0) {
            final Message msg = messageQueueBuffer.get(0);
            messageQueueBuffer.remove(0);
            sendMessage(msg);
        }
    }

    /**
     * Pause the handler.
     */
    public final synchronized void pause() {
        activity = null;
    }

    /**
     * Store the message if we have been paused, otherwise handle it now.
     *
     * @param msg   Message to handle.
     */
    @Override
    public final synchronized void handleMessage(Message msg) {
        if (activity == null) {
            final Message msgCopy = new Message();
            msgCopy.copyFrom(msg);
            messageQueueBuffer.add(msgCopy);
        } else {
            processMessage(activity, msg);
        }
    }

    /**
     * Notification message to be processed. This will either be directly from
     * handleMessage or played back from a saved message when the activity was
     * paused.
     *
     * @param activity  Activity owning this Handler that isn't currently paused.
     * @param message   Message to be handled
     */
    protected abstract void processMessage(Activity activity, Message message);
}

Uso da amostra:

public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {

    private final static String ASYNC_TASK_FRAGMENT_A = "ASYNC_TASK_FRAGMENT_A";
    private final static String ASYNC_TASK_FRAGMENT_B = "ASYNC_TASK_FRAGMENT_B";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        Button testButton = (Button) findViewById(R.id.test_button);
        final AsyncTaskFragment fragment = AsyncTaskFragment.getRetainedOrNewFragment(TestActivity.this, ASYNC_TASK_FRAGMENT_A);

        testButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(!fragment.isRunning()) {

                    fragment.setTask(new Task() {

                        @Override
                        protected Object doInBackground(Object... objects) {

                            // Do your async stuff

                            return null;
                        }
                    });

                    fragment.execute();
                }
            }
        });
    }

    @Override
    public void onPreExecute(String fragmentTag) {}

    @Override
    public void onProgressUpdate(String fragmentTag, Float percent) {}

    @Override
    public void onCancelled(String fragmentTag) {}

    @Override
    public void onPostExecute(String fragmentTag, Object result) {

        switch (fragmentTag) {

            case ASYNC_TASK_FRAGMENT_A: {

                // Handle ASYNC_TASK_FRAGMENT_A
                break;
            }
            case ASYNC_TASK_FRAGMENT_B: {

                // Handle ASYNC_TASK_FRAGMENT_B
                break;
            }
        }
    }
}
Tim Autin
fonte
3

Você pode usar carregadores para isso. Verifique Doc aqui

PPD
fonte
2
Agora, os carregadores foram descontinuados a partir da API do Android 28 (como o link indicará).
Sumit
Carregadores não são preteridos, é apenas como você os chama que mudou
EdgeDev 16/04/19
2

Para quem deseja evitar os fragmentos, é possível reter o AsyncTask executando alterações de orientação usando onRetainCustomNonConfigurationInstance () e alguma fiação.

(Observe que esse método é a alternativa ao onRetainNonConfigurationInstance () descontinuado ).

Parece que esta solução não é frequentemente mencionada. Eu escrevi um exemplo simples para ilustrar.

Felicidades!

public class MainActivity extends AppCompatActivity {

private TextView result;
private Button run;
private AsyncTaskHolder asyncTaskHolder;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    result = (TextView) findViewById(R.id.textView_result);
    run = (Button) findViewById(R.id.button_run);
    asyncTaskHolder = getAsyncTaskHolder();
    run.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            asyncTaskHolder.execute();
        }
    });
}

private AsyncTaskHolder getAsyncTaskHolder() {
    if (this.asyncTaskHolder != null) {
        return asyncTaskHolder;
    }
    //Not deprecated. Get the same instance back.
    Object instance = getLastCustomNonConfigurationInstance();

    if (instance == null) {
        instance = new AsyncTaskHolder();
    }
    if (!(instance instanceof ActivityDependant)) {
        Log.e("", instance.getClass().getName() + " must implement ActivityDependant");
    }
    return (AsyncTaskHolder) instance;
}

@Override
//Not deprecated. Save the object containing the running task.
public Object onRetainCustomNonConfigurationInstance() {
    return asyncTaskHolder;
}

@Override
protected void onStart() {
    super.onStart();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.attach(this);
    }
}

@Override
protected void onStop() {
    super.onStop();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.detach();
    }
}

void updateUI(String value) {
    this.result.setText(value);
}

interface ActivityDependant {

    void attach(Activity activity);

    void detach();
}

class AsyncTaskHolder implements ActivityDependant {

    private Activity parentActivity;
    private boolean isRunning;
    private boolean isUpdateOnAttach;

    @Override
    public synchronized void attach(Activity activity) {
        this.parentActivity = activity;
        if (isUpdateOnAttach) {
            ((MainActivity) parentActivity).updateUI("done");
            isUpdateOnAttach = false;
        }
    }

    @Override
    public synchronized void detach() {
        this.parentActivity = null;
    }

    public synchronized void execute() {
        if (isRunning) {
            Toast.makeText(parentActivity, "Already running", Toast.LENGTH_SHORT).show();
            return;
        }
        isRunning = true;
        new AsyncTask<Void, Integer, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                for (int i = 0; i < 100; i += 10) {
                    try {
                        Thread.sleep(500);
                        publishProgress(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI(String.valueOf(values[0]));
                }
            }

            @Override
            protected synchronized void onPostExecute(Void aVoid) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI("done");
                } else {
                    isUpdateOnAttach = true;
                }
                isRunning = false;
            }
        }.execute();
    }
}
cgrenzel
fonte
0

Eu implementei uma biblioteca que pode resolver problemas com pausa e recreação de atividades enquanto sua tarefa está em execução.

Você deve implementar AsmykPleaseWaitTaske AsmykBasicPleaseWaitActivity. Sua atividade e tarefa em segundo plano funcionarão bem, mesmo que você esteja, girará a tela e alternará entre aplicativos

mabramyan
fonte
-9

SOLUÇÃO RÁPIDA (não recomendado)

Para evitar que uma Atividade seja destruída e criada por si mesma, é necessário declarar sua atividade no arquivo de manifesto: android: configChanges = "guidance | keyboardHidden | screenSize

  <activity
        android:name=".ui.activity.MyActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:label="@string/app_name">

Como mencionado nos documentos

A orientação da tela mudou - o usuário girou o dispositivo.

Nota: Se o seu aplicativo tiver como alvo a API nível 13 ou superior (conforme declarado pelos atributos minSdkVersion e targetSdkVersion), você também deverá declarar a configuração "screenSize", pois ela também muda quando um dispositivo alterna entre as orientações retrato e paisagem.

Choletski
fonte
1
É melhor evitar isso. developer.android.com/guide/topics/resources/… "Observação: o gerenciamento das alterações na configuração pode tornar muito mais difícil o uso de recursos alternativos, porque o sistema não os aplica automaticamente a você. Essa técnica deve ser considerada uma última recurso quando você deve evitar reinicializações devido a uma alteração na configuração e não é recomendado para a maioria dos aplicativos ".
David