五、性能优化
根据用户的四个方面需求,总结如下:
- 追求流畅,防止卡顿
- 追求稳定,防止闪退
- 追求续航,防止耗损
- 追求精简,防止臃肿
常见性能检测工具:网易开源的Emmagee、腾讯开源的GT、科大讯飞的iTest、Google的开源Battery Historian、Android 自带 Lint 工具
内存分析工具
(1)Memory Monitor 工具:
它是Android Studio自带的一个内存监视工具,它可以很好地帮助我们进行内存实时分析。通过点击Android Studio右下角的Memory Monitor标签,打开工具可以看见较浅蓝色代表free的内存,而深色的部分代表使用的内存从内存变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生GC等
(2)Memory Analyzer 工具:
MAT(Memory Analyzer Tool) 是一个快速,功能丰富的 Java Heap 分析工具,通过分析 Java 进程的内存快照 HPROF 分析,从众多的对象中分析,快速计算出在内存中对象占用的大小,查看哪些对象不能被垃圾收集器回收,并可以通过视图直观地查看可能造成这种结果的对象。
(3)LeakCanary工具:
LeakCanary 只在debug版本下检测,正版先上线后自动跳过检测这就方便开发者无需操作每次上线时注释检测代码。这个工具是Square公司在Github开源的。
使用:
- 添加依赖库
依赖项 { // debugImplementation,因为LeakCanary应该只在调试版本中运行。 debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' }
- 自定义Application
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); setupLeakCanary(); } protected void setupLeakCanary() { if (LeakCanary.isInAnalyzerProcess(this)) { return; } LeakCanary.install(this); }
- 安装包-APK瘦身
1.代码混淆。使用IDE 自带的 proGuard 代码混淆器工具 ,它包括压缩、优化、混淆等功能。
2.资源优化。比如使用 Android Lint 删除冗余资源,资源文件最少化等。
3.图片优化。比如利用 PNG优化工具 (tinypng)对图片做压缩处理。如果应用在4.0版本以上,推荐使用 WebP图片格式(低版本兼容性问题)
4.避免重复或无用功能的第三方库。
5.插件化热修复开发。比如功能模块放在服务器上,按需下载,可以减少安装包大小。
- UI优化
为了app流畅 需要在每一帧16ms内处理完所有的cpu和gpu计算 绘制和渲染操作。
- Hierarchy Viewer
性能优化工具知识梳理(4) - Hierarchy Viewer
- 真机调试-调试GPU过度绘制
性能优化工具知识梳理(3) - 调试GPU过度绘制 & GPU呈现模式分析
比如三星手机的开发者选项中有调试GPU过度渲染工具。
颜色:蓝色<浅绿<浅红<深红
分别代表:绘制一次<绘制两次<绘制三次<绘制四次及以上
- Lint检查
UI优化技巧:
一)减少布局层级
使用merge标签。比如,自定义一个控件继承LinearLayout,若该自定义控件需要填充布局,填充的布局第一层是LinearLayout的话,我们就可以使用merge标签。
再比如,activity的布局文件第一层是FrameLayout的话,我们就可以使用merge标签。
1)利用View的特殊属性
a、利用TextView滑动属性来避免嵌套一层ScrollView。
在xml中添加TextView的属性:android:scrollbars ="vertical"
在代码中添加:textView.setMovementMethod(newScrollingMovementMethod())
b、利用TextView的drawableStart属性等设置icon
2)使用ViewStub标签延迟加载布局
a、ViewStub特点
ViewStub是一个不可见的,大小为0的视图,可以在运行过程中延时加载布局资源。ViewStub被设置成可见或者它的inflate()方法被调用之后填充的布局才会加载。
可以这么理解这句话:Android在绘制界面时会略过ViewStub,直到ViewStub被填充,界面再次绘制才会显现填充后的视图。
b、何时使用ViewStub?
b.1、 某个布局的显现需要条件。
比如应用第一次使用某功能、记录数据为空等等不常出现的UI需要提醒用户感知。
b.2、控制整个布局的显示与隐藏。
b.3、不需要第一时间出现在用户视野的布局。
3)使用约束布局ConstraintLayout
4)自定义View减少布局层级,优化onDraw方法
一般来说,有两点需要注意:
避免在其中进行对象的分配、使用Canvas的ClipRect方法避免过度绘制。
二)减少过度绘制
1)去掉不必要的背景
直接在XML根View写上android:background="@color/color_ffffff",这样会导致过度绘制,为什么呢?
我们知道Activity的xml布局最终会添加到一个叫“content”的FlameLayout中,Android系统本身对这个FlameLayout设置背景颜色,如果我们在加入这个FlameLayout容器的XML根View中再设置背景颜色相当于GPU做了两次绘制,同一区域绘制了2次背景色,导致我们说的过度绘制了。
解决办法也很简单,在activity setContentView()之前写上findViewById(android.R.id.content).setBackgroundResource(R.color.color_ffffff);一行代码搞定。
2)不可见区域不要绘制,ClipRect & QuickReject
某些UI在特定操作后出现,比如刷新时背景出现,刷新后背景隐藏。
对于这种情况我们可以监听刷新操作,获取下拉距离,利用canvas的clipRect()方法只绘制规定矩形内的区域。
除了clipRect方法之外,我们还可以使用canvas的quickReject()来判断是否和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。
3)去掉无用的WindowBackgroud,移除Window默认的Background
当我们使用某些主题时,系统有可能在DecorView中给我们加上一个背景,但是有时候它是无用的
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_overdraw);
getWindow().setBackgroundDrawable(null);
}
4)按需显示占位背景图片
三)布局复用
使用 <include> 标签
当我们的布局中有多个相同的布局时,可以使用include标签来进行布局的复用
四)减少布局中的View
a、使用 SpannableStringBuilder替换多个
多种不同大小、颜色或者图文混排需要显示时,我们往往会利用多个TextView来进行组合,但是某些效果通过一个TextView就可以实现。
b、使用LinearLayout自带的divider属性实现分割线,而不是在布局中手动添加一个额外的View作为分割线
与分割线相关的属性包括以下几个:
divider:传入分割线的drawable,可以是一个图片,也可以是自己通过xml实现的drawable。
showDividers:分割线显示的位置,beginning/middle/end,分割对应头部、中间、尾部。
dividerPadding:分割线距离两边的间距。
c、使用Space控件进行合理的占位
Space控件位于android.support.v4.widget包中,与一般控件不同,它的draw方法是一个空实现,因此它只占位置,而不去渲染,使用它来进行占位填充比其它控件更加高效
- 内存优化
1)定义
优化处理 应用程序的内存使用、空间占用
2) 作用
避免因不正确使用内存 & 缺乏管理,从而出现 内存泄露(ML)、内存溢出(OOM)、内存空间占用过大 等问题,最终导致应用程序崩溃(Crash)
3)储备知识:Android 内存管理机制
3.1) 简介
3.2 )针对进程的内存策略
a. 内存分配策略
由 ActivityManagerService 集中管理 所有进程的内存分配
b. 内存回收策略
步骤1:Application Framework 决定回收的进程类型
Android中的进程 是托管的;当进程空间紧张时,会 按进程优先级低->>高的顺序 自动回收进程
Android将进程分为5个优先等级,具体如下:
步骤2:Linux 内核真正回收具体进程
- ActivityManagerService 对 所有进程进行评分(评分存放在变量adj中)
- 更新评分到Linux 内核
- 由Linux 内核完成真正的内存回收
3.3) 针对对象、变量的内存策略
- Android的对于对象、变量的内存策略同 Java
- 内存管理 = 对象 / 变量的内存分配 + 内存释放
下面,将详细讲解内存分配 & 内存释放策略
a. 内存分配策略
- 对象 / 变量的内存分配 由程序自动 负责
- 共有3种:静态分配、栈式分配、 & 堆式分配,分别面向静态变量、局部变量 & 对象实例
- 具体介绍如下
注:用1个实例讲解 内存分配
public class Sample {
// 该类的实例对象的成员变量s1、mSample1 & 指向对象存放在堆内存中
int s1 = 0;
Sample mSample1 = new Sample();
// 方法中的局部变量s2、mSample2存放在 栈内存
// 变量mSample2所指向的对象实例存放在 堆内存
public void method() {
int s2 = 0;
Sample mSample2 = new Sample();
}
}
// 变量mSample3的引用存放在栈内存中
// 变量mSample3所指向的对象实例存放在堆内存
// 该实例的成员变量s1、mSample1也存放在堆内存中
Sample mSample3 = new Sample();
b. 内存释放策略
对象 / 变量的内存释放 由Java垃圾回收器(GC) / 帧栈 负责
此处主要讲解对象分配(即堆式分配)的内存释放策略 = Java垃圾回收器(GC)
由于静态分配不需释放、栈式分配仅 通过帧栈自动出、入栈,较简单,故不详细描述
3.4) 常见的内存问题 & 优化方案
- 常见的内存问题如下
- 内存泄露
- 内存抖动
- 图片Bitmap相关
- 代码质量 & 数量
- 日常不正确使用
下面,我将详细分析每项的内存问题 & 给出优化方案