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

Fat*_*tum 9 java android android-asynctask

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

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

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

Xav*_*ler 25

简单回答:你刚刚发现了

内存泄漏

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


解决方案

避免内存泄漏但是很简单:你AsyncTask应该永远不会有一个参考的Activity, Service或任何其他UI组件.

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


几个例子

引用View一个AsyncTask

AsyncTask使用an的正确实现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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这完全说明了WeakReference它的作用.WeakReferences允许Object他们引用垃圾收集.所以在这个例子中我们在构造函数中创建了WeakReference一个.然后在10秒后,当不再存在时,我们可以调用它来查看是否存在.只要返回者不为null,那么就没有垃圾收集,因此我们可以毫无顾虑地使用它!在此期间用户应该退出应用程序,然后立即有资格进行垃圾收集,如果完成一段时间后它会看到已经消失.没有内存泄漏,没有问题.ImageViewAsyncTaskonPostExecute()ImageViewget()WeakReferenceImageViewImageViewget()ImageViewImageViewAsyncTaskImageView


使用听众

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这看起来很相似,因为它实际上非常相似.您可以在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);
    }
} 
Run Code Online (Sandbox Code Playgroud)

或者您可以像这样使用它:

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(); 
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

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

问题很简单:您创建一个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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你看到了吗?这是另一个完全相同问题的例子,它只是看起来不同:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

你弄清楚问题是什么吗?我每天都会看到人们在不知道自己做错了什么的情况下不经意地实施上述内容.问题是Java如何基于Java的基本特性工作的结果 - 没有任何借口,实现上述内容的人要么喝醉了,要么对Java一无所知.让我们简化问题:

想象一下,你有一个这样的嵌套类:

public class A {

    private String mSomeText;

    public class B {

        public void doIt() {
            System.out.println(mSomeText);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当您这样做时,您可以A从类内部访问该类的成员B.这就是如何doIt()打印mSomeText,它可以访问A甚至私人的所有成员.
您可以这样做的原因是,如果您嵌套类似Java,则隐式创建对A内部的引用B.正是由于这个参考而你没有其他任何东西可以访问所有A内部成员B.但是,在内存泄漏的情况下,如果您不知道自己在做什么,这又会造成问题.考虑第一个例子(我将剥离与示例无关的所有部分):

public class ExampleActivty extends AppCompatActivity {

    public class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我们在一个AsyncTask内部有一个嵌套类Activity.由于嵌套类不是静态的,我们可以访问ExampleActivity内部的成员ExampleTask.这里ExampleTask没有实际访问任何成员并不重要Activity,因为它是一个非静态嵌套类,Java隐式地创建了对Activity内部的引用ExampleTask,因此看似没有可见的原因我们有内存泄漏.我们该如何解决这个问题?实际上很简单.我们只需要添加一个单词,这是静态的:

public class ExampleActivty extends AppCompatActivity {

    public static class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

至于另一个例子与Thread?完全相同的问题,像这样的匿名类也只是非静态嵌套类,并立即发生内存泄漏.然而它实际上差了百万倍.从各个角度来看,这个Thread例子都是可怕的代码.不惜一切代价避免.


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