一、绘制优化
Android应用启动慢,经常卡顿,按照场景分可分为两大类:
页面绘制:
主要原因是绘制的层级深,页面复杂,刷新不合理。大多数场景是页面初始化或者页面跳转。
数据处理:
主要原因是数据处理量太大,一般分为三种情况:
数据处理在UI线程;数据处理占用CPU高;内存增加导致GC频繁。
Android的显示过程可以简单概括为:应用层把经过测量、布局、绘制后的surface缓存数据,通过SurfaceFlinger把数据显示到屏幕上。应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层。
应用层:
Measure:使用深度优先原则递归得到所有视图的宽高;
Layout:使用深度优先原则递归得到所有视图的位置;
Draw:支持两种绘制:软件绘制(CPU)和硬件加速(GPU)
系统层:
Android显示系统使用了匿名共享内存:
SharedClient,每个应用和SurfaceFlinger之间都会创建一个SharedClient,最多可以创建31个SharedBufferStack集合,每个Surface都对应一个SharedBufferStack,也就是一个Window。
应用层绘制到缓存区,SurfaceFlinger把缓存区数据渲染到屏幕,由于两个进程都使用了Android的匿名共享内存SharedClient。
绘制过程是CPU准备数据,通过Driver层把数据交给CPU渲染,其中CPU主要负责Measure,Layout,Record,Execute的数据计算工作,GPU负责Rasterization渲染。
卡顿的根本原因:
绘制任务太重,绘制一帧内容耗时太长。主线程太忙了,导致VSync信号来时还没有准备好数据,导致丢帧。
主线称要做的工作:
UI生命周期控制,
系统事件处理
消息处理
页面布局
页面绘制
页面刷新
绘制分析工具:GPU呈现模式分析和过度绘制监测
如何避免过度绘制:
布局上优化:include,merge,ViewStub
布局层级越少,加载速度越快。
减少同一层级控件的数量,加载速度就会变快。
一个控件的属性越少,解析越快。
尽量多使用RelativeLayout,LinearLayout和ConstraintLayout。
尽可能少用wrap_content,wrap_content会增加布局measure的计算成本。
onDraw中不要创建新的局部变量,同时onDraw不能做耗时的任务。
移除XML中非必须的背景;
移除Window默认的背景;
按需显示占位背景图片
自定义View优化:
在自定义View中可以通过canvs.clipRect()来帮助系统识别哪些区域可见,才绘制哪些区域。
二、内存优化
对象生命周期
创建阶段
应用阶段
不可见阶段
不可达阶段
收集阶段
终结阶段
对象空间重新分配阶段
创建对象后,在确认不需要使用该对象时,使对象置空。
访问本地变量优于访问类中的变量。
内存泄漏:
webview 造成的泄漏
资源对象未关闭
集合中的对象没清理
Bitmap对象
监听器未关闭
非静态内部类的静态实例
Handler内存泄漏
单例模式导致的内存泄漏
属性动画导致的内存泄漏
监听内存泄漏:
LeakCanary
内存空间优化
对象引用:强引用,软引用,弱引用,虚引用。
减少不必要的内存开销
在java中尽量使用基本数据类型,使用最优的数据类型(HashMap与ArrayMap)
不要大量使用枚举类型。
ANR:
Activity的UI在5秒内没有响应输入事件(按键按下,屏幕触摸)
BroastcastReceiver在10秒内没有执行完毕
Service在特定时间内(20秒内)无法处理完成。
主线程频繁进行IO操作;
硬件操作如进行调用照相机或者录音等操作;
多线程操作的死锁,导致主线程等待超时;
主线程操作调用join()方法、sleep()方法或者wait()方法;
System server中发生WatchDog ANR;
service binder的数量达到上限。
Context
Application:Android应用中的默认单例类,在Activity或者Service中通过getApplication()可以获取到这个单例,通过context.getApplicationContext()可以获取到应用全局唯一的Context实例。
Activity/Service:这两个类都是ContextWrapper的子类,在这两个类中可以通过getBaseContext() 获取他们的Context实例,不同的Activity或者Service实例,他们的Conetxt都是独立的,不会复用。
BroastcastReceiver:和Activity以及Service不同,BroastcastReceiver本身并不是Context的子类,而是在回调函数onReceive中由Android框架传了一个Context实例。
ContentProvider:同样的,ContentProvider也不是Context的实例。通过getContext()获取Context实例。如果ContentProvider和调用者处于相同的应用进程中,getContext()将返回全局唯一的Context实例。
如果不是,那么ContentProvider持有自身所在进程的Context实例。
Application | Activity | Service | ContentProvidProvider | BroastcaseReceiver | |
Show a Dialog | NO | YES | NO | NO | NO |
Start an Activity | NO1 | YES | NO1 | NO1 | NO1 |
Layout Inflation | NO2 | YES | NO2 | NO2 | NO2 |
Start a Service | YES | YES | YES | YES | YES |
Bind to a Service | YES | YES | YES | YES | NO |
Send a Broadcast | YES | YES | YES | YES | YES |
Register BroastcastReceiver | YES | YES | YES | YES | NO3 |
Load Resource Values | YES | YES | YES | YES | YES |
数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task,添加FLAG_ACTIVITY_NEW_TASK。一般情况不推荐。
数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。
三、包体积优化
res资源优化
只使用一套图片,使用高分辨率的图片。图片使用WebP。
代码优化,Lint工具检查无用文件,将无用的文件列在“UnusedResources:Unused resources”
代码混淆
使用proGuard代码混淆器工具
assets资源优化
音频文件使用有损压缩格式。对ttf字体文件压缩,可以采用FontCreator工具只提取你需要的字体。
lib资源优化
动态下载的资源
一些模块的插件化动态添加
so文件的过滤与适配。
四、其他优化
ListView和Bitmap优化
ListView分三部分:
采用ViewHolder;根据列表的滑动状态来控制任务的执行频率;开启硬件加速
Bitmap优化:主要是通过BitmapFactory.Options来根据需要对图片进行采样。inSampleSize。
线程优化,采用线程池;
避免创建过多的对象;
不要过多使用枚举,枚举占用的内存空间要比整型大;
常量请使用static final来修饰;
使用Android特有的数据结构,SparseArray和Pair;
适当使用软引用和弱引用;
采用内存缓存和磁盘缓存;
尽量采用静态内部类。