如果用户已经离开活动,那么AsyncTask仍如何使用活动?


问题内容

在Android上,您可以Thread使用Runnable或单独进行工作AsyncTask。在这两种情况下,你可能需要通过重载做一些工作,工作完成后,例如onPostExecute()AsyncTask。但是,在后台完成工作时,用户可能会导航或关闭应用程序。

我的问题是:如果在我仍然有对Activity刚刚关闭的用户的引用的情况下,用户导航或关闭了应用程序,将会发生什么AsyncTask

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


问题答案:

简单答案:您刚刚发现

内存泄漏

只要应用程序的某些部分(如)AsyncTask仍保留Activity对它的引用,它就 不会被破坏
。它会一直存在直到AsyncTask完成或以其他方式释放其引用。这可能会带来非常严重的后果,例如您的应用程序崩溃,但最糟糕的后果是您没有注意到的后果:您的应用程序可能会一直引用Activities早就发布的内容,并且每次用户执行任何操作都会泄漏Activity内存中的内存。设备可能会越来越满,直到看似无处可逃Android会因为消耗太多内存而杀死您的应用程序。内存泄漏是我在Stack
Overflow上的Android问题中看到的最常见和最严重的错误


解决方案

避免内存泄漏但是很简单:你AsyncTask应该 永远不会 有一个参考的ActivityService或任何其他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);
        }
    }
}

这完美地说明了a的WeakReference功能。WeakReferences允许Object它们引用被垃圾回收。因此,在此示例中,我们在的构造函数中创建了一个WeakReferenceto
。然后在10秒钟后当不再存在时调用它,我们调用来查看是否存在。只要return by
不为null,那么就不会进行垃圾回收,因此我们可以放心使用它!同时,如果用户退出该应用程序,则该应用程序将立即具有进行垃圾回收的资格,并且如果在一段时间后完成处理,则该应用程序将消失。没有内存泄漏,没有问题。ImageView``AsyncTask``onPostExecute()``ImageView``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您传递给的实例没有强大的参考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从类内部访问该类的成员B。这样便doIt()可以打印mSomeText,甚至可以访问A私有成员的所有成员。
可以这样做的原因是,如果您嵌套这样的类,则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示例都是糟糕的代码。不惜一切代价避免。


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