警告:此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

到目前为止,如果有必要,我始终可以将新的Thread(...)。statr()与runOnUiThread(...)组合在一起来替换AsyncTask,因此我不再需要处理此警告。

1
Kotlin中针对此问题的解决方案是什么?
TapanHP

请重新考虑哪个答案应该被接受。请参阅下面的答案。
Ωmega

就我而言,我是从没有直接引用Activity的Singleton收到此警告的(myActivity.getApplication()为了初始化RoomDB类和其他类,它收到Singleton的私有构造函数的输出)。我的ViewModels将Singleton实例作为私有引用来对数据库执行一些操作。因此,ViewModels会导入Singleton包,android.app.Application甚至是其中之一android.app.Activity。由于“单例”不需要导入那些ViewModel即可工作,即使这样,是否可能发生内存泄漏?
SebasSBM

Answers:


64

非静态内部类持有对包含类的引用。当您声明AsyncTask为内部类时,它的寿命可能比包含Activity类更长。这是因为对包含类的隐式引用。这将防止活动被垃圾回收,从而导致内存泄漏。

要解决您的问题,请使用静态嵌套类而不是匿名,本地和内部类,或使用顶级类。


1
解决办法在于警告本身。使用静态嵌套类或顶级类。
阿南德

3
@KeyurNimavat我认为您可以对活动进行微弱提及
peterchaula

42
那么使用AsyncTask有什么意义呢?如果更容易在Thread的run方法中运行新的Thread和handler.post或view.post(以更新UI)。如果AsyncTask是静态或顶级类,则很难从中访问所需的变量/方法
user924

8
没有提供有关如何正确使用它的代码。我曾经尝试过在此处放置静态内容,但会出现更多警告和错误消息
Kasnady

19
@Anand请删除此答案,以便将stackoverflow.com/a/46166223/145119上更有用的答案放在顶部。
米萨尔杜

555

如何使用静态内部AsyncTask类

为了防止泄漏,可以将内部类设为静态。但是,这样做的问题是您不再有权访问活动的UI视图或成员变量。您可以传递对的引用,Context但随后会遇到相同的内存泄漏风险。(如果AsyncTask类具有强引用,则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那里的许多主要教程仍然不涉及它(请参见此处此处此处此处)。
  • 如果您AsyncTask是顶级班,则您也将遵循类似的过程。静态内部类基本上与Java中的顶级类相同。
  • 如果您不需要Activity本身,但仍想要Context(例如显示Toast),则可以传递对应用程序上下文的引用。在这种情况下,AsyncTask构造函数将如下所示:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • 有一些参数可以忽略此警告,而仅使用非静态类。毕竟,AsyncTask的寿命很短(最长为几秒钟),并且无论如何完成它都会释放对Activity的引用。看到这个这个
  • 优秀文章:如何泄漏上下文:处理程序和内部类

科特林

在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,不,实际上,您可以通过使用对传入的Activity的弱引用来更新UI。onPostExecute在上面的代码中再次检查我的方法。您可以看到我在TextView那里更新了UI 。只需使用activity.findViewById即可获取您需要更新的任何UI元素的参考。
Suragch '17

7
+1。这是我见过的最好,最干净的解决方案!只是,如果要在onPostExecute方法中修改UI,还应该检查Activity是否被破坏:activity.isFinishing()
zapotec

1
注意!使用此答案时,我一直遇到Null指针异常,因为doInBackground操作占用大量内存,触发了垃圾回收,收集了weakReference,并杀死了asynctask。如果您知道后台操作会占用更多的内存,则可能需要使用SoftReference而不是弱引用。
PGMacDesign '18

2
@Sunny,传递对Fragment的引用而不是Activity。您将取出activity.isFinishing()支票,并可能将其替换为fragment.isRemoving()支票。不过,我最近在片段处理方面工作不多。
Suragch

1
@bashan,(1)如果外部类不是Activity,则在AsyncTask构造函数中传递对外部类的引用。并且doInBackground()您可以使用引用外部类MyOuterClass ref = classReference.get()。检查null。(2)在其中onPostExecute(),仅使用后台任务的结果更新UI。就像您更新UI一样。检查activity.isFinishing()只是为了确保活动尚未开始完成,在这种情况下,更新UI将毫无意义。
Suragch '18

23

这个AsyncTask类应该是静态的或泄漏可能发生,因为

  • Activity被破坏,AsyncTask(两个staticnon-static)仍在运行
  • 如果内部类是non-staticAsyncTask)类,它将引用外部类(Activity)。
  • 如果一个对象没有指向它的引用,Garbage Collected将释放它。如果一个对象未​​使用并且Garbage Collected 不能释放它=>泄漏内存

=>如果AsyncTasknon-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呢?这不会告诉GC尽管AsyncTask正在运行,但可以释放Activity吗?
哈尼夫

@Hanif将AsyncTask实例变量设置为NUL将无济于事,因为任务仍然具有通过侦听器的引用。
Susanta
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.