如果用户已经离开它,AsyncTask如何仍然可以使用一个Activity?

在Android上,您可以使用RunnableAsyncTask在单独的Thread工作。 在这两种情况下,在完成工作之后,您可能需要做一些工作,例如通过重写AsyncTask中的onPostExecute() 。 但是,在后台完成工作时,用户可能会导航或closures应用程序。

我的问题是:如果用户导航或closures应用程序时,会发生什么,而我仍然有一个引用的用户刚在我的AsyncTaskclosures的Activity

我的猜测是,只要用户导航,它应该被销毁,但是当我出于某种原因在设备上进行testing时,我仍然可以调用Activity上的方法,即使它已经消失了! 这里发生了什么?

Solutions Collecting From Web of "如果用户已经离开它,AsyncTask如何仍然可以使用一个Activity?"

简单的答案:你刚刚发现

内存泄漏

只要像AsyncTask这样的应用程序的某些部分仍然持有对Activity的引用,它就不会被销毁 。 它将坚持到AsyncTask完成或以其他方式释放其引用。 这可能会导致非常糟糕的后果,比如您的应用程序崩溃,但是最糟糕的后果是您没有注意到的后果:您的应用程序可能会继续引用应该在几年前已经发布的Activities ,并且每次用户做任何泄漏Activity在设备上可能会变得越来越充满,直到看似不知何故,Android会杀死你的应用程序消耗太多的内存。 内存泄漏是我在Stack Overflow的Android问题中看到的最常见和最糟糕的错误


解决scheme

避免内存泄漏非常简单:你的AsyncTask不应该有一个ActivityService或任何其他UI组件的引用。

而是使用侦听器模式,并始终使用WeakReference 。 切勿在AsyncTask之外保留强引用。


几个例子

AsyncTask引用一个View

一个正确实现的使用ImageView AsyncTask可能如下所示:

 public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { private final WeakReference<ImageView> mImageViewReference; public ExampleTask(ImageView imageView) { mImageViewReference = new WeakReference<>(imageView); } @Override protected Bitmap doInBackground(Void... params) { ... } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); final ImageView imageView = mImageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); } } } 

这完全说明了WeakReference作用。 WeakReferences允许它们引用的Object被垃圾回收。 所以在这个例子中,我们在AsyncTask的构造函数中创build了一个WeakReference 。 然后在onPostExecute() ,当ImageView不存在时,可能会在10秒后调用它,我们在WeakReference上调用get()来查看ImageView存在。 只要get()返回的ImageView不为null, ImageView就不会被垃圾回收,因此我们可以不用担心! 在此期间用户应该退出应用程序,那么ImageView将立即变得适合垃圾收集,如果AsyncTask在一段时间后完成,它将看到ImageView已经消失。 没有内存泄漏,没有问题。


使用一个监听器

 public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { public interface Listener { void onResult(Bitmap image); } private final WeakReference<Listener> mListenerReference; public ExampleTask(Listener listener) { mListenerReference = new WeakReference<>(listener); } @Override protected Bitmap doInBackground(Void... params) { ... } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); final Listener listener = mListenerReference.get(); if (listener != null) { listener.onResult(bitmap); } } } 

这看起来很相似,因为它实际上很相似。 你可以在ActivityFragment像这样使用它:

 public class ExampleActivty extends AppCompatActivity implements ExampleTask.Listener { private ImageView mImageView; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... new ExampleTask(this).execute(); } @Override public void onResult(Bitmap image) { mImageView.setImageBitmap(image); } } 

或者你可以像这样使用它:

 public class ExampleFragment extends Fragment { private ImageView mImageView; private final ExampleTask.Listener mListener = new ExampleTask.Listener() { @Override public void onResult(Bitmap image) { mImageView.setImageBitmap(image); } }; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); new ExampleTask(mListener).execute(); } ... } 

WeakReference及其使用侦听器的结果

不过还有一件事你必须注意。 仅对监听器具有WeakReference的结果。 想象一下,你可以像这样实现监听器接口:

 private static class ExampleListener implements ExampleTask.Listener { private final ImageView mImageView; private ExampleListener(ImageView imageView) { mImageView = imageView; } @Override public void onResult(Bitmap image) { mImageView.setImageBitmap(image); } } public void doSomething() { final ExampleListener listener = new ExampleListener(someImageView); new ExampleTask(listener).execute(); } 

相当不寻常的方法 – 我知道 – 但是类似的东西可能会潜入你的代码而不知道它,结果可能很难debugging。 你现在注意到上面的例子可能是错的吗? 尝试搞清楚,否则继续阅读下面。

问题很简单:您创build一个包含对ImageView的引用的ExampleListener的实例。 然后将它传递给ExampleTask并启动任务。 然后doSomething()方法完成,所以所有的局部variables都有资格进行垃圾回收。 对于传递给ExampleTaskExampleListener实例没有强引用,只有一个WeakReference 。 所以ExampleListener将被垃圾回收,并且当ExampleTask结束时什么也不会发生。 如果ExampleTask执行得足够快,那么垃圾收集器可能还没有收集到ExampleListener实例,所以它可能会工作一段时间,或根本不工作。 而像这样的debugging问题可能是一场噩梦。 所以这个故事的寓意是:始终注意你的强和弱的引用,以及对象何时有资格进行垃圾回收。


嵌套类和使用static

另一个可能是大多数内存泄漏的原因,我看到Stack Overflow人以错误的方式使用嵌套类。 看下面的例子,并尝试在下面的例子中找出导致内存泄漏的原因:

 public class ExampleActivty extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... final ImageView imageView = (ImageView) findViewById(R.id.image); new ExampleTask(imageView).execute(); } public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { private final WeakReference<ImageView> mListenerReference; public ExampleTask(ImageView imageView) { mListenerReference = new WeakReference<>(imageView); } @Override protected Bitmap doInBackground(Void... params) { ... } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); final ImageView imageView = mListenerReference.get(); if (imageView != null) { imageView.setImageAlpha(bitmap); } } } } 

你看到了吗? 下面是另外一个与问题完全相同的例子,它看起来不一样:

 public class ExampleActivty extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... final ImageView imageView = (ImageView) findViewById(R.id.image); final Thread thread = new Thread() { @Override public void run() { ... final Bitmap image = doStuff(); imageView.post(new Runnable() { @Override public void run() { imageView.setImageBitmap(image); } }); } }; thread.start(); } } 

你有没有想清楚是什么问题? 我每天都会看到人们不小心执行上面这样的东西,可能不知道自己做错了什么。 这个问题是Java根据Java的一个基本特性工作的结果 – 没有任何借口,实现像上面这样的东西的人不是喝醉了,就是不了解Java。 让我们简化这个问题:

想象一下你有这样的嵌套类:

 public class A { private String mSomeText; public class B { public void doIt() { System.out.println(mSomeText); } } } 

当你这样做时,你可以从B类内部访问AA成员。 这就是doIt()可以打印mSomeText ,它可以访问A所有成员甚至是私有的成员。
你可以这样做的原因是,如果你像Java那样嵌套类,就隐式地在B创build一个对A的引用。 正是因为这个参考,而且没有其他的东西可以访问B所有的A成员。 然而,在内存泄漏的情况下,如果你不知道自己在做什么,这又会造成问题。 考虑第一个例子(我将去掉所有不重要的部分):

 public class ExampleActivty extends AppCompatActivity { public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { ... } } 

所以我们有一个AsyncTask作为一个Activity的嵌套类。 由于嵌套类不是静态的,我们可以访问ExampleTask中的ExampleActivityExampleTask 。 这里没有关系, ExampleTask实际上并没有访问Activity任何成员,因为它是一个非静态的嵌套类Java隐式地创build了一个对ExampleTaskActivityExampleTask ,所以看起来没有可见的原因,我们有一个内存泄漏。 我们如何解决这个问题? 其实很简单。 我们只需要添加一个词,这是静态的:

 public class ExampleActivty extends AppCompatActivity { public static class ExampleTask extends AsyncTask<Void, Void, Bitmap> { ... } } 

只是这一个简单的嵌套类缺less关键字是内存泄漏和完全罚款代码之间的差异。 真的在这里理解这个问题,因为它是Java工作的核心,理解这是至关重要的。

至于与Thread的另一个例子? 完全相同的问题,像这样的匿名类也只是非静态嵌套类,并立即内存泄漏。 但实际上却是一百万倍。 从各个angular度来看, Thread示例只是可怕的代码。 避免不惜一切代价。


所以我希望这些例子帮助你理解这个问题,以及如何编写无内存泄漏的代码。 如果您有任何其他问题随时问。