前言
不管我们是做什么开发的 我相信性能这块都是非常关键的 尤其是做app更是重中之重 可以说直接决定用户的留存,间接的决定app的生死 并且性能优化也是检验一个程序员水平的重要指标 比如面试的时候就会问你之前做项目的时候有没有做过性能优化?或者说有没有遇到过内存泄漏 是怎么处理的? 回答这个问题基本上就可以看出来你是不是一个老司机。这也是初级开发者进阶中高级必须掌握的技能之一!下面就让我们一起系统的学习下这块相关的知识~
内存溢出和内存泄漏
首先我们应该知道的是 一个app进程分配的内存 一般来讲是16M、32M、64M 甚至早期的时候只有8M 也就是说给你玩的空间就这么大 想象一下 8M?20多张大图就崩了吧。所以我们不得不做一些措施,其中很重要的一个是什么呢?就是内存分配和释放。写过C的朋友应该很熟悉,每次我们都需要手动去管理这些内存,这要求我们很细心,一不留神可能内存就泄漏了。而Android他采用的是java语言开发的,java有别于C的一个重要东西就是他有一个智能系统(基于一套复杂的算法)由他来帮助我们管理内存,他就是GC,俗称垃圾回收机制!开发人员不用考虑内存的管理,简化了开发,降低了出错的可能性,而指针也被引用代替。一切OOP,万物皆对象。用代码将梦想照进现实..扯远了..
- 内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。比方说只有8M内存空间,你一下就加载了10M的东西,就溢出了.
- 内存泄漏( memory leak):是指程序在申请内存后,由于疏忽或错误造成程序未能释放已经不再使用的内存。这个什么意思呢?比方说你声明了Context,当Activity销毁后 这个Context被应该被释放掉的,结果没有,它还是占据内存空间的,而你又不能操作它,这就造成内存泄漏了,注意这会app还是可以正常运行的,但是当这样的情况多了,有大量的内存泄漏出现,最终就会导致out of memory,程序崩溃。这就是有时候不注意,一个很小的问题就可能最终引发一个系统的瘫痪,而当着个系统很庞大的时候,你要在找到出问题的地方是很困难的,正所谓千里之堤毁于蚁穴,这就更要求我们开发人员平时写代码的时候要额外注意内存泄漏这块,防患于未然。
说了这么多,下面举个错误的代码示例:
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context){
this.context = context;
}
public static CommUtil getInstance(Context context){
if(instance == null){
instance = new CommUtil(context);
}
return instance;
}
}
分析:上面是一个简单的单例类,它类部持有一个Context的引用 这样的写法容易造成内存泄漏
Activity中使用这个类并且把Context传进来,当Activity被销毁时 由于这个类对象是静态的 所以他持有的Context无法被释放,内存里面就会多一个失去控制的Context 造成内存泄漏,而当其他的Activity使用这个CommUtil 类时 里面的Context对象一直都是第一个Activity传进来的那个,造成数据混乱。
正确的做法是什么呢?应该是使用Application的context 它的生命周期是伴随整个app进程的 当他被销毁 整个app进程结束 CommUtil类也会被销毁 自然就不会产生Context泄漏
内存分配的几种策略
这里讲的是一个比较笼统的概念,关于java内存分配深入研究内容还是很多很细的,需要我们搜索或者看书更全面的学习
- 静态存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在;它主要存放静态数据、全局的static数据和一些常量
- 栈:一块连续的内存区域,在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。
- 堆:不连续的内存区,动态内存分配。可以用malloc或者new来申请分配一个内存。
- java里面栈和堆分别存放的什么?
常见的错误说法:基本数据类型存在栈上,引用类型都存在堆上;引用都存在栈(不严谨)
正确解释:
成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)
局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中; - 栈和堆的区别是什么?
栈:一块连续的内存区域,内置在处理器的里面的,容量有限,运算速度很快。先进后出,进出完全不会产生碎片,运行效率高且稳定。
堆:不连续的内存区域,堆空间比较灵活也特别大。管理很麻烦,频繁地new/remove会造成大量的内存碎片,慢慢导致效率低下。
java的四种引用类型
- 强引用(StrongReference):
回收时机:从不回收; 使用:对象的一般保存 生命周期:JVM停止的时候才会终止 - 软引用(SoftReference):
回收时机:当内存不足的时候;使用:结合ReferenceQueue构造有效期短;生命周期:内存不足时终止 - 弱引用(WeakReference):
回收时机:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止 - 虚引用(PhatomReference):
回收时机:在垃圾回收的时候;使用:结合ReferenceQueue来跟踪对象垃圾回收期回收的活动; 生命周期:GC后终止
ListView是怎么做性能优化的?
- convertView缓存重用
分析:
listview里面有大量数据的item时,为每一个Item创建一个View对象,必将占用很多内存空间,所以必须要做内存处理;
当一个item滑出去的时候它应该被回收,但是当用户快速滑动的时候,如果回收就会造成频繁的I/O操作(回收了又要重新从xml中生成view 这就是I/O操作)非常影响性能;
针对这样的问题,Android提供了一个叫做Recycler(反复循环)的构件,滑出去的Item会被缓存到Recycler中,当滑完一屏再往下滑的时候,getView中的convertView参数就会复用滑出屏幕缓存的Item的View。从而大大改善性能。 - 图片的缓存
分析:
上面提到的convertView缓存重用仅仅是对View的缓存重用,因而我们还要对View里面的数据做缓存,这里重点提到的是图片;
我们知道图片是非常耗内存的,很容易造成内存溢出;我们可以用软引用,但是已经不推荐了,我们常使用的第三方图片加载框架 里面用到的算法就是比较著名的Lrucache(LRU)算法—最近最少使用先回收 它是有LinkedHashMap实现的 感兴趣的同学可以研究下它的源码 ViewHolder重用
分析:
我们都知道在getView()方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们已经采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个item的子View,这也是一个耗时的操作。
当第一次创建convertView对象时,便把这些item的子View保存到ViewHolder对象中。然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的子View,不需要再findViewById找item的子View了。从而大大的提高了性能。
值得一提的是:这里我们可以用SparseArray(稀疏数组) 保存ViewHodler里面的元素,可以写成通用的方法,方便快捷的获取ViewHodler里面的元素,但其实不如直接写ViewHodler的性能好。优点是加快开发速度和减少代码维护。public class ViewHolder { public static <T extends View> T get(View view, int id) { SparseArray<View> viewHolder = (SparseArray<View>) view.getTag(); if (viewHolder == null) { viewHolder = new SparseArray<View>(); view.setTag(viewHolder); } View childView = viewHolder.get(id); if (childView == null) { childView = view.findViewById(id); viewHolder.put(id, childView); } return (T) childView; } }
补充:SparseArray是android内部特有的api,标准的jdk是没有这个类的,是android里 <.Interger,Object> 这样的Hashmap而专门写的类,目的是提高效率,其核心是折半查找函数(binarySearch)
- 数据分页加载等..
- 补充->ListView重复调用getview多次问题以及布局优化(推荐)
http://blog.csdn.net/f8376904110/article/details/6460934