我在代码中收到一条警告,指出:
此 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();
我该如何纠正?
myActivity.getApplication()
的输出接收到 Singleton 的私有构造函数中,以初始化 RoomDB 类和其他类)。我的 ViewModel 将 Singleton 实例作为私有引用,以在数据库上执行一些操作。因此,ViewModel 导入了 Singleton 包以及 android.app.Application
,其中之一甚至是 android.app.Activity
。由于“Singleton”不需要导入那些 ViewModel 来工作,即便如此,是否会发生内存泄漏?
如何使用静态内部 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
有一些论点可以忽略此警告并仅使用非静态类。毕竟,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
}
}
}
非静态内部类包含对包含类的引用。当您将 AsyncTask
声明为内部类时,它的寿命可能比包含 Activity
的类长。这是因为对包含类的隐式引用。这将防止活动被垃圾收集,从而防止内存泄漏。
要解决您的问题,请使用静态嵌套类而不是匿名、本地和内部类,或者使用顶级类。
这个 AsyncTask
类应该是静态的,否则可能会发生泄漏,因为
当 Activity 被销毁时,AsyncTask(无论是静态的还是非静态的)仍在运行
如果内部类是非静态(AsyncTask)类,它将引用外部类(Activity)。
如果一个对象没有指向它的引用,Garbage Collected 将释放它。如果一个对象未被使用并且垃圾收集不能释放它 => 泄漏内存
=>如果 AsyncTask
是 non-static
,则 Activity
不会释放它被销毁的事件 =>泄漏
将 AsyncTask 设为静态类后更新 UI 的解决方案
1) 像 @Suragch answer
一样使用 WeakReference
2) 发送和删除对 (from) AsyncTask
的 Activity
引用
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);
}
}
}
onPostExecute
方法。您可以看到我在那里更新了 UITextView
。只需使用activity.findViewById
来获取对您需要更新的任何 UI 元素的引用。activity.isFinishing()
检查并可能用fragment.isRemoving()
检查替换它。不过,我最近对片段的工作不多。AsyncTask
构造函数中传入对外部类的引用。在doInBackground()
中,您可以使用MyOuterClass ref = classReference.get()
获得对外部类的引用。检查null
。 (2) 在onPostExecute()
中,您仅使用后台任务的结果更新 UI。就像您更新 UI 的任何其他时间一样。检查activity.isFinishing()
只是为了确保 Activity 尚未开始完成,在这种情况下更新 UI 将毫无意义。