[译]Android内存泄漏的八种可能(上)

Android防止内存泄漏的八种方法(下)

Static Activities

在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。
如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。

static Activity activity;

    void setStaticActivity() {
activity = this;
} View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticActivity();
nextActivity();
}
});

Android提供了特殊的Set集合https://developer.android.com/reference/java/lang/ref/package-summary.html#classes
允许开发者控制引用的“强度”。Activity对象泄漏是由于需要被销毁时,仍然被强引用着,只要强引用存在就无法被回收。

可以用弱引用代替强引用。
https://developer.android.com/reference/java/lang/ref/WeakReference.html.

弱引用不会阻止对象的内存释放,所以即使有弱引用的存在,该对象也可以被回收。

private static WeakReference<MainActivity> activityReference;

    void setStaticActivity() {
activityReference = new WeakReference<MainActivity>(this);
}

Static Views

类似的情况会发生在单例模式中,如果Activity经常被用到,那么在内存中保存一个实例是很实用的。正如之前所述,强制延长Activity的生命周期是相当危险而且不必要的,无论如何都不能这样做。

特殊情况:如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。(译者注:示例代码中并没有释放内存,把这个static view置null即可,但是还是不建议用这个static view的方法)

static view;

    void setStaticView() {
view = findViewById(R.id.sv_button);
} View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticView();
nextActivity();
}
});

由于View持有其宿主Activity的引用,导致的问题与Activity一样严重。弱引用是个有效的解决方法,然而还有另一种方法是在生命周期结束时清除引用,Activity#onDestory()方法就很适合把引用置空。

private static View view;

@Override
public void onDestroy() {
super.onDestroy();
if (view != null) {
unsetStaticView();
}
} void unsetStaticView() {
view = null;
}

Inner Classes

继续,假设Activity中有个内部类,这样做可以提高可读性和封装性。将如我们创建一个内部类,而且持有一个静态变量的引用,恭喜,内存泄漏就离你不远了(译者注:销毁的时候置空,嗯)。

private static Object inner;

       void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
} View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createInnerClass();
nextActivity();
}
});

内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。

与上述两种情况相似,开发者必须注意少用非静态内部类,因为非静态内部类持有外部类的隐式引用,容易导致意料之外的泄漏。然而内部类可以访问外部类的私有变量,只要我们注意引用的生命周期,就可以避免意外的发生。

private Object inner;

void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}
 

Anonymous Classes

相似地,匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk
。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了(译者注:用户退出,系统回收),这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。

void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}.execute();
} super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
startAsyncTask();
nextActivity();
}
});

Handler

同样道理,定义匿名的Runnable,用匿名类Handler执行Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。

void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
} View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createHandler();
nextActivity();
}
});

Threads

我们再次通过ThreadTimerTask来展现内存泄漏。

void spawnThread() {
new Thread() {
@Override public void run() {
while(true);
}
}.start();
} View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
spawnThread();
nextActivity();
}
});

全部都是因为匿名类导致的。匿名类是特殊的内部类——写法更为简洁。当需要一次性特殊的子类时,Java提供的语法糖能让表达式最少化。这种很赞很偷懒的写法容易导致泄漏。正如使用内部类一样,只要不跨越生命周期,内部类是完全没问题的。但是,这些类是用于产生后台线程的,这些Java线程是全局的,而且持有创建者的引用(即匿名类的引用),而匿名类又持有外部类的引用。线程是可能长时间运行的,所以一直持有Activity的引用导致当销毁时无法回收。
这次我们不能通过移除静态成员变量解决,因为线程是于应用生命周期相关的。为了避免泄漏,我们必须舍弃简洁偷懒的写法,把子类声明为静态内部类。

 
但是,如果你坚持使用匿名类,只要在生命周期结束时中断线程就可以。
private Thread thread;

@Override
public void onDestroy() {
super.onDestroy();
if (thread != null) {
thread.interrupt();
}
} void spawnThread() {
thread = new Thread() {
@Override public void run() {
while (!isInterrupted()) {
}
}
}
thread.start();
}

Android - 内存泄漏的情况以及解决方法的更多相关文章

  1. Android 内存泄漏分析与解决方法

    在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...

  2. Android内存泄漏总结

    内存泄漏问题老生常谈,很常见也很难根治,今天我在这里总结一下内存泄漏的原因和解决方法: 所谓内存泄漏,就是本该被回收的对象,由于某些原因不能被回收,继续占用堆内存的这种状态,导致的结果也是显而易见的, ...

  3. 【腾讯优测干货分享】Android内存泄漏的简单检查与分析方法

    本文来自于Dev Club 开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d14047603a5bf1242ad01b 导语 内存泄漏问题大约是An ...

  4. Android内存优化10 内存泄漏常见情况1 静态泄漏

    1,内存泄漏到本质是该释放的对象被持久化的对象引用了,造成持久化的常见情况有1,静态持久化 2,线程持久化 线程持久化 因为存活的线程是有dvk虚拟久直接持有,所以存活的线程都是持久化的 内存泄漏1: ...

  5. Java虚拟机系列(三)---内存溢出情况及解决方法

    因为Java虚拟机内存有堆内存.方法区.虚拟机栈.本地方法栈和程序计数器五部分组成,其中程序计数器是唯一一块不会发生内存溢出异常的内存区,所以只有四类内存区可能发生内存溢出异常,其中虚拟机栈和本地方法 ...

  6. 【转】android 内存泄漏相关收藏博客。

    关于android内存泄漏的研究   博客建了几个月,都没有去写,一是因为当时换工作,然后又是新入职(你懂的,好好表现),比较忙:二是也因为自己没有写博客的习惯了.现在还算是比较稳定了,加上这个迭代基 ...

  7. 关于android内存泄漏的研究

    博客建了几个月,都没有去写,一是因为当时换工作,然后又是新入职(你懂的,好好表现),比较忙:二是也因为自己没有写博客的习惯了.现在还算是比较稳定了,加上这个迭代基本也快结束了,有点时间来写写博客.好了 ...

  8. Android 内存泄漏优化汇总

    android内存泄漏优化摘要 博客分类: android android内存溢出OutOfMemoryError . android移动应用程序的内存分配一般是8凯瑟琳约,不正确地假定处理内存处理非 ...

  9. android 内存泄漏分析技巧

    java虚拟机执行一般都有一个内存界限,超过这个界限,就会报outofmemory.这个时候一般都是存在内存泄漏.解决内存泄漏问题,窃以为分为两个步骤:分析应用程序是否真的有内存泄漏,找到内存泄漏的地 ...

随机推荐

  1. 恢复 MSSQL bak 文件扩展名数据(下)

    恢复 MSSQL bak 文件扩展名数据 一.概念: RESTORE Statements (Transact-SQL) Restores backups taken using the BACKUP ...

  2. python 强大的工具

    numpy Python科学计算的基础包 安装工具 pip3 install numpy pandas包含了高级的数据结构和操作工具,它们使得Python数据分析更加快速和容易. 安装工具 pip3 ...

  3. CentOS7 安装 Docker CE步骤

    准备工作 系统要求 Docker CE 支持 64 位版本 CentOS 7,并且要求内核版本不低于 3.10. CentOS 7 满足最低内核的要求,但由于内核版本比较低,部分功能(如 overla ...

  4. 亿级 Elasticsearch 性能优化

    前言 最近一年使用 Elasticsearch 完成亿级别日志搜索平台「ELK」,亿级别的分布式跟踪系统.在设计这些系统的过程中,底层都是采用 Elasticsearch 来做数据的存储,并且数据量都 ...

  5. weex 自定义Modul

    扩展iOS的功能 一. 新建 NSOjbect 子类, 并遵循协议<WXModuleProtocol> .h 代码 #import <Foundation/Foundation.h& ...

  6. css属性详解和浮动

    一.CSS属性组成和作用 属性:属性值 1)每个css样式都必须由两部分组成:选择符和声明 注:声明又包括属性和属性值 2)css属性:属性是指定选择符具有的属性,他是css的核心,css2共有150 ...

  7. python全栈开发_day4_if,while和for

    一.if 1)if的用途 if常用于判断. 2)if的语法 tag=True tag2=True if tag: print("代码") elif tag2: print(&quo ...

  8. Codeforces - tag::dp 大合集 [占坑 6 / inf]

    Gym - 100753J 某国家仅有金币和银币两种货币,起汇率为g,纪念品市场有n个商人和商品,商人结帐只用银币,并且把一堆银币装在袋子里,分为三种类型,分别按向下/向上/四舍五入取整(其中向上的优 ...

  9. CDH6安装文档

    1.准备工作 1.1 环境 centos7.jdk8.mysql5.7.python2.7.CDH6 1.2文件下载 1.2.1 cloudmanger地址 https://archive.cloud ...

  10. idea使用时,部分jdk的jar包(tool.jar com.sun.javadoc) 无法引入-gradle处理方案

    gradle 增加配置 def jdkHome = System.getenv("JAVA_HOME") dependencies { compile files("$j ...