ChatGPT解决这个技术问题 Extra ChatGPT

警告:此 AsyncTask 类应该是静态的,否则可能会发生泄漏

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

此 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 的 Singleton 收到此警告(它将 myActivity.getApplication() 的输出接收到 Singleton 的私有构造函数中,以初始化 RoomDB 类和其他类)。我的 ViewModel 将 Singleton 实例作为私有引用,以在数据库上执行一些操作。因此,ViewModel 导入了 Singleton 包以及 android.app.Application,其中之一甚至是 android.app.Activity。由于“Singleton”不需要导入那些 ViewModel 来工作,即便如此,是否会发生内存泄漏?

S
Suragch

如何使用静态内部 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 教程仍然没有处理它(参见这里、这里、这里和这里)。

如果您的 AsyncTask 是顶级类,您也将遵循类似的过程。静态内部类与 Java 中的顶级类基本相同。

如果您不需要 Activity 本身但仍需要 Context(例如,显示 Toast),则可以传入对应用程序上下文的引用。在这种情况下,AsyncTask 构造函数如下所示: private WeakReference appReference; MyTask(Application context) { appReference = new WeakReference<>(context); }

有一些论点可以忽略此警告并仅使用非静态类。毕竟,AsyncTask 的生命周期很短(最长几秒钟),无论如何它都会在完成时释放对 Activity 的引用。看到这个和这个。

优秀文章:如何泄漏上下文:处理程序和内部类

科特林

在 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,传入对片段而不是活动的引用。您将取出 activity.isFinishing() 检查并可能用 fragment.isRemoving() 检查替换它。不过,我最近对片段的工作不多。
@bashan,(1) 如果外部类不是 Activity,则在 AsyncTask 构造函数中传入对外部类的引用。在 doInBackground() 中,您可以使用 MyOuterClass ref = classReference.get() 获得对外部类的引用。检查 null。 (2) 在 onPostExecute() 中,您仅使用后台任务的结果更新 UI。就像您更新 UI 的任何其他时间一样。检查 activity.isFinishing() 只是为了确保 Activity 尚未开始完成,在这种情况下更新 UI 将毫无意义。
A
Anand

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

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


解决方案在于警告本身。使用静态嵌套类或顶级类。
@KeyurNimavat 我认为您可以传递对您的活动的弱引用
那么使用 AsyncTask 有什么意义呢?如果在 Thread 的 run 方法中运行 new Thread 和 handler.post 或 view.post (更新 UI)更容易。如果 AsyncTask 是静态或顶级类,则很难从中访问所需的变量/方法
没有提供如何正确使用它的代码。我曾尝试将静态放在那里,但会出现更多警告和错误
@Anand 请删除此答案,以便 stackoverflow.com/a/46166223/145119 处更有用的答案可以在顶部。
L
Linh

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

当 Activity 被销毁时,AsyncTask(无论是静态的还是非静态的)仍在运行

如果内部类是非静态(AsyncTask)类,它将引用外部类(Activity)。

如果一个对象没有指向它的引用,Garbage Collected 将释放它。如果一个对象未被使用并且垃圾收集不能释放它 => 泄漏内存

=>如果 AsyncTasknon-static,则 Activity 不会释放它被销毁的事件 =>泄漏

将 AsyncTask 设为静态类后更新 UI 的解决方案

1) 像 @Suragch answer
一样使用 WeakReference 2) 发送和删除对 (from) AsyncTaskActivity 引用

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 将无济于事,因为任务仍然具有通过侦听器的引用。