gpt4 book ai didi

java - 如果用户已经离开它,AsyncTask 如何仍然使用 Activity?

转载 作者:行者123 更新时间:2023-12-02 15:43:10 25 4
gpt4 key购买 nike

在 Android 上,您可以在单独的 Thread 中工作例如通过使用 RunnableAsyncTask .在这两种情况下,您可能需要在工作完成后做一些工作,例如通过覆盖 onPostExecute()AsyncTask .但是,用户可能会在后台完成工作时离开或关闭应用程序。

我的问题是:如果用户导航离开或关闭应用程序而我仍然有对 Activity 的引用会发生什么情况用户刚刚在我的 AsyncTask 中关闭?

我的猜测是它应该在用户离开后立即销毁,但是当我出于某种原因在设备上测试它时,我仍然可以调用 Activity 上的方法。即使它已经消失了!这里发生了什么?

最佳答案

简单回答:你刚刚发现

内存泄漏

只要应用程序的某些部分像 AsyncTask仍然保留对 Activity 的引用它会不被破坏 .它会一直存在,直到 AsyncTask完成或以其他方式释放其引用。这可能会产生非常糟糕的后果,例如您的应用程序崩溃,但最糟糕的后果是您没有注意到:您的应用程序可能会继续引用 Activities这应该在很久以前就发布了,每次用户做任何泄漏的事情时 Activity设备上的内存可能会越来越满,直到似乎无处不在 Android 会因为消耗太多内存而杀死您的应用程序。内存泄漏是我在 Stack 上的 Android 问题中看到的最常见和最严重的错误

解决方案

然而,避免内存泄漏非常简单:您的 AsyncTask应该从不 引用 Activity , Service或任何其他 UI 组件。

而是使用监听器模式并始终使用 WeakReference .永远不要强引用 AsyncTask 之外的东西.

几个例子

引用 ViewAsyncTask
正确实现 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他们指的是垃圾收集。所以在这个例子中我们创建了一个 WeakReferenceImageViewAsyncTask 的构造函数中.然后在 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 的结果给听者。想象一下,你实现了这样的监听器接口(interface):
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 没有强引用您传入 ExampleTask 的实例,只有一个 WeakReference .所以 ExampleListener将被垃圾收集,当 ExampleTask完成什么都不会发生。如果 ExampleTask执行得足够快,垃圾收集器可能没有收集到 ExampleListener实例,所以它可能在某些时候工作或根本不工作。调试这样的问题可能是一场噩梦。所以这个故事的寓意是:始终注意您的强引用和弱引用,以及对象何时有资格进行垃圾回收。

嵌套类和使用 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);
}
}
}

当你这样做时,你可以访问类的成员 A来自类(class)内部 B .就是这样 doIt()可以打印 mSomeText ,它可以访问 A的所有成员甚至私有(private)的。
您可以这样做的原因是,如果您嵌套这样的类,Java 会隐式地创建对 A 的引用。内部 B .正是由于该引用而不是其他任何东西,您才能访问 A 的所有成员。内部 B .但是,在内存泄漏的情况下,如果您不知道自己在做什么,这又会带来问题。考虑第一个示例(我将删除示例中所有无关紧要的部分):
public class ExampleActivty extends AppCompatActivity {

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

所以我们有一个 AsyncTask作为 Activity 中的嵌套类.由于嵌套类不是静态的,我们可以访问 ExampleActivity 的成员。内 ExampleTask .这里无关紧要 ExampleTask实际上并不访问 Activity 中的任何成员,因为它是一个非静态嵌套类,Java 隐式地创建了对 Activity 的引用。内 ExampleTask因此,在看似没有明显原因的情况下,我们出现了内存泄漏。我们怎样才能解决这个问题?其实很简单。我们只需要添加一个词,那就是静态的:
public class ExampleActivty extends AppCompatActivity {

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

一个简单的嵌套类上缺少的关键字就是内存泄漏和完全正确的代码之间的区别。真正尝试理解这里的问题,因为它是 Java 工作方式的核心,理解这一点至关重要。

至于另一个带有 Thread 的例子?完全相同的问题,像这样的匿名类也只是非静态嵌套类,并且会立即发生内存泄漏。然而,它实际上要糟糕一百万倍。从各个角度看, Thread示例只是糟糕的代码。不惜一切代价避免。

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

关于java - 如果用户已经离开它,AsyncTask 如何仍然使用 Activity?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35086967/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com