如果用户已经离开它,AsyncTask 如何仍然使用 Activity? [英] How can an AsyncTask still use an Activity if the user has already navigated away from it?

查看:38
本文介绍了如果用户已经离开它,AsyncTask 如何仍然使用 Activity?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Android 上,您可以在单独的 Thread 中工作,例如使用 RunnableAsyncTask.在这两种情况下,您可能需要在工作完成后做一些工作,例如通过覆盖 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 应该永远引用ActivityService> 或任何其他 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()WeakReference 查看 ImageView 是否存在.只要 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);
        }
    }
}

这看起来非常相似,因为它实际上非常相似.您可以在 ActivityFragment 中像这样使用它:

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() 方法结束,所以所有局部变量都可以进行垃圾回收.您传递给 ExampleTaskExampleListener 实例没有强引用,只有一个 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 的引用.正是由于该引用,而没有其他任何东西,您可以访问 BA 的所有成员.但是,在内存泄漏的情况下,如果您不知道自己在做什么,这又会带来问题.考虑第一个示例(我将删除示例中所有无关紧要的部分):

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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆