问题描述
在 Android 上,您可以在单独的 Thread
中工作,例如使用 Runnable
或 AsyncTask
.在这两种情况下,您可能需要在工作完成后做一些工作,例如通过覆盖 AsyncTask
中的 onPostExecute()
.但是,用户可能会在后台完成工作时离开或关闭应用程序.
On Android you can do work in a separate Thread
for example by using a Runnable
or AsyncTask
. In both cases you might need to do some work after the work is done, for example by overriding onPostExecute()
in the AsyncTask
. However the user might navigate away or close the app while work is being done in the background.
我的问题是:如果用户在我的 AsyncTask
中刚刚关闭的 Activity
仍然有引用时,如果用户导航离开或关闭应用程序会发生什么?
My question is: What happens if the user navigates away or closes the app while I still have a reference to the Activity
the user just closed in my AsyncTask
?
我的猜测是它应该在用户导航离开后立即销毁,但是当我出于某种原因在设备上测试它时,我仍然可以调用 Activity
上的方法,即使它是已经走了!这里发生了什么?
My guess is that it should be destroyed as soon as the user navigates away, however when I test it out on a device for some reason I can still call methods on the Activity
even though it is already gone! What is going on here?
推荐答案
简单回答:你刚刚发现
Simple answer: You have just discovered
只要应用程序的某些部分(如 AsyncTask
仍然持有对 Activity
的引用,它就不会被销毁.它会一直存在,直到 AsyncTask
完成或以其他方式释放其引用.这可能会产生非常糟糕的后果,例如您的应用程序崩溃,但最糟糕的后果是您没有注意到:您的应用程序可能会一直引用应该在很久以前发布的Activities
,并且每次用户做任何泄漏 Activity
设备上的内存可能会越来越满,直到似乎无处不在,Android 会因为消耗太多内存而杀死您的应用程序.内存泄漏是我在 Stack Overflow 上的 Android 问题中看到的最常见和最严重的错误
As long as some part of the app like an AsyncTask
still holds a reference to the Activity
it will not be destroyed. It will stick around until the AsyncTask
is done or releases its reference in some other way. This can have very bad consequences like your app crashing, but the worst consequences are the ones you don't notice: your app may keep reference to Activities
which should have been released ages ago and each time the user does whatever leaks the Activity
the memory on the device might get more and more full until seemingly out of nowhere Android kills your app for consuming too much memory. Memory leaks are the single most frequent and worst mistakes I see in Android questions on Stack Overflow
然而,避免内存泄漏非常简单:您的AsyncTask
应该永远引用Activity
、Service
> 或任何其他 UI 组件.
Avoiding memory leaks however is very simple: Your AsyncTask
should never have a reference to an Activity
, Service
or any other UI component.
改为使用侦听器模式并始终使用 WeakReference
.永远不要强引用 AsyncTask
之外的东西.
Instead use the listener pattern and always use a WeakReference
. Never hold strong references to something outside the AsyncTask
.
使用 ImageView
的正确实现的 AsyncTask
可能如下所示:
A correctly implemented AsyncTask
which uses an ImageView
could look like this:
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
的构造函数中为 ImageView
创建了一个 WeakReference
.然后在 onPostExecute()
中,当 ImageView
不再存在时,它可能会在 10 秒后被调用,我们在 上调用
查看 get()
WeakReferenceImageView
是否存在.只要 get()
返回的 ImageView
不为 null,那么 ImageView
就没有被垃圾回收,因此我们可以放心使用它!如果同时用户退出应用程序,则 ImageView
立即有资格进行垃圾收集,如果 AsyncTask
在一段时间后完成,它会看到 ImageView
代码> 已经不见了.没有内存泄漏,没有问题.
This illustrates perfectly what a WeakReference
does. WeakReferences
allow for the Object
they are referencing to be garbage collected. So in this example We create a WeakReference
to an ImageView
in the constructor of the AsyncTask
. Then in onPostExecute()
which might be called 10 seconds later when the ImageView
does not exist anymore we call get()
on the WeakReference
to see if the ImageView
exists. As long as the ImageView
returned by get()
is not null then the ImageView
has not been garbage collected and we can therefore use it without worry! Should in the meantime the user quit the app then the ImageView
becomes eligible for garbage collection immediately and if the AsyncTask
finishes some time later it sees that the ImageView
is already gone. No memory leaks, no problems.
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
中像这样使用它:
This looks quite similar because it actually is quite similar. You can use it like this in an Activity
or 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);
}
}
或者你可以这样使用它:
Or you can use it like this:
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
的结果.想象一下,你实现了这样的监听器接口:
WeakReference
and its consequences when using a listener
However there is another thing you have to be aware of. A consequence of only having a WeakReference
to the listener. Imagine you implement the listener interface like this:
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();
}
这是一种非常不寻常的方法 - 我知道 - 但是类似的东西可能会在您不知情的情况下潜入您的代码中,并且其后果可能难以调试.你现在有没有注意到上面的例子可能有什么问题?试着弄清楚,否则继续阅读下面的内容.
Quite an unusual way to do this - I know - but something similar might sneak into your code somewhere without you knowing it and the consequences can be difficult to debug. Have you noticed by now what might be wrong with the above example? Try figuring out, otherwise continue reading below.
问题很简单:您创建了一个 ExampleListener
的实例,其中包含您对 ImageView
的引用.然后将其传递到 ExampleTask
并启动任务.然后 doSomething()
方法结束,所以所有局部变量都可以进行垃圾回收.您传递给 ExampleTask
的 ExampleListener
实例没有强引用,只有一个 WeakReference
.所以 ExampleListener
将被垃圾收集,当 ExampleTask
完成时什么都不会发生.如果 ExampleTask
执行得足够快,垃圾收集器可能还没有收集 ExampleListener
实例,所以它可能在某些时候工作或根本不工作.调试这样的问题可能是一场噩梦.所以这个故事的寓意是:始终注意您的强引用和弱引用,以及对象何时有资格进行垃圾收集.
The problem is simple: You create an instance of the ExampleListener
which contains your reference to the ImageView
. Then you pass it into the ExampleTask
and start the task. And then the doSomething()
method finishes, so all local variables become eligible for garbage collection. There is no strong reference left to the ExampleListener
instance you passed into the ExampleTask
, there is just a WeakReference
. So the ExampleListener
will be garbage collected and when the ExampleTask
finishes nothing will happen. If the ExampleTask
executes fast enough the garbage collector might not have collected the ExampleListener
instance yet, so it may work some of the time or not at all. And debugging issues like this can be a nightmare. So the moral of the story is: Always be aware of your strong and weak references and when objects become eligible for garbage collection.
另一件事可能是导致大多数内存泄漏的原因,我在 Stack Overflow 上看到人们以错误的方式使用嵌套类.查看以下示例并尝试找出导致以下示例中内存泄漏的原因:
Another thing which probably is the cause of most memory leaks I see on Stack Overflow people using nested classes in the wrong way. Look at the following example and try to spot what causes a memory leak in the following example:
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);
}
}
}
}
你看到了吗?这是另一个具有完全相同问题的示例,只是看起来不同:
Do you see it? Here is another example with the exact same problem, it just looks different:
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 一无所知.让我们简化一下问题:
Have you figured out what the problem is? I daily see people carelessly implementing stuff like above probably without knowing what they are doing wrong. The problem is a consequence of how Java works based on a fundamental feature of Java - there is no excuse, people who implement things like above are either drunk or don't know anything about Java. Let's simplify the problem:
想象一下你有一个这样的嵌套类:
Imagine you have a nested class like this:
public class A {
private String mSomeText;
public class B {
public void doIt() {
System.out.println(mSomeText);
}
}
}
当您这样做时,您可以从 B
类内部访问 A
类的成员.这就是 doIt()
可以打印 mSomeText
的方式,它可以访问 A
的所有成员,甚至是私有成员.
您可以这样做的原因是,如果您嵌套这样的类,Java 会在 B
内隐式地创建对 A
的引用.正是由于该引用,而没有其他任何东西,您可以访问 B
中 A
的所有成员.但是,在内存泄漏的情况下,如果您不知道自己在做什么,这又会带来问题.考虑第一个示例(我将删除示例中所有无关紧要的部分):
When you do that you can access members of the class A
from inside the class B
. That's how doIt()
can print mSomeText
, it has access to all the members of A
even private ones.
The reason you can do that is that if you nest classes like that Java implicitly creates a reference to A
inside of B
. It is because of that reference and nothing else that you have access to all members of A
inside of B
. However in the context of memory leaks that again poses a problem if you don't know what you are doing. Consider the first example (I'll strip all the parts that don't matter from the example):
public class ExampleActivty extends AppCompatActivity {
public class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
...
}
}
所以我们有一个 AsyncTask
作为 Activity
中的嵌套类.由于嵌套类不是静态的,我们可以在 ExampleTask
中访问 ExampleActivity
的成员.ExampleTask
实际上并不访问 Activity
中的任何成员在这里无关紧要,因为它是一个非静态嵌套类 Java 隐式地创建了对 的引用
等看似没有明显原因的内存泄漏.我们如何解决这个问题?其实很简单.我们只需要添加一个词,那就是静态的:ExampleTask
中的 >Activity
So we have an AsyncTask
as a nested class inside an Activity
. Since the nested class is not static we can access members of the ExampleActivity
inside the ExampleTask
. It doesn't matter here that ExampleTask
doesn't actually access any members from the Activity
, since it is a non static nested class Java implicitly creates a reference to the Activity
inside the ExampleTask
and so with seemingly no visible cause we have a memory leak. How can we fix this? Very simple actually. We just need to add one word and that is static:
public class ExampleActivty extends AppCompatActivity {
public static class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
...
}
}
在一个简单的嵌套类中缺少的这个关键字就是内存泄漏和完全好的代码之间的区别.真正尝试理解这里的问题,因为它是 Java 工作原理的核心,理解这一点至关重要.
Just this one missing keyword on a simple nested class is the difference between a memory leak and completely fine code. Really try to understand the issue here, because it is at the core of how Java works and understanding this is crucial.
至于 Thread
的另一个例子?完全相同的问题,像这样的匿名类也只是非静态嵌套类,并且会立即发生内存泄漏.然而,它实际上要糟糕一百万倍.从各个角度看,Thread
示例都是糟糕的代码.不惜一切代价避免.
And as for the other example with the Thread
? The exactly same issue, anonymous classes like that are also just non static nested classes and immediately a memory leak. However it is actually a million times worse. From every angle you look at it that Thread
example is just terrible code. Avoid at all costs.
所以我希望这些例子能帮助你理解问题以及如何编写没有内存泄漏的代码.如果您有任何其他问题,请随时提出.
So I hope these example helped you understand the problem and how to write code free of memory leaks. If you have any other questions feel free to ask.
这篇关于如果用户已经离开它,AsyncTask 如何仍然使用 Activity?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!