https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/%E5%9C%A8Activity%E4%B8%AD%E4%BD%BF%E7%94%A8Thread%E5%AF%BC%E8%87%B4%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F

已测试

在Activity中使用Thread导致的内存泄漏

注:这篇博文涉及的源码可以在 GitHub 上面下载哦

做 Android 开发最常遇到的问题就是在 Activity 的生命周期中协调耗时任务,避免执行任务导致不易察觉的内存泄漏。不妨先读一读下面的代码,代码写了一个简单的 Activity,Activity 在启动后就会开启一个线程,并循环执行该线程中的任务

    /**
* 示例向我们展示了在 Activity 的配置改变时(配置改变会导致其下的 Activity 实例被销
* 毁)存活。此外,Activity 的 context 也是内存泄漏的一部分,因为每一个线程都被初始
* 化为匿名内部类,使得每一个线程都持有一个外部 Activity 实例的隐式引用,使得
* Activity 不会被 Java 的垃圾回收机制回收。
*/
public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleOne();
} private void exampleOne() {
new Thread() {
@Override
public void run() {
while (true) {
SystemClock.sleep(1000);
}
}
}.start();
}
}

Activity 配置发生改变会使 Activity 被销毁,并新建一个 Activity,我们总会觉得 Android 系统会将与被销毁的 Activity 相关的一切清理干净,例如回收与 Activity 关联的内存,Activity 执行的线程等等……然而,现实总是很残酷的,刚刚提到的这些东西都不会被回收,并导致内存泄漏,从而显著地影响应用的性能表现。

Activity 内存泄漏的根源

如果你读过我以前写的一篇有关 Handler 和 内部类的博文,那我接下来要讲的知识你肯定知道。在 Java 中,非静态匿名内部类会持有其外部类的隐式引用,如果你没有考虑过这一点,那么存储该引用会导致 Activity 被保留,而不是被垃圾回收机制回收。Activity 对象持有其 View 层以及相关联的所有资源文件的引用,换句话说,如果你的内存泄漏发生在 Activity 中,那么你将损失大量的内存空间。

而这样的问题在 Activity 配置改变时会更加严重,因为 Activity 的配置改变表示 Android 系统将要销毁当前 Activity 并新建一个 Activity。举例来说吧,在使用应用的时候,你执行了10次横屏/竖屏操作,每一次方向的改变都会执行下面的代码,那么我们会发现(使用Eclipse 的内存分析工具可以看到)每一个 Activity 对象都会因为留有一个隐式引用而被保留在内存中。

每一次配置的改变都会使 Android 系统新建一个 Activity 并把改变前的 Activity 交给垃圾回收机制回收。但因为线程持有旧 Activity 的隐式引用,使该 Activity 没有被垃圾回收机制回收。这样的问题会导致每一个新建的 Activity 都将发生内存泄漏,与 Activity 相关的所有资源文件也不会被回收,其中的内存泄漏有多严重可想而知。

看到这里可能你会很害怕,很惶恐,很无助,那我们该怎么办……莫慌,解决办法非常简单,既然我们已经确定了问题的根源,那么对症下药就可以了:我们把该线程类声明为私有的静态内部类就可以解决这个问题:

     /**
* 示例通过将线程类声明为私有的静态内部类避免了 Activity context 的内存泄漏问题,但
* 在配置发生改变后,线程仍然会执行。原因在于,DVM 虚拟机持有所有运行线程的引用,无论
* 这些线程是否被回收,都与 Activity 的生命周期无关。运行中的线程只会继续运行,直到
* Android 系统将整个应用进程杀死
*/
public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleTwo();
} private void exampleTwo() {
new MyThread().start();
} private static class MyThread extends Thread {
@Override
public void run() {
while (true) {
SystemClock.sleep(1000);
}
}
}
}

通过上面的代码,新线程再也不会持有一个外部 Activity 的隐式引用,而且该 Activity 也会在配置改变后被回收。

线程内存泄漏的根源

第二个问题是:对于每个新建 Activity,如果 Activity 中的线程发生发生内存泄漏。在Java中线程是垃圾回收机制的根源,也就是说,在运行系统中DVM虚拟机总会使硬件持有所有运行状态的进程的引用,结果导致处于运行状态的线程将永远不会被回收。因此,你必须为你的后台线程实现销毁逻辑!下面是一种解决办法:

    /**
* 除了我们需要实现销毁逻辑以保证线程不会发生内存泄漏,其他代码和示例2相同。在退出当前
* Activity 前使用 onDestroy() 方法结束你的运行中线程是个不错的选择
*/
public class MainActivity extends Activity {
private MyThread mThread; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleThree();
} private void exampleThree() {
mThread = new MyThread();
mThread.start();
} /**
* 私有的静态内部类不会持有其外部类的引用,使得 Activity 实例不会在配置改变时发生内
* 存泄漏
*/
private static class MyThread extends Thread {
private boolean mRunning = false; @Override
public void run() {
mRunning = true;
while (mRunning) {
SystemClock.sleep(1000);
}
} public void close() {
mRunning = false;
}
} @Override
protected void onDestroy() {
super.onDestroy();
mThread.close();
}
}

通过上面的代码,我们在 onDestroy() 方法中结束了线程,确保不会发生意外的线程的内存泄漏问题。如果你想要在配置改变后保留该线程(而不是每一次在关闭 Activity 后都要新建一个线程),那我建议你使用 Fragment 去完成该耗时任务。你可以翻我以前的博文,一名叫作“Handling Configuration Changes with Fragments”应该能满足你的需求,在API demo中也提供了很好理解的例子来为你阐述相关概念。

结论

Android 开发过程中,在 Activity 的生命周期里协调耗时任务可能会很困难,你一不小心就会导致内存泄漏问题。下面是一些小提示,能帮助你预防内存泄漏问题的发生:

  • 尽可能使用静态内部类而不是非静态内部类。每一个非静态内部类实例都会持有一个外部类的引用,若该引用是 Activity 的引用,那么该 Activity 在被销毁时将无法被回收。如果你的静态内部类需要一个相关 Activity 的引用以确保功能能够正常运行,那么你得确保你在对象中使用的是一个 Activity 的弱引用,否则你的 Activity 将会发生意外的内存泄漏。

  • 不要总想着 Java 的垃圾回收机制会帮你解决所有内存回收问题。就像上面的示例,我们以为垃圾回收机制会帮我们将不需要使用的内存回收,例如:我们需要结束一个 Activity,那么它的实例和相关的线程都该被回收。但现实并不会像我们剧本那样走。Java 线程会一直存活,直到他们都被显式关闭,抑或是其进程被 Android 系统杀死。所以,为你的后台线程实现销毁逻辑是你在使用线程时必须时刻铭记的细节,此外,你在设计销毁逻辑时要根据 Activity 的生命周期去设计,避免出现 Bug。

  • 考虑你是否真的需要使用线程。Android 应用的框架层为我们提供了很多便于开发者执行后台操作的类。例如:我们可以使用 Loader 代替在 Activity 的生命周期中用线程通过注入执行短暂的异步后台查询操作,考虑用 Service 将结构通知给 UI 的 BroadcastReceiver。最后,记住,这篇博文中对线程进行的讨论同样适用于 AsyncTask(因为 AsyncTask 使用 ExecutorService 执行它的任务)。然而,虽说 ExecutorService 只能在短暂操作(文档说最多几秒)中被使用,那么这些方法导致的 Activity 内存泄漏应该永远不会发生。

这篇博文的源码可以在 GitHub 中下载,你也可以在 Google Play 下载 APK 使用。

在Activity中使用Thread导致的内存泄漏的更多相关文章

  1. android Context 持有导致的内存泄漏

    Context使用场景 为了防止Activity,Service等这样的Context泄漏于一些生命周期更长的对象,可以使用生命周期更长的ApplicationContext,但是不是所有的Conte ...

  2. static关键字所导致的内存泄漏问题

    大家都知道内存泄漏和内存溢出是不一样的,内存泄漏所导致的越来越多的内存得不到回收的失手,最终就有可能导致内存溢出,下面说一下使用staitc属性所导致的内存泄漏的问题. 在dalvik虚拟机中,sta ...

  3. 使用block的时候,导致的内存泄漏

    明确,只要在block里边用到我们自己的东西,成员变量,self之类的,我们都需要将其拿出来,把它做成弱指针以便之后进行释放. 在ZPShareViewController这个控制器中,由如下代码: ...

  4. 精华阅读第 13 期 |常见的八种导致 APP 内存泄漏的问题

    本期是移动开发精英俱乐部的第13期文章,都是以技术为主,所以这里就不过多的进行赘述了,我们直接看干货内容吧!本文系ITOM管理平台OneAPM整理. 实际项目中的MVVM(积木)模式–序章 导读:开篇 ...

  5. 深入理解Node.js中的垃圾回收和内存泄漏的捕获

    深入理解Node.js中的垃圾回收和内存泄漏的捕获 文章来自:http://wwsun.github.io/posts/understanding-nodejs-gc.html Jan 5, 2016 ...

  6. 一个驱动导致的内存泄漏问题的分析过程(meminfo->pmap->slabtop->alloc_calls)

    关键词:sqllite.meminfo.slabinfo.alloc_calls.nand.SUnreclaim等等. 下面记录一个由于驱动导致的内存泄漏问题分析过程. 首先介绍问题背景,在一款嵌入式 ...

  7. 使用HandyJSON导致的内存泄漏问题相关解决方法

    在移动开发中,与服务器打交道是不可避免的,从服务器拿到的接口数据最终都会被我们解析成模型,现在比较常见的数据传输格式是json格式,对json格式的解析可以使用原生的解析方式,也可以使用第三方的,我们 ...

  8. vue自定义指令导致的内存泄漏问题解决

    vue的自定义指令是一个比较容易引起内存泄漏的地方,原因就在于指令通常给元素绑定了事件,但是如果忘记了解绑,就会产生内存泄漏的问题. 看下面代码: directives: { scroll: { in ...

  9. Java匿名对象导致的内存泄漏

    这几天与在某群与群友讨论了Runnable匿名对象导致内存泄漏的相关问题,特此记录一下. 示例代码如下: package com.memleak.memleakdemo; public class L ...

随机推荐

  1. tab切换webuploader失效的解决方法

    <script type="text/javascript"> $(document).ready(function () { $('#tt').tabs({ bord ...

  2. ****CI和UEditor集成

    百度UEditor是一款比较常用编辑器 下载地址: http://ueditor.baidu.com/website/download.html 1.在assets目录下建立ueditor文件夹,把下 ...

  3. Luogu 1903 数颜色 | 分块

    Luogu 1903 数颜色 | 分块 莫队不会啊-- 这道题直接分块也能卡过! 这道题的做法很有趣:对于每个位置i,记录它的颜色a[i]上一次出现的位置,记为pre[i]. 这样在查询一个区间[l, ...

  4. kafka 基础知识梳理

    一.kafka 简介 kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据.这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因 ...

  5. 解决 Could not resolve com.android.tools.build:gradle:3.1.3

      android studio 升级到3.1.3后总会遇到莫名其妙的错误,前几天刚解决了项目 dependencies报错的问题. 解决android studio 升级到3.0+之后 项目 dep ...

  6. CSS3实现原腾讯视频透明边框,多重边框等(关于边框那些不为人知的事情)

    1.hsla或rgba实现半透明边框. rgba在rgb的基础上增加了透明通道,就不详细说了,下面重点说下hsla: 说明: HSLA(H,S,L,A) 取值: H:Hue(色调).0(或360)表示 ...

  7. 「WC 2019」数树

    「WC 2019」数树 一道涨姿势的EGF好题,官方题解我并没有完全看懂,尝试用指数型生成函数和组合意义的角度推了一波.考场上只得了 44 分也暴露了我在数数的一些基本套路上的不足,后面的 \(\ex ...

  8. hiho1269 优化延迟 ([Offer收割]编程练习赛1)

    一道中文题,就不用翻译了. 大意是讲,一串数字,可以按照输入的先后顺序扔到一个固定大小的缓冲池子里,这个池子里的数输出顺序随意.然后计算—— SP=1*Pi1+2*Pi2+3*Pi3+...+N*Pi ...

  9. hdu 1754 I Hate It(树状数组区间求最值)2007省赛集训队练习赛(6)_linle专场

    题意: 输入一行数字,查询第i个数到第j个数之间的最大值.可以修改其中的某个数的值. 输入: 包含多组输入数据. 每组输入首行两个整数n,m.表示共有n个数,m次操作. 接下来一行包含n个整数. 接下 ...

  10. curl dns缓存设置

    CURLOPT_DNS_USE_GLOBAL_CACHE 启用时会启用一个全局的DNS缓存,此项为线程安全的,并且默认启用.CURLOPT_DNS_CACHE_TIMEOUT 设置在内存中保存DNS信 ...