【问题标题】:Warning: This AsyncTask class should be static or leaks might occur警告:此 AsyncTask 类应该是静态的,否则可能会发生泄漏
【发布时间】:2017-11-02 16:52:17
【问题描述】:

我在代码中收到一条警告:

此 AsyncTask 类应该是静态的,否则可能会发生泄漏(匿名 android.os.AsyncTask)

完整的警告是:

此 AsyncTask 类应该是静态的,否则可能会发生泄漏(匿名 android.os.AsyncTask) 静态字段会泄漏上下文。非静态内部类具有对其外部类的隐式引用。如果该外部类例如是 Fragment 或 Activity,则此引用意味着长时间运行的处理程序/加载程序/任务将持有对该 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();

我该如何纠正这个问题?

【问题讨论】:

  • 阅读此androiddesignpatterns.com/2013/01/… 应该会提示您为什么它应该是静态的
  • 到目前为止,我一直能够在必要时将 AsyncTask 替换为 new Thread(...).statr() 并结合 runOnUiThread(...) ,所以我不必处理不再有这个警告了。
  • kotlin 中针对这个问题的解决方案是什么?
  • 请重新考虑哪个答案应该被接受。请参阅下面的答案。
  • 在我的例子中,我从没有直接引用 Activity 的单例中收到此警告(它接收 myActivity.getApplication() 的输出到单例的私有构造函数中,以便初始化 RoomDB 类和其他类)。我的 ViewModel 将 Singleton 实例作为私有引用,以在数据库上执行一些操作。因此,ViewModel 导入了 Singleton 包以及 android.app.Application,其中之一甚至是 android.app.Activity。由于“Singleton”不需要导入这些 ViewModel 即可工作,即使如此,是否会发生内存泄漏?

标签: android android-asynctask android-runonuithread


【解决方案1】:

如何使用静态内部 AsyncTask 类

为防止泄漏,您可以将内部类设为静态。但是,问题在于您不再有权访问 Activity 的 UI 视图或成员变量。您可以传入对Context 的引用,但同样会面临内存泄漏的风险。 (如果 AsyncTask 类对 Activity 有强引用,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 教程仍然没有处理它(请参阅herehereherehere)。
  • 如果您的AsyncTask 是顶级课程,您也将遵循类似的程序。静态内部类与 Java 中的顶级类基本相同。
  • 如果您不需要 Activity 本身但仍需要 Context(例如,显示Toast),则可以传入对应用上下文的引用。在这种情况下,AsyncTask 构造函数将如下所示:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
    
  • 有一些争论可以忽略此警告而只使用非静态类。毕竟,AsyncTask 的生命周期很短(最长几秒钟),无论如何它都会在完成时释放对 Activity 的引用。请参阅thisthis
  • 优秀文章:How to Leak a Context: Handlers & Inner Classes

科特林

在 Kotlin 中,内部类只需 don't include the inner keyword。这使其默认为静态。

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

【讨论】:

  • @ManojFrekzz,不,实际上您可以通过使用传入的 Activity 的弱引用来更新 UI。再次查看我的 onPostExecute 方法上面的代码。你可以看到我在那里更新了 UI TextView。只需使用 activity.findViewById 获取对您需要更新的任何 UI 元素的引用。
  • +1。这是我见过的最好和最干净的解决方案!只是,如果你想在 onPostExecute 方法中修改 UI,你还应该检查 Activity 是否被销毁:activity.isFinishing()
  • 注意!在使用这个答案时,我一直遇到空指针异常,因为 doInBackground 操作是内存密集型的,触发了垃圾收集,收集了弱引用,并终止了异步任务。如果您知道后台操作会占用更多内存,则可能需要使用 SoftReference 而不是弱引用。
  • @Sunny,传入对 Fragment 的引用而不是 Activity。您将取出 activity.isFinishing() 支票并可能将其替换为 fragment.isRemoving() 支票。不过,我最近没怎么处理 Fragments。
  • @bashan, (1) 如果外部类不是 Activity,那么在 AsyncTask 构造函数中传入对外部类的引用。在doInBackground() 中,您可以使用MyOuterClass ref = classReference.get() 获得对外部类的引用。检查null。 (2) 在onPostExecute() 中,您仅使用后台任务的结果更新 UI。就像您更新 UI 的任何其他时间一样。检查activity.isFinishing() 只是为了确保活动尚未开始完成,在这种情况下更新 UI 将毫无意义。
【解决方案2】:

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

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

=> 如果AsyncTasknon-staticActivity 不会释放它被销毁的事件 => 泄漏

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

【讨论】:

  • @Suragch 您的链接指出,虽然不能保证调用 onDestroy,但唯一不能保证调用的情况是系统终止进程,因此无论如何都会释放所有资源。所以,不要在这里做保存,但你可以在这里做资源释放。
  • 在非静态 AsyncTask 用例的情况下,为什么我们不能只将 AsyncTask 实例变量设置为 NULL,类似于此。尽管 AsyncTask 正在运行,这不会告诉 GC 释放 Activity 吗?
  • @Hanif 将 AsyncTask 实例变量设置为 NUL 将无济于事,因为任务仍然具有通过侦听器的引用。
【解决方案3】:

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

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

【讨论】:

  • 解决方案在于警告本身。使用静态嵌套类或顶级类。
  • @KeyurNimavat 我认为您可以传入对您的活动的弱引用
  • 那么使用 AsyncTask 有什么意义呢?如果在 Thread 的 run 方法中运行 new Thread 和 handler.post 或 view.post (更新 UI)更容易。如果 AsyncTask 是静态或顶级类,则很难从中访问所需的变量/方法
  • 没有提供如何正确使用它的代码。我曾尝试将静态放在那里,但会出现更多警告和错误
  • @Anand 请删除此答案,以便stackoverflow.com/a/46166223/145119 上更有用的答案可以在顶部。
猜你喜欢
  • 2020-10-30
  • 2019-09-03
  • 2013-09-22
  • 2012-07-09
  • 1970-01-01
  • 2012-11-10
  • 2013-08-14
相关资源
最近更新 更多