Aviso: Essa classe AsyncTask deve ser estática ou podem ocorrer vazamentos

270

Estou recebendo um aviso no meu código que afirma:

Esta classe AsyncTask deve ser estática ou podem ocorrer vazamentos (android.os.AsyncTask anônimo)

O aviso completo é:

Essa classe AsyncTask deve ser estática ou podem ocorrer vazamentos (android.os.AsyncTask anônimo) Um campo estático vazará contextos. Classes internas não estáticas têm uma referência implícita à sua classe externa. Se essa classe externa for, por exemplo, um Fragmento ou Atividade, essa referência significa que o manipulador / carregador / tarefa de longa duração manterá uma referência à atividade que impede a coleta de lixo. Da mesma forma, referências diretas de campo a atividades e fragmentos dessas instâncias de execução mais longa podem causar vazamentos. As classes ViewModel nunca devem apontar para Views ou Contextos que não sejam de aplicativos.

Este é o meu código:

 new AsyncTask<Void,Void,Void>(){

        @Override
        protected Void doInBackground(Void... params) {
            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    mAdapter.notifyDataSetChanged();
                }
            });

            return null;
        }
    }.execute();

Como faço para corrigir isso?

Keyur Nimavat
fonte
2
lendo este androiddesignpatterns.com/2013/01/... deve dar-lhe um toque de por que deve ser estático
Raghunandan
Até o momento, sempre consegui substituir o AsyncTask pelo novo Thread (...). Statr () em combinação com runOnUiThread (...) se necessário, portanto, não preciso mais lidar com esse aviso.
Hong
1
Qual é a solução no kotlin para esse problema?
TapanHP
Por favor, reconsidere qual resposta deve ser a aceita. Veja as respostas abaixo.
Sepmega 5/09
No meu caso, recebo esse aviso de um Singleton que não tem referências diretas à Activity (ele recebe a saída myActivity.getApplication()no construtor privado do Singleton, para inicializar as classes RoomDB e outras classes). Meus ViewModels obtêm a instância Singleton como uma referência particular para executar algumas operações no banco de dados. Portanto, os ViewModels importam o pacote Singleton e também android.app.Applicationum deles android.app.Activity. Como "o Singleton" não precisa importar esses ViewModels para funcionar, mesmo assim, podem ocorrer vazamentos de memória?
SebasSBM

Respostas:

65

Classes internas não estáticas mantém uma referência à classe que contém. Quando você declara AsyncTaskcomo uma classe interna, ela pode durar mais que a Activityclasse que a contém . Isso ocorre devido à referência implícita à classe que o contém. Isso impedirá que a atividade seja coletada como lixo, daí o vazamento de memória.

Para resolver seu problema, use a classe aninhada estática em vez da classe anônima, local e interna ou use a classe de nível superior.

Anand
fonte
1
A solução está no próprio aviso. Use uma classe aninhada estática ou uma classe de nível superior.
Anand
3
@KeyurNimavat Eu acho que você pode passar em uma referência fraca para a sua actividade
peterchaula
42
então qual é o sentido de usar o AsyncTask? se for mais fácil executar o novo Thread e handler.post ou view.post (para atualizar a interface do usuário) no final no método run do Thread. Se AsyncTask é estático ou classe de nível superior, então é difícil de acesso necessários variáveis / métodos dele
user924
8
não há código fornecido sobre como é a maneira correta de usá-lo. Eu sempre tentei colocar estático lá, mas haverá mais alerta e erro aparecer
Kasnady
19
@Anand Exclua esta resposta para que a resposta mais útil em stackoverflow.com/a/46166223/145119 possa estar na parte superior.
Mithaldu
557

Como usar uma classe AsyncTask interna estática

Para evitar vazamentos, você pode tornar a classe interna estática. O problema disso, porém, é que você não tem mais acesso às visualizações da interface do usuário da atividade ou às variáveis ​​de membro. Você pode passar uma referência ao, Contextmas então corre o mesmo risco de um vazamento de memória. (O Android não pode coletar a Atividade após o fechamento, se a classe AsyncTask tiver uma forte referência a ela.) A solução é fazer uma referência fraca à Atividade (ou o que Contextvocê precisar).

public class MyActivity extends AppCompatActivity {

    int mSomeMemberVariable = 123;

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

        // start the AsyncTask, passing the Activity context
        // in to a custom constructor 
        new MyTask(this).execute();
    }

    private static class MyTask extends AsyncTask<Void, Void, String> {

        private WeakReference<MyActivity> activityReference;

        // only retain a weak reference to the activity 
        MyTask(MyActivity context) {
            activityReference = new WeakReference<>(context);
        }

        @Override
        protected String doInBackground(Void... params) {

            // do some long running task...

            return "task finished";
        }

        @Override
        protected void onPostExecute(String result) {

            // get a reference to the activity if it is still there
            MyActivity activity = activityReference.get();
            if (activity == null || activity.isFinishing()) return;

            // modify the activity's UI
            TextView textView = activity.findViewById(R.id.textview);
            textView.setText(result);

            // access Activity member variables
            activity.mSomeMemberVariable = 321;
        }
    }
}

Notas

  • Tanto quanto sei, esse tipo de perigo de vazamento de memória sempre foi verdadeiro, mas só comecei a ver o aviso no Android Studio 3.0. Muitos dos principais AsyncTasktutoriais por aí ainda não lidam com isso (veja aqui , aqui , aqui e aqui ).
  • Você também seguiria um procedimento semelhante se AsyncTaskfosse uma classe de nível superior. Uma classe interna estática é basicamente a mesma que uma classe de nível superior em Java.
  • Se você não precisa da Atividade em si, mas ainda deseja que o Contexto (por exemplo, exiba a Toast), pode passar uma referência ao contexto do aplicativo. Nesse caso, o AsyncTaskconstrutor ficaria assim:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • Existem alguns argumentos para ignorar esse aviso e usar apenas a classe não estática. Afinal, o AsyncTask deve durar muito pouco (alguns segundos no máximo) e liberará sua referência à Atividade quando terminar de qualquer maneira. Veja isso e isso .
  • Artigo excelente: Como vazar um contexto: manipuladores e classes internas

Kotlin

No Kotlin , não inclua a innerpalavra - chave da classe interna. Isso o torna estático por padrão.

class MyActivity : AppCompatActivity() {

    internal var mSomeMemberVariable = 123

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // start the AsyncTask, passing the Activity context
        // in to a custom constructor
        MyTask(this).execute()
    }

    private class MyTask
    internal constructor(context: MyActivity) : AsyncTask<Void, Void, String>() {

        private val activityReference: WeakReference<MyActivity> = WeakReference(context)

        override fun doInBackground(vararg params: Void): String {

            // do some long running task...

            return "task finished"
        }

        override fun onPostExecute(result: String) {

            // get a reference to the activity if it is still there
            val activity = activityReference.get()
            if (activity == null || activity.isFinishing) return

            // modify the activity's UI
            val textView = activity.findViewById(R.id.textview)
            textView.setText(result)

            // access Activity member variables
            activity.mSomeMemberVariable = 321
        }
    }
}
Suragch
fonte
1
@ManojFrekzz, Não, na verdade, você pode atualizar a interface do usuário usando a referência fraca para a Atividade que foi transmitida. Confira meu onPostExecutemétodo novamente no código acima. Você pode ver que eu atualizei a interface do usuário TextViewlá. Basta usar activity.findViewByIdpara obter uma referência a qualquer elemento da interface do usuário que você precise atualizar.
Suragch 4/17/17
7
+1. Esta é a melhor e mais limpa solução que eu já vi! Só que, no caso de você deseja modificar UI no método OnPostExecute, você também deve verificar se a atividade está sendo destruído: activity.isFinishing ()
zapotec
1
Nota! Ao usar esta resposta, continuei encontrando Exceções de Ponteiro Nulo porque a operação doInBackground consumia muita memória, acionava a coleta de lixo, coletava o fracaReference e matava a tarefa assíncrona. Você pode usar um SoftReference em vez de um fraco, se souber que suas operações em segundo plano consomem mais memória.
PGMacDesign
2
@ Sunny, passe uma referência ao fragmento em vez da atividade. Você faria o activity.isFinishing()cheque e possivelmente o substituiria por um fragment.isRemoving()cheque. Eu não trabalhei muito com fragmentos recentemente, no entanto.
Suragch
1
@bashan, (1) Se a classe externa não for uma Activity, em seu AsyncTaskconstrutor você passará uma referência à sua classe externa. E doInBackground()você pode obter uma referência à classe externa com MyOuterClass ref = classReference.get(). Verifique se há null. (2) onPostExecute()Você está atualizando a interface do usuário apenas com os resultados da tarefa em segundo plano. É como qualquer outra vez que você atualiza a interface do usuário. A verificação activity.isFinishing()é apenas para garantir que a atividade ainda não tenha começado a concluir; nesse caso, seria inútil atualizar a interface do usuário.
Suragch 26/09/18
23

Essa AsyncTaskclasse deve ser estática ou podem ocorrer vazamentos porque

  • Quando Activityé destruído, AsyncTask(ambos staticounon-static ) ainda está em execução
  • Se classe interna for non-static( AsyncTask), ela terá referência à classe externa ( Activity).
  • Se um objeto não tiver referências, ele Garbage Collectedserá liberado. Se um objeto não estiver sendo usado e Garbage Collected não puder liberá-lo => vazar memória

=> Se AsyncTaskfor non-static,Activity não irá liberar evento se for destruído => vazamento

Solução para atualização da interface do usuário após fazer o AsyncTask como classe estática sem vazamento

1) Use WeakReferencecomo resposta do @Suragch
2) Envie e remova a Activityreferência a (de)AsyncTask

public class NoLeakAsyncTaskActivity extends AppCompatActivity {
    private ExampleAsyncTask asyncTask;

    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        ...

        // START AsyncTask
        asyncTask = new ExampleAsyncTask();
        asyncTask.setListener(new ExampleAsyncTask.ExampleAsyncTaskListener() {
            @Override
            public void onExampleAsyncTaskFinished(Integer value) {
                // update UI in Activity here
            }
        });
        asyncTask.execute();
    }

    @Override
    protected void onDestroy() {
        asyncTask.setListener(null); // PREVENT LEAK AFTER ACTIVITY DESTROYED
        super.onDestroy();
    }

    static class ExampleAsyncTask extends AsyncTask<Void, Void, Integer> {
        private ExampleAsyncTaskListener listener;

        @Override
        protected Integer doInBackground(Void... voids) {
            ...
            return null;
        }

        @Override
        protected void onPostExecute(Integer value) {
            super.onPostExecute(value);
            if (listener != null) {
                listener.onExampleAsyncTaskFinished(value);
            }
        }

        public void setListener(ExampleAsyncTaskListener listener) {
            this.listener = listener;
        }

        public interface ExampleAsyncTaskListener {
            void onExampleAsyncTaskFinished(Integer value);
        }
    }
}
Phan Van Linh
fonte
5
@Suragch, seu link afirma que, embora não seja garantido que o onDestroy seja chamado, a única situação em que não é é quando o sistema conclui o processo, para que todos os recursos sejam liberados de qualquer maneira. Portanto, não faça salvamentos aqui, mas você pode fazer um lançamento de recurso aqui.
Angelo Fuchs
2
No caso de uso não estático do AsyncTask, por que não podemos simplesmente definir a variável de instância AsyncTask como NULL, semelhante a isso. isso não diz ao GC para liberar Atividade, embora o AsyncTask esteja em execução?
Hanif
A definição de @Hanif como variável de instância AsyncTask como NUL não ajudará, porque a tarefa ainda tem a ref por meio do ouvinte.
Susanta 15/06