Cảnh báo: Lớp AsyncTask này phải ở trạng thái tĩnh hoặc có thể xảy ra rò rỉ


270

Tôi nhận được một cảnh báo trong mã của tôi nói rằng:

Lớp AsyncTask này phải ở trạng thái tĩnh hoặc có thể xảy ra rò rỉ (ẩn danh android.os.AsyncTask)

Cảnh báo đầy đủ là:

Lớp AsyncTask này phải là tĩnh hoặc có thể xảy ra rò rỉ (ẩn danh android.os.AsyncTask) Trường tĩnh sẽ rò rỉ bối cảnh. Các lớp bên trong không tĩnh có một tham chiếu ngầm đến lớp bên ngoài của chúng. Nếu lớp bên ngoài đó là một mảnh hoặc Hoạt động, thì tham chiếu này có nghĩa là trình xử lý / trình tải / tác vụ chạy dài sẽ giữ một tham chiếu đến hoạt động ngăn không cho nó thu gom rác. Tương tự, tham chiếu trường trực tiếp đến các hoạt động và đoạn từ các trường hợp chạy dài hơn này có thể gây rò rỉ. Các lớp ViewModel không bao giờ nên trỏ đến Khung nhìn hoặc bối cảnh không ứng dụng.

Đây là mã của tôi:

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

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

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

            return null;
        }
    }.execute();

Làm thế nào để tôi sửa điều này?


2
đọc này androiddesignpatterns.com/2013/01/... nên cung cấp cho bạn một gợi ý về việc tại sao nó phải được tĩnh
Raghunandan

Cho đến nay, tôi luôn có thể thay thế AsyncTask bằng Chủ đề mới (...). Statr () kết hợp với runOnUiThread (...) nếu cần, vì vậy tôi không phải đối phó với cảnh báo này nữa.
Hồng

1
Giải pháp trong kotlin cho vấn đề này là gì?
TapanHP

Vui lòng xem xét lại câu trả lời nào nên được chấp nhận. Xem câu trả lời dưới đây.
Ωmega

Trong trường hợp của tôi, tôi nhận được cảnh báo này từ một Singleton không có tham chiếu trực tiếp đến Hoạt động (nó nhận đầu ra của myActivity.getApplication()hàm tạo riêng cho Singleton, để khởi tạo các lớp RoomDB và các lớp khác). ViewModels của tôi lấy phiên bản Singleton làm tham chiếu riêng để thực hiện một số thao tác trên DB. Vì vậy, ViewModels nhập gói Singleton, cũng như android.app.Applicationmột trong số chúng android.app.Activity. Vì "Singleton" không cần nhập các ViewModels đó để hoạt động, nên có thể xảy ra rò rỉ bộ nhớ?
SebasSBM

Câu trả lời:


65

Các lớp bên trong không tĩnh giữ một tham chiếu đến lớp chứa. Khi bạn khai báo AsyncTasklà một lớp bên trong, nó có thể sống lâu hơn Activitylớp chứa . Điều này là do tham chiếu ngầm đến lớp chứa. Điều này sẽ ngăn hoạt động được thu gom rác, do đó bộ nhớ bị rò rỉ.

Để giải quyết vấn đề của bạn, hãy sử dụng lớp lồng tĩnh thay vì ẩn danh, cục bộ và lớp bên trong hoặc sử dụng lớp cấp cao nhất.


1
Giải pháp nằm trong chính cảnh báo. Hoặc sử dụng lớp lồng tĩnh hoặc lớp cấp cao nhất.
Anand

3
@KeyurNimavat Tôi nghĩ rằng bạn có thể chuyển qua một tài liệu tham khảo yếu cho hoạt động của mình
peterchaula

42
Vậy quan điểm của việc sử dụng AsyncTask là gì? nếu việc chạy Thread và handler.post hoặc view.post (để cập nhật UI) dễ dàng hơn ở cuối phương thức chạy Thread. Nếu AsyncTask là lớp tĩnh hoặc cấp cao nhất thì rất khó truy cập các biến / phương thức cần thiết từ nó
user924

8
không có mã được cung cấp như thế nào là cách sử dụng đúng. Tôi đã từng thử đặt tĩnh tại đó, nhưng sẽ có nhiều cảnh báo và lỗi xuất hiện hơn
Kasnady

19
@Anand Vui lòng xóa câu trả lời này để câu trả lời hữu ích hơn tại stackoverflow.com/a/46166223/145119 có thể đứng đầu.
Mithaldu

557

Cách sử dụng lớp AsyncTask tĩnh bên trong

Để ngăn chặn rò rỉ, bạn có thể làm cho lớp bên trong tĩnh. Tuy nhiên, vấn đề với điều đó là bạn không còn có quyền truy cập vào các giao diện UI hoặc các biến thành viên của Activity. Bạn có thể chuyển tham chiếu đến Contextnhưng sau đó bạn có nguy cơ bị rò rỉ bộ nhớ tương tự. (Android không thể thu thập hoạt động sau khi đóng nếu lớp AsyncTask có tham chiếu mạnh đến nó.) Giải pháp là tạo một tham chiếu yếu đến Hoạt động (hoặc bất cứ điều gì Contextbạn cần).

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

Ghi chú

  • Theo tôi biết, loại nguy hiểm rò rỉ bộ nhớ này luôn luôn đúng, nhưng tôi chỉ bắt đầu thấy cảnh báo trong Android Studio 3.0. Rất nhiều AsyncTaskhướng dẫn chính ngoài kia vẫn không giải quyết được (xem ở đây , ở đây , ở đâyở đây ).
  • Bạn cũng sẽ làm theo một quy trình tương tự nếu AsyncTask là một lớp cấp cao nhất. Một lớp bên trong tĩnh về cơ bản giống như một lớp cấp cao nhất trong Java.
  • Nếu bạn không cần Hoạt động mà vẫn muốn Bối cảnh (ví dụ: để hiển thị a Toast), bạn có thể chuyển tham chiếu đến ngữ cảnh ứng dụng. Trong trường hợp này, hàm AsyncTasktạo sẽ trông như thế này:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • Có một số đối số ngoài kia để bỏ qua cảnh báo này và chỉ sử dụng lớp không tĩnh. Rốt cuộc, AsyncTask dự định sẽ tồn tại rất ngắn (một vài giây lâu nhất) và nó sẽ phát hành tham chiếu đến Hoạt động khi nó kết thúc. Xem cái nàycái này .
  • Bài viết xuất sắc: Làm thế nào để rò rỉ bối cảnh: Người xử lý & Lớp học bên trong

Kotlin

Trong Kotlin chỉ không bao gồm innertừ khóa cho lớp bên trong. Điều này làm cho nó tĩnh theo mặc định.

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

1
@ManojFrekzz, Không, thực sự bạn có thể cập nhật giao diện người dùng bằng cách sử dụng tham chiếu yếu đến Hoạt động được truyền vào. Kiểm tra onPostExecutelại phương thức của tôi trong đoạn mã trên. Bạn có thể thấy rằng tôi đã cập nhật giao diện người dùng TextViewở đó. Chỉ cần sử dụng activity.findViewByIdđể có được một tham chiếu đến bất kỳ yếu tố UI nào bạn cần cập nhật.
Suragch

7
+1. Đây là giải pháp tốt nhất và sạch nhất tôi từng thấy! Chỉ trong trường hợp bạn muốn sửa đổi UI trong phương thức onPostExecute, bạn cũng nên kiểm tra xem Activity có bị hủy hay không: Activity.isFinishing ()
zapotec

1
Ghi chú! Khi sử dụng câu trả lời này, tôi tiếp tục chạy vào Null Pulum Exceptions vì hoạt động doInBackground chiếm nhiều bộ nhớ, kích hoạt bộ sưu tập rác, thu thập yếu tố chính và giết chết asynctask. Bạn có thể muốn sử dụng SoftReference thay vì yếu nếu bạn biết các thao tác nền của mình tốn nhiều bộ nhớ hơn.
PGMacDesign

2
@Sunny, chuyển tham chiếu đến Đoạn thay vì Hoạt động. Bạn sẽ lấy activity.isFinishing()séc ra và có thể thay thế bằng fragment.isRemoving()séc. Gần đây tôi đã không làm việc nhiều với các mảnh vỡ.
Suragch

1
@bashan, (1) Nếu lớp bên ngoài không phải là Hoạt động, thì trong hàm tạo của AsyncTaskbạn, bạn chuyển tham chiếu đến lớp bên ngoài. Và trong doInBackground()bạn có thể có được một tài liệu tham khảo cho lớp bên ngoài với MyOuterClass ref = classReference.get(). Kiểm tra cho null. (2) Trong onPostExecute()bạn chỉ cập nhật giao diện người dùng với kết quả từ tác vụ nền. Nó cũng giống như bất kỳ lúc nào bạn cập nhật giao diện người dùng. Việc kiểm tra activity.isFinishing()chỉ là để đảm bảo rằng hoạt động chưa bắt đầu kết thúc, trong trường hợp đó sẽ là vô nghĩa khi cập nhật giao diện người dùng.
Suragch

23

AsyncTaskLớp này nên tĩnh hoặc rò rỉ có thể xảy ra vì

  • Khi Activitybị phá hủy, AsyncTask(cả hai statichoặcnon-static ) vẫn chạy
  • Nếu lớp bên trong là lớp non-static( AsyncTask), nó sẽ có tham chiếu đến lớp bên ngoài ( Activity).
  • Nếu một đối tượng không có điểm tham chiếu đến nó, Garbage Collectedsẽ giải phóng nó. Nếu một đối tượng không được sử dụng và Garbage Collected không thể giải phóng nó => bộ nhớ bị rò rỉ

=> Nếu AsyncTasknon-static,Activity sẽ không phát hành sự kiện nó bị phá hủy => rò rỉ

Giải pháp cập nhật giao diện người dùng sau khi tạo AsyncTask dưới dạng lớp tĩnh mà không bị rò rỉ

1) Sử dụng WeakReferencenhư @Suragch trả lời
2) Gửi và xóa Activitytham chiếu đến (từ)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);
        }
    }
}


5
@Suragch liên kết của bạn nói rằng, trong khi onDestroy không được đảm bảo để được gọi, tình huống duy nhất không phải là khi hệ thống giết quá trình, vì vậy mọi tài nguyên đều được giải phóng. Vì vậy, đừng tiết kiệm ở đây, nhưng bạn có thể phát hành tài nguyên ở đây.
Fuchs Angelo

2
Trong trường hợp sử dụng AsyncTask không tĩnh, tại sao chúng ta không thể đặt biến đối tượng AsyncTask thành NULL, tương tự như thế này. Điều này có nói với GC về Hoạt động miễn phí mặc dù AsyncTask đang chạy không?
Hanif

@Hanif đặt biến đối tượng AsyncTask thành NUL sẽ không giúp ích, vì tác vụ vẫn có ref thông qua trình nghe.
Susanta
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.