경고 :이 AsyncTask 클래스는 정적이거나 누수가 발생할 수 있습니다.


270

내 코드에 다음과 같은 경고가 표시됩니다.

이 AsyncTask 클래스는 정적이거나 누수가 발생할 수 있습니다 (익명 android.os.AsyncTask)

완전한 경고는 다음과 같습니다.

이 AsyncTask 클래스는 정적이거나 누출이 발생할 수 있습니다 (익명 android.os.AsyncTask) 정적 필드는 컨텍스트를 누출합니다. 비 정적 내부 클래스에는 외부 클래스에 대한 암시 적 참조가 있습니다. 해당 외부 클래스가 예를 들어 Fragment 또는 Activity 인 경우이 참조는 장기 실행 핸들러 / 로더 / 태스크가 가비지 수집을 방해하는 활동에 대한 참조를 보유 함을 의미합니다. 마찬가지로, 더 오래 실행되는 인스턴스의 활동 및 단편에 대한 직접 필드 참조는 누출을 일으킬 수 있습니다. ViewModel 클래스는 절대로 뷰 또는 비 응용 컨텍스트를 가리켜서는 안됩니다.

이것은 내 코드입니다.

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

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

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

            return null;
        }
    }.execute();

이 문제를 어떻게 해결합니까?


2
androiddesignpatterns.com/2013/01/… 을 읽으면 왜 정적이어야하는지 힌트를 얻을 수 있습니다.
Raghunandan

지금까지 필요에 따라 runOnUiThread (...)와 함께 AsyncTask를 새 Thread (...). statr ()로 대체 할 수 있었 으므로이 경고를 더 이상 처리 할 필요가 없습니다.
Hong

1
이 문제에 대한 kotlin의 솔루션은 무엇입니까?
TapanHP

어떤 답변을 받아 들여야하는지 다시 생각해보십시오. 아래 답변을 참조하십시오.
Ωmega

필자의 경우 Activity에 대한 직접적인 참조가없는 Singleton 에서이 경고를 얻습니다 ( myActivity.getApplication()RoomDB 클래스 및 기타 클래스를 초기화하기 위해 Singleton의 개인 생성자로 출력을받습니다 ). My ViewModels는 DB에서 일부 작업을 수행하기 위해 Singleton 인스턴스를 프라이빗 참조로 가져옵니다. 따라서 ViewModel은 Singleton 패키지와 패키지 android.app.Application중 하나도 가져옵니다 android.app.Activity. "싱글 톤"은 이러한 ViewModel을 가져와도 작동하지 않아도 메모리 누수가 발생할 수 있습니까?
SebasSBM

답변:


64

비 정적 내부 클래스는 포함 클래스에 대한 참조를 보유합니다. AsyncTask내부 클래스로 선언 하면 포함 Activity클래스 보다 오래 살 수 있습니다 . 이것은 포함하는 클래스에 대한 암시 적 참조 때문입니다. 이렇게하면 활동이 가비지 수집되지 않으므로 메모리 누수가 발생합니다.

문제를 해결하려면 익명, 로컬 및 내부 클래스 대신 정적 중첩 클래스를 사용하거나 최상위 클래스를 사용하십시오.


1
해결책은 경고 자체에 있습니다. 정적 중첩 클래스 또는 최상위 클래스를 사용하십시오.
Anand

3
@KeyurNimavat 나는 당신이 당신의 활동에 대한 약한 참조를 전달할 수 있다고 생각합니다
peterchaula

42
AsyncTask를 사용하는 요점은 무엇입니까? Thread의 run 메소드가 끝날 때 새로운 Thread 및 handler.post 또는 view.post (UI 업데이트)를 실행하는 것이 더 쉬운 경우. AsyncTask가 정적 또는 최상위 클래스 인 경우 필요한 변수 / 방법에 액세스하기가 어렵습니다.
user924

8
올바른 사용법을 알려주는 코드는 없습니다. 나는 거기에 정적 인 것을 시도했지만 더 많은 경고와 오류가 나타날 것입니다
Kasnady

19
@Anand이 답변을 삭제하여 stackoverflow.com/a/46166223/145119 에서 더 유용한 답변 이 맨 위에 오도록하십시오.
Mithaldu

556

정적 내부 AsyncTask 클래스를 사용하는 방법

누출을 방지하기 위해 내부 클래스를 정적으로 만들 수 있습니다. 그러나 문제는 더 이상 활동의 UI보기 또는 멤버 변수에 액세스 할 수 없다는 것입니다. 에 대한 참조를 전달할 수 Context있지만 동일한 메모리 누수 위험이 있습니다. (AsyncTask 클래스에 강한 참조가있는 경우 Android를 닫은 후에는 Android에서 Activity를 가비지 수집 할 수 없습니다.) 해결책은 Activity (또는 Context필요한 것) 를 약하게 참조하는 것 입니다.

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

노트

  • 내가 아는 한,이 유형의 메모리 누수 위험은 항상 사실이지만 Android Studio 3.0에서만 경고가 표시되기 시작했습니다. 많은 주요 AsyncTask튜토리얼은 여전히 ​​다루지 않습니다 ( here , here , herehere 참조 ).
  • 당신이 AsyncTask최상위 클래스 라면 비슷한 절차를 따를 것 입니다. 정적 내부 클래스는 기본적으로 Java의 최상위 클래스와 동일합니다.
  • 액티비티 자체는 필요하지 않지만 여전히 컨텍스트 (예 :)를 표시 Toast하려는 경우 앱 컨텍스트에 대한 참조를 전달할 수 있습니다. 이 경우 AsyncTask생성자는 다음과 같습니다.

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • 이 경고를 무시하고 정적이 아닌 클래스를 사용한다는 몇 가지 주장이 있습니다. 결국 AsyncTask는 매우 짧은 수명 (최대 2 초)이며 활동이 완료되면 액티비티에 대한 참조를 해제합니다. 참조 .
  • 훌륭한 기사 : 컨텍스트를 누출하는 방법 : 처리기 및 내부 클래스

코 틀린

Kotlin에서는 내부 클래스에 대한 키워드포함시키지 마십시오inner . 기본적으로 정적입니다.

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, 아니요, 실제로 전달 된 활동에 대한 약한 참조를 사용하여 UI를 업데이트 할 수 있습니다 . onPostExecute위 코드에서 내 메소드를 다시 확인하십시오 . UI TextView가 업데이트되었음을 ​​알 수 있습니다 . 그냥 사용 activity.findViewById하면 업데이트해야 어떤 UI 요소에 대한 참조를 가져옵니다.
Suragch

7
+1. 이것은 내가 본 것 중 가장 깨끗하고 깨끗한 솔루션입니다! onPostExecute 메소드에서 UI를 수정하려는 경우에만 Activity가 삭제되는지 여부도 확인해야합니다. activity.isFinishing ()
zapotec

1
노트! 이 답변을 사용할 때 doInBackground 작업이 메모리를 많이 사용하고 가비지 수집을 트리거하고 weakReference를 수집하여 비동기 작업을 종료했기 때문에 Null 포인터 예외가 계속 발생했습니다. 백그라운드 작업이 메모리를 많이 사용한다는 것을 알고 있다면 약한 것이 아니라 SoftReference를 사용하는 것이 좋습니다.
PGMacDesign

2
@Sunny, Activity 대신 Fragment에 대한 참조를 전달하십시오. activity.isFinishing()수표 를 꺼내서 수표로 교체 할 수 fragment.isRemoving()있습니다. 그래도 최근에는 조각으로 많은 일을하지 않았습니다.
Suragch

1
@bashan, (1) 외부 클래스가 활동이 아닌 경우 AsyncTask생성자에서 외부 클래스에 대한 참조를 전달합니다. 그리고에서 doInBackground()외부 클래스에 대한 참조를 얻을 수 있습니다 MyOuterClass ref = classReference.get(). 확인하십시오 null. (2) onPostExecute()백그라운드 작업의 결과로만 UI를 업데이트합니다. UI를 업데이트하는 것은 다른 때와 같습니다. 점검 activity.isFinishing()은 활동이 아직 완료되지 않았는지 확인하는 것인데,이 경우 UI를 업데이트하는 것은 의미가 없습니다.
Suragch

23

AsyncTask클래스는 정적이어야합니다. 그렇지 않으면 누수가 발생할 수 있습니다.

  • Activity파괴, AsyncTask(모두 static또는non-static )은 여전히 실행
  • 내부 클래스가 non-static( AsyncTask) 클래스 인 경우 외부 클래스 ( Activity)를 참조하게됩니다 .
  • 오브젝트에 대한 참조가 없으면 Garbage Collected해제합니다. 객체를 사용하지 Garbage Collected 않고 해제 할 수없는 경우 => 메모리 누수

=> 경우가 AsyncTask있다 non-static,Activity => 누출이 파괴 이벤트를 공개하지 않습니다

누출없이 AsyncTask를 정적 클래스로 만든 후 업데이트 UI 솔루션

1) 사용 WeakReference과 같은 @Suragch 답
2) 보내기 및 제거 Activity를 참조 (에서)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는 onDestroy가 호출되는 것을 보장하지는 않지만 시스템이 프로세스를 종료 할 때만 그렇지 않은 경우에만 모든 리소스가 해제된다고 링크에 표시합니다. 여기에서 저장하지 말고 여기에서 리소스 릴리스를 수행 할 수 있습니다.
Angelo Fuchs

2
정적이 아닌 AsyncTask 유스 케이스의 경우 왜 AsyncTask 인스턴스 변수를 NULL로 설정해야합니까? AsyncTask가 실행 중이지만 GC에 활동을 해제하도록 지시하지 않습니까?
Hanif

@Hanif AsyncTask 인스턴스 변수를 NUL로 설정하면 작업에 리스너를 통한 참조가 있기 때문에 도움이되지 않습니다.
Susanta
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.