Android内存优化-内存泄漏的几个场景以及解决方式
转自:http://blog.csdn.net/a910626/article/details/50849760
一.什么是内存泄漏
在Java程序中,如果一个对象没有利用价值了,正常情况下gc是会对其进行回收的,但是此时仍然有其他引用指向这个活在堆内存中的对象,那么gc就不会认为这个对象是一个垃圾,那么就不会对其进行回收,所以它会一直活在堆内存中占用内存,这就导致了内存泄漏。
总结一下,导致内存泄漏的原因就是有一些我们永远不会使用的对象,仍然有引用指向它(当然这是在强引用的情况下),那么就不满足gc回收的条件,从而一直活在堆内存中导致内存泄漏,这样的对象多了占用大量内存就会导致App发生oom。
举几个例子:比如使用EventBus,肯定是要执行register(),那么在Fragment或Activity finish的时候,一定不要忘记执行unregister()方法。
二.内存泄漏的常见场景以及解决方式
1.Activity中的Handler长期持有activity引用导致activity泄漏
public class MainActivity extends AppCompatActivity {
private final Handler myHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//doSomething
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myHandler.postDelayed(new Runnable() {
@Override
public void run() {
//doSomething
}
},60*10*1000);
}
}
由于myHandler延时10分钟就会发送一条消息,当activity finish之后,延时发送的消息会在主线程的消息队列中存活10分钟直到被looper拿到然后给到handler处理。此消息(new Runnable)隐式持有其外部类handler的引用,myHandler又隐式的持有其外部类Activity的引用,直到消息被处理完之后,这个引用都不会被释放。因此Activity即使finish,但仍然不会被gc回收。
引用的顺序MessageQueue->Message->Runnable->Handler->Activity,从这个引用链得到Activity与MessageQueue关联,所以Activity对象不能被gc回收,从而导致内存泄漏。
解决方式:
为了解决Handler隐式的持有外部类引用,我们应当将Handler定义在一个新文件或在Activity中使用静态内部类。因为静态内部类不会持有外部类的引用,这样当Activity finish时,Handler不会持有Activity的引用就不会导致Activity内存泄漏。如果需要在Handler内部调用外部Activity的方法,正确的做法是让Handler持有一个Activity的弱引用(WeakReference),这样当gc扫描的时候,这个弱引用的对象就会被回收。
解决了Handler隐式持有外部类Activity引用,Runnable在之前的代码中作为匿名内部类隐式持有Handler引用,所以我们在Activity内部定义一个静态变量引用Runnable类,这是因为匿名类的静态实例不会隐式持有他们外部类的引用。
public class MainActivity extends AppCompatActivity {
private final MyHandler mHandler = new MyHandler(this);
private static Runnable sRunnable = new Runnable() {
@Override
public void run() {
//doSomething
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 60 * 10);
this.finish();
}
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
this.mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
//doSomething
}
}
}
}
或者
我们也可以这样做:
在Activity的onDestroy方法中干掉handler中所有的callback和message:
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
2.非静态匿名内部类造成内存泄漏
在Android中最常见的操作就是当有耗时操作的时候我们不能在主线程执行这些操作,否则有可能造成ANR,主线程主要是UI操作的主战场。
比如网络请求或者数据库查询这些耗时操作我们需要自己另外开启线程,在子线程中执行这些耗时操作。当我们需要开启的子线程比较少的时候,直接new Thread(Runnable)就可以了。如果你经常这样做的话就说明你没有注意到有可能会产生内存泄漏的问题。
如果Activity结束了,而Thread还在跑,同样会导致Activity内存泄漏,这是因为new Thread作为非静态内部类对象都会隐式持有一个外部类对象的引用,我们所创建的线程就是Activity中的一个内部类,持有Activity对象的引用,所以当Activity 结束了,而子线程还在跑就会导致Activity内存泄漏。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testThread();
}
private void testThread() {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
SystemClock.sleep(1000);
}
}
}).start();
}
}
new Thread()是匿名内部类,且非静态。所以会隐式持有外部类的一个引用,只要非静态匿名类对象没有被回收,Activity就不会被回收。
解决方式:
同样把Thread定义为静态的内部类,这样就不会持有外部类的引用。
3.单例+依赖注入
LeakActivity.java
public class LeakActivity extends AppCompatActivity {
private TestManager testManager = TestManager.getInstance();
private MyListener listener=new MyListener() {
@Override
public void doSomeThing() {}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testManager.registerListener(listener);
}
}
TestManager.java
public class TestManager {
private static final TestManager INSTANCE = new TestManager();
private MyListener listener;
public static TestManager getInstance() {
return INSTANCE;
}
public void registerListener(MyListener listener) {
this.listener = listener;
}
public void unregisterListener() {
listener = null;
}
}
interface MyListener {
void doSomeThing();
}
在LeakActivity中TestManager.getInstance()创建对象实例,TestManager中采用单例模式返回一个Testmanager实例变量。
引用链:TestManager->listener->Activity
TestManager中的实例变量是static静态变量,静态变量和类的生命周期是一样的。类加载的时候,静态变量就被加载,类销毁时,静态变量也会随之销毁。
因为INSTANCE是一个单例,所以和app的生命周期是一样的。当app进程销毁时,堆内存中的INSTANCE对象才会被释放,INSTANCE的生命周期非常的长。
而又可以看到代码中Activity里面创建了listener非静态内部类,所以listener就持有外部类Activity的引用。随着testManager.registerListener(listener)执行,TestManager中的listener就持有Activity中listener对象,由此形成了一个引用链。关键在于INSTANCE是一个静态变量,往往Activity finish的时候,INSTANCE还活着,而INSTANCE依然持有Activity的引用,所以造成了Activity内存泄漏。
所以,解决方式:要在Activity的onDestroy()方法中注销注册的listener
@Override
protected void onDestroy() {
testManager.unregisterListener();
super.onDestroy();
}
将TestManager中listener与Activity中的listener关联断开。
三.总结
出现内存泄露的主要原因是生命周期的不一致造成的:在Android中,长时间运行的任务和Acyivity生命周期进行协调会有点困难,如果你不加以小心的话会导致内存泄漏。
内存泄漏的主要原因在于一个生命周期长的东西间接引用了一个生命周期短的东西,会造成生命周期短的东西无法被回收。反过来,如果是一个生命周期短的东西引用了一个生命周期长的东西,是不会影响生命周期短的东西被回收的。
对象都是有生命周期的,对象的生命周期有的是进程级别的,有的是Activity所在的生命周期,随Activity消亡;有的是Service所在的生命周期,随Service消亡。很多情况下判断对象是否合理存在的一个很重要的理由就是它实际的生命周期是否符合它本来的生命周期。很多Memory Leak的发生,很大程度上都是生命周期的错配,本来在随Activity销毁的对象变成了进程级别的对象,Memory Leak就无法避免了。
四.避免内存泄漏的一些技巧
- 使用静态内部类/匿名类,不要使用非静态内部类/匿名类.非静态内部类/匿名类会隐式的持有外部类的引用,外部类就有可能发生泄漏。而静态内部类/匿名类不会隐式的持有外部类引用,外部类会以正常的方式回收,如果你想在静态内部类/匿名类中使用外部类的属性或方法时,可以显示的持有一个弱引用。
- 不要以为Java永远会帮你清理回收正在运行的threads.在上面的代码中,我们很容易误以为当Activity结束销毁时会帮我们把正在运行的thread也结束回收掉,但事情永远不是这样的!Java threads会一直存在,只有当线程运行完成或被杀死掉,线程才会被回收。所以我们应该养成为thread设置退出逻辑条件的习惯。
- 适当的考虑下是否应该使用线程.Android应用框架设计了许多的类来简化执行后台任务,我们可以使用与Activity生命周期相关联的Loaders来执行简短的后台查询任务。如果一个线程不依赖与Activity,我们还可以使用Service来执行后台任务,然后用BroadcastReceiver来向Activity报告结果。另外需要注意的是本文讨论的thread同样使用于AsyncTasks,AsyncTask同样也是由线程来实现,只不过使用了Java5.0新增并发包中的功能,但同时需要注意的是根据官方文档所说,AsyncTask适用于执行一些简短的后台任务。
- 频繁的使用static关键字修饰
很多初学者非常喜欢用static类static变量,声明赋值调用都简单方便。由于static声明变量的生命周期其实是和APP的生命周期一样的(进程级别)。大量的使用的话,就会占据内存空间不释放,积少成多也会造成内存的不断开销,直至挂掉。static的合理使用一般用来修饰基本数据类型或者轻量级对象,尽量避免修复集合或者大对象,常用作修饰全局配置项、工具类方法、内部类。 - BitMap隐患
Bitmap的不当处理极可能造成OOM,绝大多数情况应用程序OOM都是因这个原因出现的。Bitamp位图是Android中当之无愧的胖子,所以在操作的时候必须小心。
及时释放recycle。由于Dalivk并不会主动的去回收,需要开发者在Bitmap不被使用的时候recycle掉。
设置一定的压缩率。需求允许的话,应该去对BItmap进行一定的缩放,通过BitmapFactory.Options的inSampleSize属性进行控制。如果仅仅只想获得Bitmap的属性,其实并不需要根据BItmap的像素去分配内存,只需在解析读取Bmp的时候使用BitmapFactory.Options的inJustDecodeBounds属性。
最后建议大家在加载网络图片的时候,使用软引用或者弱引用并进行本地缓存,推荐使用android-universal-imageloader或者xUtils。 - 引用地狱
Activity中生成的对象原则上是应该在Activity生命周期结束之后就释放的。Activity对象本身也是,所以应该尽量避免有appliction进程级别的对象来引用Activity级别的对象,如果有的话也应该在Activity结束的时候解引用。如不应用applicationContext在Activity中获取资源。Service也一样。
有的时候我们也会为了程序的效率性能把本来是Activity级里才用的资源提升到进程级别,比如ImageCache,或者其它DataManager等。
我只能说,空间和时间是相对的,有的时候需要牺牲时间换取空间,有的时候需要牺牲空间换取时间。内存是空间的存在,性能是时间的存在。完美的程序是在一定条件下的完美。 - BroadCastReceiver、Service 解绑
绑定广播和服务,一定要记得在不需要的时候给解绑。 - handler 清理
在Activity的onDestroy方法中调用
handler.removeCallbacksAndMessages(null);
取消所有的消息的处理,包括待处理的消息; - Cursor及时关闭
在查询SQLite数据库时,会返回一个Cursor,当查询完毕后,及时关闭,这样就可以把查询的结果集及时给回收掉。 - I/O流
I/O流操作完毕,读写结束,记得关闭。 - 线程
线程不再需要继续执行的时候要记得及时关闭,开启线程数量不易过多,一般和自己机器内核数一样最好,推荐开启线程的时候,使用线程池。线程生命周期要跟activity同步。 - 网络请求也是线程操作的,也应该与activity生命周期同步,在onDestroy的时候cancle掉请求。
Android内存优化-内存泄漏的几个场景以及解决方式的更多相关文章
- Android性能优化-内存泄漏的8个Case
1为什么要做性能优化? 手机性能越来越好,不用纠结这些细微的性能? Android每一个应用都是运行的独立的Dalivk虚拟机,根据不同的手机分配的可用内存可能只有(32M.64M等),所谓的4GB. ...
- Android 性能优化——内存篇
一.android官方一些内存方面的内存tips 1.避免创建不必要的对象. 如尽量避免字符串的加号拼接,可以使用StringBuilder来拼接. 如果需要TextView设置多个字符串片段,可以使 ...
- Android 性能优化 ---- 内存优化
1.Android内存管理机制 1.1 Java内存分配模型 先上一张JVM将内存划分区域的图 程序计数器:存储当前线程执行目标方法执行到第几行. 栈内存:Java栈中存放的是一个个栈帧,每个栈帧对应 ...
- Android性能优化-内存优化
原文链接 Manage Your App’s Memory 前言 在任何软件开发环境中,RAM都是比较珍贵的资源.在移动操作系统上更是这样,因为它们的物理内存通常受限.尽管在ART和Dalvik虚拟机 ...
- 【Android开发经验】Cannot generate texture from bitmap异常的解决方式
异常现象: 今天在处理用户头像的过程中,由于头像的处理比較复杂,由于,没有使用afinal自带的自己主动载入.而是自己依据头像的下载路径.手动进行下载和使用.可是在手动回收bitmap对象的过程中,会 ...
- android开发游记:SpringView 下拉刷新的高效解决方式,定制你自己风格的拖拽页面
关于下拉刷新/上拉载入很多其它的解决方式网上已经有非常多了,浏览了眼下主流的下拉控件比方PullToRefresh库等.第一:大多数实现库都难以进行动画和样式的自己定义. 第二:不能非常好的兼容多种滚 ...
- Android内存优化1 了解java内存分配 1
开篇废话 今天我们一起来学习JVM的内存分配,主要目的是为我们Android内存优化打下基础. 一直在想以什么样的方式来呈现这个知识点才能让我们易于理解,最终决定使用方法为:图解+源代码分析. 欢迎访 ...
- unity优化-内存(网上整理)
内存优化内存的开销无外乎以下三大部分:1.资源内存占用:2.引擎模块自身内存占用:3.托管堆内存占用.在一个较为复杂的大中型项目中,资源的内存占用往往占据了总体内存的70%以上.因此,资源使用是否恰当 ...
- Android群英传笔记——第十章:Android性能优化
Android群英传笔记--第十章:Android性能优化 随着Android应用增多,功能越来越复杂,布局也越来越丰富了,而这些也成为了阻碍一个应用流畅运行,因此,对复杂的功能进行性能优化是创造高质 ...
随机推荐
- VS 中關於附加到進程中調試 的問題。
在使用Vs 2012 時,項目發佈到Local IIS 中,如果在調試某個頁面中時,都要F5--> Login --> Debug 很繁瑣,下列有一種較快捷的方式,能夠更快的調試代碼. 1 ...
- Oracle索引梳理系列(六)- Oracle索引种类之函数索引
版权声明:本文发布于http://www.cnblogs.com/yumiko/,版权由Yumiko_sunny所有,欢迎转载.转载时,请在文章明显位置注明原文链接.若在未经作者同意的情况下,将本文内 ...
- Ubuntu中root用户和user用户
从user用户切换到root用户 在ununtu系统中缺省用户是user,出于安全考虑,默认时Ubuntu的root用户时没有固定密码的,它的密码是随机产生并且动态改变的. 但是有的时候我们的用户要执 ...
- Java程序性能优化——让你的java程序更快、更稳定
1.Java性能调优概述 1.1.Web服务器,响应时间.吞吐量是两个重要的性能参数. 1.2.程序性能的几个表现: 执行速度:程序的反映是否迅速,响应时间是否足够短 内存分配:分配是否合理,是否过多 ...
- 报表开发工具Finereport移动端app js接口列表【全】
应用报表工具Finereport的开发人员会发现其移动端app 同样也推出了很多js接口,那这些接口到底有多少,其移动端又有哪些地方支持调用js,这些接口具体又该如何调用呢.根据我平时的开发经验,给大 ...
- MVC架构学习之EasyFirst——快点夸我爱学习~
iMooc上的MVC教程练习. MVC是PHP基础和进阶的分界点吧应该说是 一.准备 工欲善其事~ 个人环境:windows10+wamp2.5+ZendStudio12: 项目名称:MVCEasyF ...
- MMORPG大型游戏设计与开发(服务器 游戏场景 搜索机)
双十一注定是忙碌的日子,所以到了现在我才将今天自己学习的内容拿出来跟大家分享.搜索机是我自己暂时取的名字,其实简单的说就是场景里提供搜索的一个工具,负责场景对象的范围搜索和获取.空洞的理论总是让人一头 ...
- NOIP2003pj栈[卡特兰数]
题目背景 栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表. 栈有两种最重要的操作,即pop(从栈顶弹出一个元素)和push(将一个元素进栈). 栈的重要性不言自明,任何 ...
- NOIP2001统计单词个数[序列DP]
题目描述 给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个).要求将此字母串分成k份(1<k<=40),且每份中包含的单 ...
- Unity 下载存档
各种版本的UNITY下载 https://unity3d.com/cn/get-unity/download/archive