在Android上,您可以使用Thread
或Runnable
在单独的AsyncTask
中进行操作。在这两种情况下,您可能需要在完成工作后完成一些工作,例如通过覆盖onPostExecute()
中的AsyncTask
。但是,用户可能会在后台完成工作时离开或关闭应用程序。
我的问题是:如果用户导航或关闭应用时会发生什么情况,而我仍然引用刚刚在我的Activity
关闭的AsyncTask
用户?
我的猜测是,一旦用户导航就应该将其销毁,但是当我出于某种原因在设备上测试它时,我仍然可以调用Activity
上的方法,即使它已经消失了!这里发生了什么?
答案 0 :(得分:25)
简单回答:你刚刚发现了
只要AsyncTask
这样的应用程序的某些部分仍然包含对Activity
的引用,就不会被销毁。它将一直存在,直到AsyncTask
完成或以其他方式释放其引用。这可能会导致非常糟糕的后果,例如您的应用崩溃,但最糟糕的后果是您没有注意到:您的应用可能会继续引用应该在很久以前发布的Activities
以及每次用户执行时无论什么泄漏Activity
设备上的内存可能会变得越来越满,直到看似无处不在Android杀死你的应用程序消耗太多内存。内存泄漏是我在Stack Overflow上的Android问题中看到的最常见和最严重的错误
避免内存泄漏非常简单:AsyncTask
从不应该引用Activity
,Service
或任何其他UI组件。
而是使用侦听器模式并始终使用WeakReference
。永远不要强烈引用AsyncTask
以外的内容。
View
AsyncTask
使用AsyncTask
的正确实施的ImageView
可能如下所示:
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
被垃圾收集。因此,在此示例中,我们在WeakReference
的构造函数中为ImageView
创建AsyncTask
。然后在onPostExecute()
中可能会在ImageView
不再存在的情况下10秒后调用,我们会在get()
上调用WeakReference
以查看ImageView
是否存在。只要ImageView
返回的get()
不为空,那么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);
}
}
}
这看起来非常相似,因为它实际上非常相似。您可以在Activity
或Fragment
:
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();
}
这是一种不寻常的方式 - 我知道 - 但类似的东西可能会在你不知情的情况下偷偷进入你的代码,后果可能很难调试。您是否注意到上述示例可能出现的问题?试着搞清楚,否则继续阅读下面的内容。
问题很简单:您创建ExampleListener
的实例,其中包含您对ImageView
的引用。然后将其传递到ExampleTask
并启动任务。然后doSomething()
方法完成,因此所有局部变量都有资格进行垃圾回收。您传递到ExampleListener
的{{1}}实例没有强大的引用,只有ExampleTask
。所以WeakReference
将被垃圾收集,当ExampleListener
完成时,什么都不会发生。如果ExampleTask
执行得足够快,垃圾收集器可能还没有收集ExampleTask
实例,所以它可能在某些时候工作或根本不工作。像这样的调试问题可能是一场噩梦。因此,故事的寓意是:始终注意您的强弱参考以及何时对象符合垃圾收集条件。
ExampleListener
另一件可能是大多数内存泄漏的原因我在Stack Overflow上看到错误使用嵌套类的人。请查看以下示例,并尝试在以下示例中找出导致内存泄漏的原因:
static
你看到了吗?这是另一个完全相同问题的例子,它只是看起来不同:
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);
}
}
}
}
你有没有弄清楚问题是什么?我每天都会看到人们在不知道自己做错了什么的情况下不经意地实施上述内容。问题是Java如何基于Java的基本特征工作的结果 - 没有任何借口,实现上述内容的人要么喝醉,要么对Java一无所知。让我们简化一下问题:
想象一下,你有一个这样的嵌套类:
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();
}
}
执行此操作后,您可以从班级public class A {
private String mSomeText;
public class B {
public void doIt() {
System.out.println(mSomeText);
}
}
}
内访问班级A
的成员。这就是B
可以打印doIt()
的方式,它可以访问mSomeText
的所有成员,甚至是私人成员。
您可以这样做的原因是,如果您嵌套类,那么Java隐式地在A
内创建对A
的引用。正是由于该引用而您无权访问B
内A
的所有成员。但是,在内存泄漏的情况下,如果您不知道自己在做什么,这又会造成问题。考虑第一个例子(我将剥离所有与示例无关的部分):
B
所以我们在public class ExampleActivty extends AppCompatActivity {
public class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
...
}
}
中有一个AsyncTask
作为嵌套类。由于嵌套类不是静态的,我们可以访问Activity
内ExampleActivity
的成员。这里ExampleTask
实际上不访问ExampleTask
中的任何成员并不重要,因为它是非静态嵌套类Java隐式创建对{{1}的引用} Activity
内部似乎没有明显的原因我们有内存泄漏。我们该如何解决这个问题?实际上很简单。我们只需要添加一个单词,这是静态的:
Activity
在一个简单的嵌套类中,这个缺少一个关键字就是内存泄漏和完全正常的代码之间的区别。真的试着在这里理解这个问题,因为它是Java工作的核心,理解这一点至关重要。
至于ExampleTask
的另一个例子?完全相同的问题,像这样的匿名类也只是非静态嵌套类,并立即发生内存泄漏。然而它实际上差了百万倍。从各个角度来看,public class ExampleActivty extends AppCompatActivity {
public static class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
...
}
}
示例只是可怕的代码。不惜一切代价避免。
所以我希望这些例子可以帮助您理解问题以及如何编写没有内存泄漏的代码。如果您有任何其他问题,请随时提出。