系列文章:
Android性能优化|SquirrelNote
Android性能优化:布局优化实践|SquirrelNote
Android性能优化:图片的加载和图片缓存技术|SquirrelNote
Android照片墙应用实现|SquirrelNote
简介
Android设备作为一种移动设备,不管是内存还是CPU的性能都受到了一定的限制,过多地使用内存会导致程序内存溢出,即OOM。而过多地使用CPU资源,即做大量的耗时任务,会导致手机变得卡顿甚至出现程序无法响应的情况,即ANR。
Android的性能优化的方法
- 布局优化:
布局优化的思想是尽量减少布局文件的层级,即布局文件的层级少了,Android绘制时的工作量少了,那么程序的性能就提高了。
- 绘制优化
在View的onDraw方法中避免执行大量的操作。
- 内存泄漏优化
一方面在开发中避免写出内存泄漏的代码,另一方面通过使用内存泄漏工具MAT,可以发现一些开发过程中潜在的内存泄漏问题。
- 响应速度优化和ANR日志分析
避免在主线程中做耗时操作,可以将这些耗时操作放在工作线程中去执行。响应速度过慢体现在Activity的启动画面上,如果在主线程中做太多的事情,可能会导致Activity启动时出现黑屏的现象,甚至出现ANR。当发生了ANR(Application Not Responding)以后。系统会创建一个txt文件,(在/data/anr目录创建一个文件traces.txt),通过分析这个文件,就可以定位ANR的原因。
- ListView优化
可以优化加载布局,对convertview进行判空操作,缓存listView里面已经加载好的view;
不然假如有1000条数据,那么我们滑动,就会 产生1000个convertview ,这对内存是很大的浪费,所以 我们一定要复用。优化加载控件,减少findViewById的次数,使用viewholder去管理这些id,通过tag去直接拿到id;
在adapter适配器中的getView方法中尽量少使用逻辑,可以将这些逻辑代码放在别的地方;
如果listView里面数据量比较大的话,可以采用分批和分页加载;
比如:ListView有十万条数据,如果将十万条数据加载到内存,很消耗内存
解决办法:通常做法是我们将这10万条数据分为1000页,每一页100条数据,每一页加载时都覆盖掉上一页中List集合中的内容,然后每一页内再使用分批加载,这样用户的体验就会相对好一些。
(比如说1000条新闻的List集合,我们一次加载20条,等到用户翻页到底部的时候,我们再添加下面的20条到List中,再使用Adapter刷新ListView,这样用户一次只需要等待20条数据的传输时间,不需要一次等待好几分钟把数据都加载完再在ListView上显示。其次这样也可以缓解很多条新闻一次加载进而产生OOM应用崩溃的情况。)另外,对listView中的一些类似图像,进行图片的优化。
如果ListView中需要显示从网络上下载的图片的话,我们不要在ListView滑动的时候加载图片,那样会使ListView变得卡顿,所以我们需要在监听器里面监听ListView的状态,如果没有滑动,就开始加载图片,如果滑动的时候,停止加载图片。
大概从这几个方面进行ListView的优化。
进行内存优化,从Bitmap角度对内存进行优化(还有从代码角度进行行优化)
主要通过BitMapFactory.Options来根据需要对图片进行采样,采样过程主要用到了BitmapFactory.Options的inSampleSize参数。线程优化
线程优化的思想是采用线程池,避免程序中存在大量的thread。
线程池可以重用内部的线程,从而避免了线程的创建和销毁所带来的性能开销。同时线程池还能有效的控制线程池的最大并发数,避免大量的线程互相抢占系统资源从而导致阻塞现象发生。其他一些性能优化的建议
1.避免创建过多对象;
2.不要过多使用枚举,枚举占用的内存空间比整型大一些。
3.常量使用static final 来修饰。
4.使用一些Android特有的数据结构,比如SpareArray和Pair等,他们都具有更好的性能。
5.适当使用软引用和弱引用。
6.采用内存缓存和磁盘缓存
7.尽量采用静态内部类,这样可以避免潜在的内部类而导致的内存泄漏。
下面进行详细分析:
布局优化
布局优化的思想,就是尽量减少布局文件的层级。
如何进行布局优化呢?
删除布局中无用的控件和层级,有选择地使用性能较低的ViewGroup,比如LinearLayout.如果布局中既可以用LinearLayout也可以用RelativeLayout,那就用LinearLayout,这是因为RelativeLayout比较复杂,它的布局过程花费更多的CPU时间.如果布局是嵌套使用时,一般建议采用RelativeLayout,因为ViewGroup的嵌套就相当于增加了布局文件的层级,同样会降低程序的性能。
布局优化的另外一种方法是采用<include>标签、<merge>标签和ViewStud。<include>标签主要用于布局的重用,<merge>标签一般和<include>标签配合使用,它可以降低减少布局的层级,而ViewStudb则提供了按需加载的功能,当需要时才会将ViewStud中的布局加载到内存,这提高了程序的初始化效率。
绘制优化
在View的onDraw方法中避免执行大量的操作。主要体现在以下两方面:
首先,onDraw中不要创建新的布局对象,这是因为onDraw方法可能会被频繁调用,否则就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁的gc,降低了程序的执行效率。
另一方面,onDraw方法中不要做耗时的任务,也不能执行成千上万次循环操作,尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅。
内存泄漏优化
一方面在开发中避免写出内存泄漏的代码,另一方面通过使用内存泄漏工具MAT,可以发现一些开发过程中潜在的内存泄漏问题。
需要了解:什么是内存泄漏,什么是内存溢出。
内存泄漏
指的是在应用中使用资源之后没有及时释放,导致内存中持有了不需要的资源。(状态描述)内存溢出
OOM,即Out Of Memory。运行时需要的内存,超出了它可用的最大内存。(结果)
常见的内存泄露举例:
- 静态变量导致内存泄露
如果我们将activity的context对象赋值给activity的全局的静态变量。那么就会造成activity无法正常销毁,因为静态变量在引用它。
public static Context mContext;
解决办法:1使用Application的Context。 2慎用static关键字
单例模式导致的内存泄漏
例如,单例模式中,传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),因为单例对象持有 该Activity的引用,所以当前Activity退出时它的内存并不会被回收,
解决方法,调用context.getApplicationContext()方法.使用的Application的Context,而单例的生命周期和应用一样长,这样就防止了内存泄漏.属性动画导致的内存泄露
属性动画中有一类无限循环的动画,如果在Activity中播放此类动画而且没有在onDestory中去 停止动画。那么动画会一直播放下去,尽管已经看不到动画效果了。并且这个时候Activity的View会被动画所持有。而View又持有了Activity。会导致Activity无法释放,解决办法是在Activity的onDestory中调用animator.cancel();非静态内部类创建静态实例造成的内存泄漏
在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
正确的做法为:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext 。
- Handler造成的内存泄漏
创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏
创建一个静态Handler内部类,使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
(创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息)
Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏.
线程造成的内存泄漏(使用静态内部类解决)
资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
内存泄漏检测工具:AS自带的工具 Eclipse中的heap
LeakCanary 是一个开源的在debug版本中检测内存泄漏的java库。
这些开发工具,可以看到堆内存,利用这个检测工具,在打开一个界面的时候看创建了多少个对象,在退出这个界面的时候,默认情况下垃圾回收器会把垃圾回收,如果对象的数量没有减少,有可能说这个界面会出现内存泄漏的问题.
BitMap优化
BitMap优化技巧
- 使用适当分辨率和大小的图片
由于Android系统在做资源适配的时候会对不同分辨率文件夹下的图片进行缩放来适配相应的分辨率,如果图片分辨率与资源文件夹分辨率不匹配或者图片分辨率太高,就会导致系统消耗更多的内存资源。同时,在适当的时候,应该显示合适大小的图片,例如在图片列表界面可以使用图片的缩略图thumbnails,而在显示详细图片的时候再显示原图;或者在对图像要求不高的地方,尽量降低图片的精度。 - 及时回收内存
一旦使用完Bitmap后,一定要及时使用bitmap.recycle()方法释放内存资源。自Android3.0之后,由于Bitmap被放置到了堆中,其内存由GC管理,就不需要进行释放了。
-使用图片缓存
通过内存缓存(LruCache)和硬盘缓存(DiskLruCache)可以更好地使用Bitmap。
以上是根据我的一些理解,做的总结分享,旨在抛砖引玉,希望有更多的志同道合的朋友一起讨论学习,共同进步!