web-dev-qa-db-ja.com

警告:このAsyncTaskクラスには、静的であるべきか、リークが発生する可能性があります

コードに次のような警告が表示されます。

このAsyncTaskクラスは静的にする必要があります。そうしないとリークが発生する可能性があります(匿名Android.os.AsyncTask)

完全な警告は次のとおりです。

このAsyncTaskクラスは静的にする必要があります。そうしないとリークが発生する可能性があります(anonymous 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();

どうすればこれを修正できますか?

197
Keyur Nimavat

非静的内部クラスは、それを含むクラスへの参照を保持します。 AsyncTaskを内部クラスとして宣言すると、それを含むActivityクラスよりも寿命が長くなる可能性があります。これは包含クラスへの暗黙の参照のためです。これにより、アクティビティがガベージコレクションされるのを防ぐことができるため、メモリリークが発生します。

問題を解決するには、匿名クラス、ローカルクラス、および内部クラスの代わりに静的ネストクラスを使用するか、最上位クラスを使用します。

42
Anand

静的な内部AsyncTaskクラスを使用する方法

リークを防ぐために、内部クラスを静的にすることができます。ただし、それに関する問題は、アクティビティのUIビューまたはメンバー変数にアクセスできなくなることです。 Contextへの参照を渡すことができますが、メモリリークの同じリスクが発生します。 (AsyncTaskクラスが強い参照を持っている場合、Androidは閉じた後にアクティビティをガベージコレクションできません。)解決策は、アクティビティ(または必要な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 St​​udio 3.0で警告が表示されるようになりました。主なAsyncTaskチュートリアルの多くはまだ対処していません( hereherehere 、および ここ )。
  • AsyncTaskが最上位クラスの場合も、同様の手順に従います。静的内部クラスは、基本的にJavaのトップレベルクラスと同じです。
  • アクティビティ自体は必要ないが、コンテキストが必要な場合(たとえば、Toastを表示する場合)、アプリコンテキストへの参照を渡すことができます。この場合、AsyncTaskコンストラクターは次のようになります。

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
    
  • この警告を無視し、非静的クラスを使用するためのいくつかの議論があります。結局のところ、AsyncTaskは非常に短命(最長で数秒)であることが意図されており、とにかく終了するとアクティビティへの参照を解放します。 this および this を参照してください。
  • 優れた記事: コンテキストのリーク方法:ハンドラーと内部クラス

コトリン

Kotlinでは、内部クラスに対して innerキーワードを含めないでください だけです。これにより、デフォルトで静的になります。

私はまだKotlinが得意ではないので、改善できる場合は以下のコードを修正してください。

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

このAsyncTaskクラスは静的であるべきで、そうでないとリークが発生する可能性があります

  • Activityが破壊されても、AsyncTaskstaticまたはnon-staticの両方)はまだ実行中です
  • 内部クラスがnon-staticAsyncTask)クラスの場合、それは外部クラス(Activity)への参照を持ちます。
  • オブジェクトがそれを参照する参照がない場合、Garbage Collectedはそれを解放します。オブジェクトが未使用でGarbage Collected できない場合 release =>>メモリリーク

=> AsyncTasknon-staticの場合、Activityは破棄されたイベントを解放しません=> leak

AsyncTaskをリークのない静的クラスにした後のUIの更新のためのソリューション

1)@Suragch answerのようにWeakReferenceを使用
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);
        }
    }
}
19
Phan Van Linh