警告:このAsyncTaskクラスは静的でなければなりません。そうでないと、リークが発生する可能性があります


270

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

このAsyncTaskクラスは静的でなければなりません。そうでないとリークが発生する可能性があります(匿名のandroid.os.AsyncTask)

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

このAsyncTaskクラスは静的である必要があります。そうしないとリークが発生する可能性があります(匿名のandroid.os.AsyncTask)。静的フィールドはコンテキストをリークします。非静的内部クラスには、外部クラスへの暗黙的な参照があります。その外部クラスが例えばフラグメントまたはアクティビティである場合、この参照は、実行時間の長いハンドラー/ローダー/タスクが、ガベージコレクションが実行されないようにするアクティビティへの参照を保持することを意味します。同様に、これらの長時間実行インスタンスからのアクティビティとフラグメントへの直接フ​​ィールド参照は、リークを引き起こす可能性があります。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

これまでのところ、必要に応じて常にAsyncTaskをrunOnUiThread(...)と組み合わせて新しいThread(...)。statr()で置き換えることができたため、この警告に対処する必要はありません。
香港

1
この問題に対するkotlinの解決策は何ですか?
TapanHP 2018

どちらが正解かを考え直してください。以下の回答をご覧ください。
Ωmega

私の場合、Activityへの直接参照がないシングルトンからこの警告が表示されmyActivity.getApplication()ます(RoomDBクラスやその他のクラスを初期化するために、シングルトンのプライベートコンストラクターへの出力を受け取ります)。My ViewModelsは、DBでいくつかの操作を実行するためのプライベートリファレンスとしてシングルトンインスタンスを取得します。そのため、ViewModelはシングルトンパッケージだけでなくandroid.app.Application、その1つもインポートしますandroid.app.Activity。「シングルトン」は機能するためにこれらのViewModelをインポートする必要がないので、メモリリークが発生する可能性がありますか?
SebasSBM 2018年

回答:


65

非静的内部クラスは、包含クラスへの参照を保持します。AsyncTask内部クラスとして宣言すると、それを含むActivityクラスよりも長く存続する場合があります。これは、包含クラスへの暗黙的な参照が原因です。これにより、アクティビティがガベージコレクションされないようになり、メモリリークが発生しなくなります。

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


1
解決策は警告自体にあります。静的ネストクラスまたは最上位クラスを使用します。
アナンド2017年

3
@KeyurNimavatアクティビティへの弱い参照を渡すことができると思います
peterchaula 2017

42
AsyncTaskを使用する意味は何ですか?Threadのrunメソッドの最後で新しいThreadとhandler.postまたはview.post(UIを更新するため)を実行する方が簡単な場合。AsyncTaskが静的または最上位のクラスである場合、そこから必要な変数/メソッドにアクセスするのは困難です
user924

8
それを使用する正しい方法がどのように提供されているコードはありません。私はそこにstaticを入れてみましたが、より多くの警告とエラーが表示されるでしょう
Kasnady

19
でより有用な答えよう@Anandこの回答を削除してくださいstackoverflow.com/a/46166223/145119が一番上にすることができます。
ミタルドゥ

557

静的内部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 Studio 3.0でのみ警告が表示され始めました。AsyncTask世の中の主要なチュートリアルの多くは、まだそれを扱っていません(ここここここ、およびここを参照してください)。
  • AsyncTask最上位クラスの場合も、同様の手順に従います。静的内部クラスは、Javaのトップレベルクラスと基本的に同じです。
  • アクティビティ自体は必要ないがコンテキスト(たとえば、を表示するToast)が必要な場合は、アプリコンテキストへの参照を渡すことができます。この場合、AsyncTaskコンストラクターは次のようになります。

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

コトリン

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。上記のコードでもう一度メソッドを確認してください。TextViewそこでUIを更新したことがわかります。activity.findViewById更新に必要なUI要素への参照を取得するために使用するだけです。
Suragch 2017

7
+1。これは私が今まで見た中で最高で最もクリーンなソリューションです!onPostExecuteメソッドでUIを変更する場合のみ、Activityが破棄されているかどうかも確認する必要があります:activity.isFinishing()
zapotec

1
注意!この回答を使用すると、doInBackground操作がメモリを大量に消費し、ガベージコレクションがトリガーされ、weakReferenceが収集され、asynctaskが強制終了されるため、Nullポインター例外が発生し続けました。バックグラウンド操作のメモリ消費量が多いことがわかっている場合は、弱い参照ではなくSoftReferenceを使用することをお勧めします。
PGMacDesign 2018

2
@Sunny、アクティビティの代わりにフラグメントへの参照を渡します。あなたはactivity.isFinishing()小切手を取り出し、おそらくそれをfragment.isRemoving()小切手で置き換えるでしょう。しかし、最近はフラグメントをあまり扱っていません。
Suragch 2018

1
@bashan、(1)外部クラスがActivityではない場合、AsyncTaskコンストラクターで外部クラスへの参照を渡します。でdoInBackground()、外部クラスへの参照を取得できますMyOuterClass ref = classReference.get()。を確認しnullます。(2)onPostExecute()バックグラウンドタスクの結果でUIを更新しているだけです。UIを更新するのは、他の場合と同じです。のチェックactivity.isFinishing()は、アクティビティがまだ終了していないことを確認するためのものです。この場合、UIを更新しても意味がありません。
Suragch 2018

23

このAsyncTaskクラスは静的でなければなりません。そうでないとリークが発生する可能性があります

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

=>もしがAsyncTaskありnon-staticActivityそれは=>リーク破壊されたイベントを解放しません。

リークなしで静的クラスとしてAsyncTaskを作成した後にUIを更新するためのソリューション

1)WeakReference@Suragch回答のように使用する
2)Activity(from)への参照を送信および削除する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が呼び出されることは保証されていないが、システムがプロセスを強制終了したときにそうでないことが唯一の状況であるため、とにかくすべてのリソースが解放されると述べています。したがって、ここでは保存を行わないでください。ただし、ここでリソースの解放を行うことができます。
アンジェロフックス

2
静的でないAsyncTaskのユースケースの場合、これと同様に、AsyncTaskインスタンス変数をNULLに設定できないのはなぜですか。AsyncTaskが実行されているにもかかわらず、GCにアクティビティを解放するように指示しませんか?
ハニフ

@Hanif AsyncTaskインスタンス変数をNULに設定しても、タスクはまだリスナーを介して参照を持っているため、役に立ちません。
Susanta
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.