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

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

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

解决方法

简单回答:你刚刚发现了

内存泄漏

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

解决方案

但是,避免内存泄漏非常简单:您的AsyncTask永远不应该引用Activity,Service或任何其他UI组件.

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

几个例子

引用AsyncTask中的视图

正确实现的使用ImageView的AsyncTask可能如下所示:

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()中可能会在10秒后调用,当ImageView不再存在时,我们在WeakReference上调用get()来查看ImageView是否存在.只要get()返回的ImageView不为null,那么ImageView就不会被垃圾收集,因此我们可以毫无顾虑地使用它!在此期间,用户应该退出应用程序,然后ImageView立即有资格进行垃圾收集,如果AsyncTask稍后完成,则会看到ImageView已经消失.没有内存泄漏,没有问题.

使用听众

public class ExampleTask extends AsyncTask<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()方法完成,因此所有局部变量都有资格进行垃圾回收.传递给ExampleTask的ExampleListener实例没有强引用,只有一个WeakReference.因此,ExampleListener将被垃圾收集,当ExampleTask完成时,不会发生任何事情.如果ExampleTask执行得足够快,垃圾收集器可能还没有收集ExampleListener实例,所以它可能在某些时候工作或根本不工作.像这样的调试问题可能是一场噩梦.因此,故事的寓意是:始终注意您的强弱参考以及何时对象有资格进行垃圾回收.

嵌套类和使用静态

另一件可能是大多数内存泄漏的原因我在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,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);
        }
    }
}

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

public class ExampleActivty extends AppCompatActivity {

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

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

public class ExampleActivty extends AppCompatActivity {

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

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

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

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

如果用户已经远离它,AsyncTask如何仍然使用Activity?的更多相关文章

  1. ios – 如何在UIActivityViewController调用然后关闭后执行操作

    解决方法在这种情况下,您可以使用completionHadler:

  2. android – 如何在服务中正确调用startIntentSenderForResult?

    还是有其他正确的方法吗?)>然后,在onActivityResult()中,完成活动,首先通知您的服务解决问题.瞬态活动对用户来说是不可见的.我承认这个解决方案是理论上的,但它应该有效.

  3. android – 具有动态适配器更改的MultiAutoCompleteTextView

    我的Activity中有一个MultiAutoCompleteTextView小部件,它有一个ArrayAdapter由基于Web的呼叫的结果填充.当用户在textview中键入字符时,此适配器的列表应在后台更新.实现这个的最佳方法是什么?我已经尝试使用AsyncTask在后台下载字符串列表,但是从“非原始线程”调用了notifyDataSetChanged().而且,这似乎有点迂

  4. android – Slider Menu片段中的可交换选项卡

    我已经通过引用thistutorial实现了导航抽屉,现在我想要做的是在片段内显示滑动标签.即当我点击导航抽屉中的一个项目时,让我们说第一个项目,它应显示该项目的滑动标签.如果item1是Events,当我点击它时,它应该显示滑动标签.但我面临以下问题:–>如何在片段内实现视图寻呼机?

  5. android – 如何在屏幕方向上停止活动娱乐?

    我如何在屏幕方向上停止重新启动或调用create(),我想停止在屏幕方向上重新创建活动.在此先感谢请告诉我任何更好的解决方案,它真正创造了一个问题.就像在我的程序中我选择一些图片但在屏幕方向上图像消失,这就是为什么我想停止在屏幕方向上重新开始活动.解决方法在API13之前,configChanges属性screenSize有一个新值因此,如果您使用大屏幕,请确保在configChanges属性中添加screenSize:

  6. Android架构组件ViewModel – 如何在测试Activity上模拟ViewModel?

    我正在尝试设置类似于GithubbrowserSample的UI测试,看起来示例项目只有Frag的模拟viewmodel而不是Activity的示例.这是我的代码,我试图通过模拟viewmodel来测试Activity.但viewmodel未在Activity中的onCreate()之前设置.有人可以帮我解决这个问题吗?解决方法JUnit@Rules在@Before方法之前进行设置,因此您的活动在

  7. 在appuard混淆之后,Android应用程序崩溃了

    我在我的应用程序上运行了proguard工具来进行混淆.我知道,当混淆发生时,proguard会缩小并优化应用程序.因此,在混淆应用程序正确打开后,然后当我尝试登录时崩溃.我得到的最好的是这个日志,因为我连接了设备.我原以为这是因为proguard删除了一些它认为不必要的类或方法,因此导致了崩溃.我做的是这次我再次运行proguard包括所以我希望这个工具能够缩小和优化,但不要混淆,这样我就可以得

  8. android – 如何在对话框上添加进度条

    每当我想在我的应用程序中显示进度条时,我调用此方法,此方法将ProgressBar添加到我的布局中.问题:我想在Dialog上显示此进度条,但Dialog始终显示在上面.这种情况可以做些什么?解决方法尝试这样的东西,它应该全局工作:

  9. android – 用于更新小部件的AsyncTask – 如何在onPostExecute()中访问textviews?

    以下情况:我有一个app小部件,它从一个url轮询数据并用解析的html更新小部件.在预蜂窝装置上,这可以通过服务完成,而无需使用单独的螺纹.现在,在ICS上,这已经改变,并且ASyncThread是必要的.要访问我使用的Widget-Updater-Service中的TextViews但这似乎不适用于ASyncThread.可能是,当线程试图更改textview时,主服务已经终止了吗?解决这个问题的任何想法?

  10. android – 无法获得连接工厂客户端 – 与谷歌地图战斗

    另一天另一个问题,我终于设法在我的Android应用程序上设置正确的谷歌地图,或者至少我以为我已经完成了它,整个程序开始,它甚至调用应该“打印”地图的类,但是我唯一能看到的是一个带有谷歌标签的网格[在角落里].我检查了dalvik监视器和错误E/MapActivity(394):Couldn’tgetconnectionfactoryclient发生.我已经在stackoverflow网站上找到了

随机推荐

  1. bluetooth-lowenergy – Altbeacon库无法在Android 5.0上运行

    昨天我在Nexus4上获得了Android5.0的更新,并且altbeacon库停止了检测信标.似乎在监视和测距时,didEnterRegion和didRangeBeaconsInRegion都没有被调用.即使RadiusNetworks的Locate应用程序现在表现不同,一旦检测到信标的值,它们就不再得到更新,并且通常看起来好像信标超出了范围.我注意到的一点是,现在在logcat中出现以下行“B

  2. android – react-native动态更改响应者

    我正在使用react-native进行Android开发.我有一个视图,如果用户长按,我想显示一个可以拖动的动画视图.我可以使用PanResponder实现这一点,它工作正常.但我想要做的是当用户长按时,用户应该能够继续相同的触摸/按下并拖动新显示的Animated.View.如果您熟悉Google云端硬盘应用,则它具有类似的功能.当用户长按列表中的任何项目时,它会显示可拖动的项目.用户可以直接拖

  3. android – 是否有可能通过使用与最初使用的证书不同的证书对其进行签名来发布更新的应用程序

    是否可以通过使用与最初使用的证书不同的证书进行签名来发布Android应用程序的更新?我知道当我们尝试将这样的构建上传到市场时,它通常会给出错误消息.但有没有任何出路,比如将其标记为主要版本,指定市场中的某个地方?解决方法不,你不能这样做.证书是一种工具,可确保您是首次上传应用程序的人.所以总是备份密钥库!

  4. 如何检测Android中是否存在麦克风?

    ..所以我想在让用户访问语音输入功能之前检测麦克风是否存在.如何检测设备上是否有麦克风.谢谢.解决方法AndroidAPI参考:hasSystemFeature

  5. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

  6. android – 获得一首歌的流派

    我如何阅读与歌曲相关的流派?我可以读这首歌,但是如何抓住这首歌的流派,它存放在哪里?解决方法检查此代码:

  7. android – 使用textShadow折叠工具栏

    我有一个折叠工具栏的问题,在展开状态我想在文本下面有一个模糊的阴影,我使用这段代码:用:我可以更改textColor,它可以工作,但阴影不起作用.我为阴影尝试了很多不同的值.是否可以为折叠文本投射阴影?

  8. android – 重用arm共享库

    我已经建立了armarm共享库.我有兴趣重用一个函数.我想调用该函数并获得返回值.有可能做这样的事吗?我没有任何头文件.我试过这个Android.mk,我把libtest.so放在/jni和/libs/armeabi,/lib/armeabi中.此时我的cpp文件编译,但现在是什么?我从objdump知道它的名字编辑:我试图用这个android.mk从hello-jni示例中添加prebuild库:它工作,但libtest.so相同的代码显示以下错误(启动时)libtest.so存在于libhello-j

  9. android – 为NumberPicker捕获键盘’Done’

    我有一个AlertDialog只有一些文本,一个NumberPicker,一个OK和一个取消.(我知道,这个对话框还没有做它应该保留暂停和恢复状态的事情.)我想在软键盘或其他IME上执行“完成”操作来关闭对话框,就像按下了“OK”一样,因为只有一个小部件可以编辑.看起来处理IME“Done”的最佳方法通常是在TextView上使用setonEditorActionListener.但我没有任何Te

  10. android – 想要在调用WebChromeClient#onCreateWindow时知道目标URL

    当我点击一个带有target=“_blank”属性的超链接时,会调用WebChromeClient#onCreateWindow,但我找不到新的窗口将打开的新方法?主页url是我唯一能知道的东西?我想根据目标网址更改应用行为.任何帮助表示赞赏,谢谢!

返回
顶部