Peringatan: Kelas AsyncTask ini harus statis atau kebocoran mungkin terjadi

270

Saya mendapat peringatan dalam kode saya yang menyatakan:

Kelas AsyncTask ini harus statis atau kebocoran mungkin terjadi (anonim android.os.AsyncTask)

Peringatan lengkapnya adalah:

Kelas AsyncTask ini harus statis atau kebocoran mungkin terjadi (anonim android.os.AsyncTask) Bidang statis akan membocorkan konteks. Kelas dalam non-statis memiliki referensi implisit ke kelas luarnya. Jika kelas luar itu misalnya Fragmen atau Aktivitas, maka referensi ini berarti bahwa pawang / pemuat / tugas yang sudah berjalan lama akan memegang referensi ke aktivitas yang mencegahnya mengumpulkan sampah. Demikian pula, referensi lapangan langsung ke aktivitas dan fragmen dari instance yang berjalan lebih lama ini dapat menyebabkan kebocoran. Kelas ViewModel tidak boleh mengarah ke Tampilan atau Konteks non-aplikasi.

Ini kode saya:

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

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

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

            return null;
        }
    }.execute();

Bagaimana saya memperbaikinya?

Keyur Nimavat
sumber
2
membaca ini androiddesignpatterns.com/2013/01/... harus memberi Anda petunjuk mengapa itu harus statis
Raghunandan
Sejauh ini, saya selalu dapat mengganti AsyncTask dengan Thread baru (...). Statr () dikombinasikan dengan runOnUiThread (...) jika perlu, jadi saya tidak harus berurusan dengan peringatan ini lagi.
Hong
1
Apa solusi di kotlin untuk masalah ini?
TapanHP
Harap pertimbangkan kembali jawaban mana yang harus diterima. Lihat jawaban di bawah ini.
Ωmega
Dalam kasus saya, saya mendapatkan peringatan ini dari Singleton yang tidak memiliki referensi langsung ke Activity (menerima output dari myActivity.getApplication()konstruktor pribadi untuk Singleton, untuk menginisialisasi kelas RoomDB dan kelas lainnya). ViewModels saya mendapatkan instance Singleton sebagai referensi pribadi untuk melakukan beberapa operasi pada DB. Jadi, ViewModels mengimpor paket Singleton, dan juga android.app.Application, salah satunya android.app.Activity. Karena "Singleton" tidak perlu mengimpor ViewModels tersebut agar berfungsi, meskipun demikian, mungkinkah terjadi kebocoran memori?
SebasSBM

Jawaban:

65

Kelas dalam non-statis memegang referensi ke kelas yang berisi. Ketika Anda mendeklarasikan AsyncTasksebagai kelas dalam, itu mungkin hidup lebih lama daripada yang mengandungActivity kelas yang . Ini karena referensi implisit ke kelas yang mengandung. Ini akan mencegah aktivitas dari pengumpulan sampah, karenanya memori bocor.

Untuk mengatasi masalah Anda, gunakan kelas bersarang statis alih-alih anonim, lokal, dan kelas dalam atau gunakan kelas tingkat atas.

Anand
sumber
1
Solusi terletak pada peringatan itu sendiri. Baik menggunakan kelas bersarang statis atau kelas tingkat atas.
Anand
3
@KeyurNimavat Saya pikir Anda bisa memberikan referensi yang lemah untuk aktivitas Anda
peterchaula
42
jadi apa gunanya menggunakan AsyncTask? jika lebih mudah untuk menjalankan Thread dan handler.post baru atau view.post (untuk memperbarui UI) pada akhirnya dalam menjalankan metode Thread. Jika AsyncTask statis atau kelas tingkat atas maka sulit untuk mengakses variabel / metode yang diperlukan dari itu
user924
8
tidak ada kode yang disediakan tentang bagaimana cara yang benar untuk menggunakannya. Saya pernah mencoba meletakkan statis di sana, tetapi akan ada lebih banyak peringatan dan kesalahan muncul
Kasnady
19
@ Dan Harap hapus jawaban ini sehingga jawaban yang lebih berguna di stackoverflow.com/a/46166223/145119 dapat berada di atas.
Mithaldu
557

Cara menggunakan kelas AsyncTask bagian dalam statis

Untuk mencegah kebocoran, Anda dapat membuat kelas dalam statis. Masalahnya adalah Anda tidak lagi memiliki akses ke tampilan UI variabel aktivitas atau anggota. Anda dapat mengirimkan referensi ke Contexttetapi kemudian Anda menjalankan risiko yang sama dari kebocoran memori. (Android tidak dapat mengumpulkan sampah Aktivitas setelah ditutup jika kelas AsyncTask memiliki referensi yang kuat untuk itu.) Solusinya adalah membuat referensi yang lemah ke Aktivitas (atau apa pun yang ContextAnda butuhkan).

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

Catatan

  • Sejauh yang saya tahu, bahaya kebocoran memori jenis ini selalu benar, tetapi saya hanya mulai melihat peringatan di Android Studio 3.0. Banyak AsyncTasktutorial utama di luar sana yang masih belum menanganinya (lihat di sini , di sini , di sini , dan di sini ).
  • Anda juga akan mengikuti prosedur serupa jika Anda AsyncTaskkelas atas. Kelas dalam statis pada dasarnya sama dengan kelas tingkat atas di Jawa.
  • Jika Anda tidak membutuhkan Kegiatan itu sendiri tetapi tetap menginginkan Konteks (misalnya, untuk menampilkan a Toast), Anda dapat meneruskan referensi ke konteks aplikasi. Dalam hal ini AsyncTaskkonstruktor akan terlihat seperti ini:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • Ada beberapa argumen di luar sana untuk mengabaikan peringatan ini dan hanya menggunakan kelas non-statis. Lagipula, AsyncTask dimaksudkan untuk berumur sangat pendek (beberapa detik paling lama), dan itu akan merilis referensi ke Aktivitas ketika selesai. Lihat ini dan ini .
  • Artikel yang luar biasa: Cara Membocorkan Konteks: Penangan & Kelas Dalam

Kotlin

Di Kotlin, jangan memasukkan innerkata kunci untuk kelas dalam. Ini menjadikannya statis secara default.

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
sumber
1
@ ManojFrekzz, Tidak, sebenarnya Anda dapat memperbarui UI dengan memanfaatkan referensi lemah ke Aktivitas yang diteruskan. Periksa onPostExecutemetode saya lagi dalam kode di atas. Anda dapat melihat bahwa saya memperbarui UI di TextViewsana. Cukup gunakan activity.findViewByIduntuk mendapatkan referensi ke elemen UI apa pun yang perlu Anda perbarui.
Suragch
7
+1. Ini adalah solusi terbaik dan terbersih yang pernah saya lihat! Hanya, jika Anda ingin memodifikasi UI dalam metode onPostExecute, Anda juga harus memeriksa apakah Activity sedang dihancurkan: activity.isFinishing ()
zapotec
1
Catatan! Ketika menggunakan jawaban ini, saya terus berlari ke Null Pointer Pengecualian karena operasi doInBackground adalah memori intensif, memicu pengumpulan sampah, mengumpulkan referensi lemah, dan membunuh asynctask. Anda mungkin ingin menggunakan SoftReference daripada yang lemah jika Anda tahu operasi latar belakang Anda lebih intensif memori.
PGMacDesign
2
@ Sunny, berikan referensi ke Fragmen daripada Aktivitas. Anda akan mengambil activity.isFinishing()cek dan mungkin menggantinya dengan fragment.isRemoving()cek. Saya belum banyak bekerja dengan fragmen.
Suragch
1
@ Bashan, (1) Jika kelas luar bukan Kegiatan, maka di AsyncTaskkonstruktor Anda, Anda meneruskan referensi ke kelas luar Anda. Dan di dalam doInBackground()Anda bisa mendapatkan referensi dengan kelas luar MyOuterClass ref = classReference.get(). Periksa null. (2) Di onPostExecute()Anda hanya memperbarui UI dengan hasil dari tugas latar belakang. Sama seperti waktu lainnya Anda memperbarui UI. Pemeriksaan activity.isFinishing()hanya untuk memastikan bahwa aktivitas belum mulai selesai, dalam hal ini tidak ada gunanya untuk memperbarui UI.
Suragch
23

AsyncTaskKelas ini seharusnya statis atau kebocoran mungkin terjadi karena

  • Ketika Activitydihancurkan, AsyncTask(keduanya staticatau non-static) masih berjalan
  • Jika kelas dalam adalah non-static( AsyncTask) kelas, itu akan memiliki referensi ke kelas luar ( Activity).
  • Jika suatu objek tidak memiliki referensi menunjuk ke sana, Garbage Collectedakan merilisnya. Jika suatu objek tidak digunakan dan Garbage Collected tidak dapat melepaskannya => kebocoran memori

=> Jika AsyncTaskini non-static, Activitytidak akan merilis acara itu hancur => kebocoran

Solusi untuk pembaruan UI setelah menjadikan AsyncTask sebagai kelas statis tanpa kebocoran

1) Gunakan WeakReferencejawaban seperti @Suragch
2) Kirim dan hapus Activityreferensi ke (dari)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
sumber
5
@Suragch tautan Anda menyatakan bahwa, sementara onDestroy tidak dijamin dipanggil, satu-satunya situasi di mana tidak ada adalah ketika sistem membunuh prosesnya, jadi semua sumber daya tetap dibebaskan. Jadi, jangan menyimpan di sini, tetapi Anda dapat melakukan rilis sumber daya di sini.
Angelo Fuchs
2
Dalam kasus kasus penggunaan AsyncTask non-statis, mengapa kita tidak bisa mengatur variabel instance AsyncTask ke NULL, mirip dengan ini. tidakkah ini memberitahu GC untuk membebaskan Activity meskipun AsyncTask sedang berjalan?
Hanif
@ Kanif mengatur variabel instance AsyncTask ke NUL tidak akan membantu, karena tugas masih memiliki ref melalui pendengar.
Susanta