警告:这个AsyncTask类应该是静态的,否则可能会出现泄漏

302

我的代码中出现了以下警告:

这个 AsyncTask 类应该是静态的,否则可能会导致内存泄漏(匿名 android.os.AsyncTask) 静态字段将泄漏上下文。非静态内部类对其外部类有隐式引用。如果这个外部类是一个 Fragment 或者 Activity,那么这个引用意味着长时间运行的 handler/loader/task 将持有对该 activity 的引用,从而阻止它被垃圾回收。同样,这些长时间运行的实例中对 activities 和 fragments 的直接字段引用也可能导致内存泄漏。ViewModel 类永远不应该指向 Views 或非应用程序 Contexts。

这是我的代码:

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

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

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

            return null;
        }
    }.execute();

我该如何纠正这个问题?


3
阅读这篇文章 http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html 可以让你明白为什么它应该是静态的。 - Raghunandan
1
到目前为止,我总是能够将AsyncTask替换为new Thread(...).start(),并在必要时与runOnUiThread(...)结合使用,这样我就不必再处理这个警告了。 - Hong
1
这个问题在Kotlin中的解决方案是什么? - TapanHP
请重新考虑哪个答案应该被接受。请参见以下答案。 - Ωmega
在我的情况下,我从一个单例中得到了这个警告,该单例没有直接引用Activity(它将myActivity.getApplication()的输出传递到单例的私有构造函数中,以初始化RoomDB类和其他类)。我的ViewModels获取单例实例作为私有引用,以对DB执行某些操作。因此,ViewModels导入单例包,以及android.app.Application,其中一个甚至是android.app.Activity。由于“单例”不需要导入这些ViewModel才能工作,所以可能会发生内存泄漏吗? - SebasSBM
3个回答

613

如何使用静态内部AsyncTask类

为了防止内存泄漏,您可以将内部类设置为静态。然而,这样做的问题是您不再能够访问Activity的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;
        }
    }
}

笔记

  • As far as I know, this type of memory leak danger has always been true, but I only started seeing the warning in Android Studio 3.0. A lot of the main AsyncTask tutorials out there still don't deal with it (see here, here, here, and here).
  • You would also follow a similar procedure if your AsyncTask were a top-level class. A static inner class is basically the same as a top-level class in Java.
  • If you don't need the Activity itself but still want the Context (for example, to display a Toast), you can pass in a reference to the app context. In this case the AsyncTask constructor would look like this:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
    
  • There are some arguments out there for ignoring this warning and just using the non-static class. After all, the AsyncTask is intended to be very short lived (a couple seconds at the longest), and it will release its reference to the Activity when it finishes anyway. See this and this.
  • Excellent article: How to Leak a Context: Handlers & Inner Classes

Kotlin

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方法。你可以看到我在那里更新了UI TextView。只需使用activity.findViewById获取对需要更新的任何UI元素的引用即可。 - Suragch
7
这是我见过的最好、最干净的解决方案!只是,如果您想在onPostExecute方法中修改UI,您还应该检查Activity是否被销毁:activity.isFinishing()。 - zapotec
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
1
@AJW,要获取上下文,请使用activityReference.get(),就像上面的示例一样。 - Suragch
显示剩余15条评论

71

非静态内部类持有对包含类的引用。当您将AsyncTask声明为内部类时,它可能会比包含的Activity类存活更长时间。这是由于对包含类的隐式引用所致。这将阻止活动被垃圾回收,因此存在内存泄漏的风险。

要解决问题,可以使用静态嵌套类而不是匿名,本地和内部类,或者使用顶层类。


2
解决方案就在警告本身。要么使用静态嵌套类,要么使用顶级类。 - Anand
3
@KeyurNimavat 我认为你可以传递一个弱引用到你的活动中。 - Peter Chaula
43
使用AsyncTask的意义在于什么?如果在Thread的run方法中运行新线程,并使用handler.post或view.post(更新UI)更容易。如果AsyncTask是静态或顶层类,则难以从中访问所需的变量/方法。 - user924
8
没有提供如何正确使用它的代码。我曾经尝试在那里加入 static,但是会出现更多警告和错误信息。 - Kasnady
19
@Anand 请删除这个回答,以便位于 https://dev59.com/J1cP5IYBdhLWcg3wf6Gb#46166223 的更有用的回答能够排在最前面。 - Mithaldu
1
@Mithaldu,据我所知,无法删除已被采纳的答案。要么问答者取消采纳,要么Stack Overflow需要修复已采纳的答案固定在顶部的问题。这个答案本身不应该因为被固定在这里就被删除或者踩。我发现这个答案很有帮助,所以我给它点了赞,即使我仍然需要进一步研究来写下你下面链接的那个答案。 - Suragch

26

如果这个 AsyncTask 类不是静态的,可能会发生内存泄漏,因为:

  • Activity 被销毁时,AsyncTask(包括 staticnon-static)仍然在运行。
  • 如果内部类是非静态的 (AsyncTask),它将引用外部类 (Activity)。
  • 如果一个对象没有被引用指向,Garbage Collected 将释放它。如果一个对象未被使用但 Garbage Collected 无法 释放它,则会造成内存泄漏。

=> 如果 AsyncTasknon-static 的,即使 Activity 被销毁,也不会被释放 => 内存泄漏。

把 AsyncTask 设为静态后更新 UI 的解决方案,而不会发生内存泄漏:

1) 像 @Suragch 回答的那样使用弱引用 (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);
        }
    }
}

2
onDestroy() 不能保证每次都会被调用。 - Suragch
5
@Suragch你的链接说明,虽然不能保证一定会调用onDestroy方法,但唯一不会被调用的情况是系统杀死进程,此时所有资源都会被释放。因此,在这里不要进行保存操作,但可以进行资源释放。 - Angelo Fuchs
2
在非静态AsyncTask使用情况下,为什么我们不能只将AsyncTask实例变量设置为NULL,类似于这样。这样做不会告诉GC释放Activity吗,尽管AsyncTask正在运行? - Hanif
@Hanif将AsyncTask实例变量设置为NULL是没有用的,因为任务仍然通过监听器保留引用。 - Susanta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接