针对Android的性能优化,主要有以下几个有效的优化方法:
① 布局优化
② 绘制优化
③ 内存泄漏优化
④ 响应速度优化
⑤ ListView/RecycleView及Bitmap优化
⑥ 线程优化
⑦ 其他性能优化的建议
接下来详细介绍这几个方面:
1. 布局优化:
①删除布局中无用的控件和层次,其次有选择地使用性能比较低的ViewGroup
例如:如果布局中既可以使用LinearLayout也可以使用RelativeLayout,那么就采用LinearLayout,这是因为RelativeLayout的功能比较复杂,它的布局过程需要花费更多的CPU时间。FrameLayout和LinearLayout一样都是一种简单高效的ViewGroup,因此可以考虑使用它们,但是很多时候单纯通过一个LinearLayout或者FrameLayout无法实现产品效果,需要通过嵌套的方式来完成。这种情况下还是建议采用RelativeLayout,因为ViewGroup的嵌套就相当于增加了布局的层级,同样会降低程序的性能。
②采用标签,标签,ViewStub
这里主要适用于布局的重用,也可以减少布局的层级嵌套,主要有三种:
Include(例:布局重用等),ViewStub(例:逻辑不同加载不通界面等),Merge。
③避免过度绘制
过度绘制(Overdraw)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的 UI 结构里面,如果不可见的 UI 也在做绘制的操作,会导致某些像素区域被绘制了多次,同时也会浪费大量的 CPU 以及 GPU 资源。
2. 绘制优化:
绘制优化是指View的onDraw方法要避免执行大量的操作,这主要体现在两个方面:
①onDraw中不要创建新的局部对象。
因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁gc,降低了程序的执行效率。
②onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作,尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅。
按照Google官方给出的性能优化典范中的标准,View的绘制频率保证60fps是最佳的,这就要求每帧绘制时间不超过16ms(16ms = 1000/60),虽然程序很难保证16ms这个时间,但是尽量降低onDraw方法中的复杂度总是切实有效的。
3.内存泄露优化:
①为什么会产生内存泄漏?
当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
②内存泄漏对程序的影响?
内存泄漏是造成应用程序OOM的主要原因之一!我们知道Android系统为每个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。
③Android中常见的内存泄漏汇总
单例造成的内存泄漏
非静态内部类创建静态实例造成的内存泄漏
Handler造成的内存泄漏
线程造成的内存泄漏
资源未关闭造成的内存泄漏
④建议方法:
1> 对于生命周期比Activity长的对象如果需要应该使用ApplicationContext ;
2> 对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏 ;
3> 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量
4> 对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null
5> 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期
6>可以通过MAT(Memory Analyzer Tool),或者 LeakCanary来检测Android中的内存泄漏
4.响应速度优化:
响应速度优化的核心思想就是避免在主线程中做耗时操作。如果有耗时操作,可以开启子线程执行,即采用异步的方式来执行耗时操作。如果在主线程中做太多事情,会导致Activity启动时出现黑屏现象,甚至ANR。
注:Android规定,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件就会出现ANR,而BroadcastReceiver如果10秒钟之内还未执行完操作也会出现ANR。
ANR日志分析:当一个进程发生了ANR之后,系统会在/data/anr目录下创建一个文件traces.txt,通过分析这个文件就能定位出ANR的原因。
5.ListView/RecycleView及Bitmap优化:
①使用ViewHolder模式来提高效率
②异步加载:耗时的操作放在异步线程中
③ListView/RecycleView的滑动时停止加载和分页加载
④当ListView/RecycleView中有图片时,对图片做缓存,当然这里我们就不用自已去处理缓存了,有第三方已经为我们提供了,如:Glide,ImagerLoader,Picaso等
对Bitmap的优化,主要是对图片进行压缩,避免加载图片多且大导致OOM出现。 Bitmap高效加载的代码实现:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
//加载图片
BitmapFactory.decodeResource(res,resId,options);
//计算缩放比
options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
//重新加载图片
options.inJustDecodeBounds =false;
return BitmapFactory.decodeResource(res,resId,options);
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if(height>reqHeight||width>reqWidth){
int halfHeight = height/2;
int halfWidth = width/2;
//计算缩放比,是2的指数
while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
inSampleSize*=2;
}
}
return inSampleSize;
}
可以通过如下方式高效加载图片
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100);
6.线程优化:
线程优化的思想就是采用线程池,避免程序中存在大量的Thread。线程池可以重用内部的线程,从而避免了线程的创建和销毁锁带来的性能开销,同时线程池还能有效地控制线程池的最大并法术,避免大量的线程因互相抢占系统资源从而导致阻塞现象的发生。因此在实际开发中,尽量采用线程池,而不是每次都要创建一个Thread对象。线程的知识可以查看这篇文章,本人觉得写的非常好:
https://github.com/LRH1993/android_interview/blob/master/java/concurrence/thread-pool.md
7.其他性能优化建议:
①避免过度的创建对象
②不要过度使用枚举,枚举占用的内存空间要比整型大
③常量请使用static final来修饰
④使用一些Android特有的数据结构,比如SparseArray和Pair等
⑤适当采用软引用和弱引用
⑥采用内存缓存和磁盘缓存
⑦尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏。