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

时间:2016-01-29 14:30:53

标签: java android android-asynctask

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

我的问题是:如果用户导航或关闭应用时会发生什么情况,而我仍然引用刚刚在我的Activity关闭的AsyncTask用户?

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

1 个答案:

答案 0 :(得分:25)

简单回答:你刚刚发现了

内存泄漏

只要AsyncTask这样的应用程序的某些部分仍然包含对Activity的引用,就不会被销毁。它将一直存在,直到AsyncTask完成或以其他方式释放其引用。这可能会导致非常糟糕的后果,例如您的应用崩溃,但最糟糕的后果是您没有注意到:您的应用可能会继续引用应该在很久以前发布的Activities以及每次用户执行时无论什么泄漏Activity设备上的内存可能会变得越来越满,直到看似无处不在Android杀死你的应用程序消耗太多内存。内存泄漏是我在Stack Overflow上的Android问题中看到的最常见和最严重的错误

解决方案

避免内存泄漏非常简单:AsyncTask 从不应该引用ActivityService或任何其他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);
        }
    }
}

这看起来非常相似,因为它实际上非常相似。您可以在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();
}

这是一种不寻常的方式 - 我知道 - 但类似的东西可能会在你不知情的情况下偷偷进入你的代码,后果可能很难调试。您是否注意到上述示例可能出现的问题?试着搞清楚,否则继续阅读下面的内容。

问题很简单:您创建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的引用。正是由于该引用而您无权访问BA的所有成员。但是,在内存泄漏的情况下,如果您不知道自己在做什么,这又会造成问题。考虑第一个例子(我将剥离所有与示例无关的部分):

B

所以我们在public class ExampleActivty extends AppCompatActivity { public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { ... } } 中有一个AsyncTask作为嵌套类。由于嵌套类不是静态的,我们可以访问ActivityExampleActivity的成员。这里ExampleTask实际上不访问ExampleTask中的任何成员并不重要,因为它是非静态嵌套类Java隐式创建对{{1}的引用} Activity内部似乎没有明显的原因我们有内存泄漏。我们该如何解决这个问题?实际上很简单。我们只需要添加一个单词,这是静态的:

Activity

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

至于ExampleTask的另一个例子?完全相同的问题,像这样的匿名类也只是非静态嵌套类,并立即发生内存泄漏。然而它实际上差了百万倍。从各个角度来看,public class ExampleActivty extends AppCompatActivity { public static class ExampleTask extends AsyncTask<Void, Void, Bitmap> { ... } } 示例只是可怕的代码。不惜一切代价避免。

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