一:性能优化简述
当开发者的能力达到一定水平,如果仍然满足于基本功能的实现,而对于App的性能优化,程序运行的卡顿,甚至崩溃没有相应的处理改进,那么可想而知此App的质量问题。为了App的流畅运行,开发者也需要提升App质量,提升用户体验。所以,性能优化在Android开发中的作用不言而喻。
那么对于永固体验的性能问题又有哪些方面呢?
- 稳定:减低Crash和ANR机率,降低程序无响应问题
- 流畅:避免卡顿,响应速度快,减少用户等待时间
- 省电:顾名思义优化对电池的消耗,一定程度上避免手机发烫
- 省流量消耗:(针对数据连接上网)用户需要通过电信运营商购买相应的流量套餐连接网络,所也就涉及到了用户的消费成本
- Apk体积小:小的安装包可以降低用户安装成本,节省手机存储空间
二:常用工具介绍
1.手机App性能检测工具
(1).iTest(科大讯飞)
(2).GT(腾讯--随身调)
关于具体的使用请大家自行百度(手机上的检测工具本人使用的不多,而且在分析自己开发的App时,在有条件的下用电脑会更加方便和全面)
2.性能优化检测工具
在介绍工具之前,向大家推荐一个Android Lint,他是一个代码提示工具,对diamagnetic进行优化提示,包含了java和xml。合理使用lint测试,能使我们的diamagnetic变得规范且便面冗余,也能及时发现代码中隐藏的问题。
下面是使用它的贴图,一看即懂:
好了,接下来就是正式介绍性能检测工具了(只是给出了大的方向,有许多分析工具不可能都做到列出):
(1).Profiler:(此工具为主要使用的分析工具,希望大家深入学习,官方出品,必属精品)毫无疑问,熟悉Android Studio的开发者都知道Android Studio Profiler,这是一个很好用的分析工具,关于此工具的用法,大家请参考掘金的作者:maweiliang写的一篇Android Studio 3.0 Profiler 性能分析利器(这篇文章对于Profiler加入的energy没有给出说明(那时候energy还未加入Profiler)文章地址:https://juejin.im/post/5a351a76f265da430d58165a)。
(2).LeakCanary:相信这个工具非常有名,它只在debug版本下检测,当发行版上线后,自动跳过检测机制
(3).Android Device Monitor:这个工具现在已经被Google官方弃用了(当然还可以使用),网上关于DDMS的教程很多,在此就不做过多的介绍使用了,但是如果仍然需要使用ADM,则可以在Sdk目录下的tools文件夹中找到并使用。
下面附一张Google官方的说明:
(4 ).Battery Historian:顾名思义,这个Google官方推出的检测与电池有关的信息和事件工具。
(5).Systrace:这是Android性能问题很好用的一个工具。
三:性能优化
1.卡顿优化
- UI绘制
- 启动应用
- 页面跳转
- 事件响应
卡顿发生的场景是最直观的,因为其发生在和用户最直接交互的时候,所以对于界面绘制和数据处理是要注意防止出现界面卡顿问题。
(1).布局优化:这个思路很简单,那就是减少布局文件的层级:(推荐多多学习ConstraintLayout)
- 删除布局中无用的控件和层级
- 选择使用性能较低的ViewGroup(eg:LinearLayout和RelativeLayout,明显是LinearLayout的性能消耗更低)
- ViewGroup的嵌套也是增加层级的操作
- 使用<include>, <merge>和ViewStub
- 使用wrap_content,会增加measure计算成本
- 删除控件中无用属性
(2).绘制优化:这个意思是View的onDraw()方法要避免执行大量的操作:
- onDraw()中不要创建新的局部对象:其可能会被频繁调用,所以可能会产生大量临时对象,占用过多内存,导致多次gc,降低效率
- onDraw()中也避免做好事任务,大量循环操作
- 移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片
- 使用 canvas.clipRect() 帮助系统识别可见的区域,只有在这个区域内才会被绘制
2.启动优化
首先介绍一个工具Profile GPU Rendering:他是一个渲染性能检测工具,可以在手机上看到什么导致了应用的卡顿,变慢问题(这是我看过的一篇关于此的文章https://www.jianshu.com/p/061bb80025c7)
然后就用自己刚入门开发的一个超级简单的聊天(使用了图灵的SDK)应用与微信做了对比,发现没有检测的时候一点感觉都没有有,但是请看下图,就可以明白我那会是多么的菜:
很明显的就看出来了,而且自己开发的软件还算体积很小,二微信已经非常成熟了。(在此作比较知道是自不量力,但是可以从中找到深入学习的动力,望共勉)
上面的简要介绍是为了让大家对性能优化的重要性有一个更直观的了解,所以更要注重App的性能问题。
好了,接下来正式开始启动优化的内容:
应用的启动分为冷启动,热启动,暖启动,其中启动最慢的就是冷启动,此时系统和App都需要更多的工作从头开始,所以应该始终基于冷启动进行优化。,这样做同时也改善了热启动和暖启动的性能。
冷启动:
冷启动执行的三个任务:
- 加载并启动App
- App启动后立即展示blank Startup Window
- 创建Application process
当上面三个任务执行完毕后,系统创建了Application process,其负责以下的阶段:
- 创建Application对象
- 启动Main Thread
- 创建MainActivity对象
- 加载布局View
- 布置Screen
- 执行初始化lottery(第一次绘制)
当执行完以上任务后(第一次绘制后),系统进程就会用MainActvity替换已经展示的Window,此时App正式准备好与用户交互的工作(可以开始使用App)。App进程的创建等过程我们无法干预,所以优化的部分在Application,Activity创建,回调等过程。
所以对于冷启动的优化就可以分为三个方向:
避免在启动时做繁重的初始化工作
- 适当利用异步初始化第三方的组件(可以被延迟的组件做此操作,有些依赖,调用关系等不能延迟的,则应该避免延迟初始化),别让其造成对主线程的阻塞
- 延迟第三方组件的初始化时机,可以在使用前再去做初始化工作
- 慎重开启Work Thread
避免IO操作,反序列化,布局嵌套,网络操作等
使用Activity的windowBackground主题属性可以为启动的Actvity提供一个简单的drawable
- 此种方法只是优化了用户体验,而不是真正让启动变得更加快速
热启动:
其相比于热启动更简单,开销也更低,再热启动中,系统只要将Activity至于前台即可。如果此时App的所有活动仍然保存在内存中,则该App可以避免重复对象初始化工作,布局绘制和渲染。同样的,如果内存进行了清楚操作,热启动仍不可避免的重新创建所需对象。
暖启动:
暖启动包括了冷启动发生的部分工作,同时,它开销更多于热启动,有许多可能的状态都可被认为是暖启动:
- 用户退出App,然后重启。该process 可能继续运行,但是App必须通过调用来从头开始重新创建onCreate()
- 系统从内存中清楚此App,然后用户重启它,该process 和activity都需要重新启动,但是任务可以从传递中的已保存示例中onCreate()
3:内存优化
关于AndroidART和Dalvik,GC在此不做介绍。
我们首先来看Android中的内存类型(cpu与gpu都会访问相同的RAM):
- RAM:最快的内存块,但通常大小收到限制
- zRAM:用于叫呼唤空间的RAM分区。放入zRAM是,所有内容均会被压缩,当取出时,再进行解压缩。随着数据再zRAM中的存取,RAM的大小也会增加或缩小
- Storage:包含所有持久性数据,eg:文件系统以及所有应用程序,库等的目标代码。Storage具有三者中最大的容量。
Android中有两种主要机制处理内存不足情况:kernel swap daemon 和 low-memory killer.
kernel swap daemon:kswapd是Linux内核的一部分,将使用过的内存转换为空闲内存。
low-memory killer:当kswapd无法释放足够的内存时,系统使用onTrimMemory()通知应用程序内存不足,并且应该减少分配。如果仍不足,kernel将开始终止进程以释放内存。
- Memory Leaks内存泄漏,相信对于大家是一个耳熟能详的词,它一直陪伴着我们至今,那Android中的内存泄漏又是何种原因造成的呢?这通常由于在静态成员变量中保留不再用到的对象引用导致的。发生内存泄漏会导致Memory Generation中的剩余可用Heap Size越来越小,这样会导致频繁触发GC(通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms),如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了),更进一步引起性能问题。
- Memory Churn内存抖动,内存抖动是因为在短时间内大量的对象被创建又马上被释放。瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,会触发GC从而导致刚产生的对象又很快被回收。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。
内存泄漏:
常见的内存泄露:
- 集合类造成的泄露:集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。
- 单例造成的内存泄漏:由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。
- 非静态内部类创建静态实例造成的内存泄露:因为非静态内部类默认会持有外部类的引用,又使用了该非静态内部类创建了一个静态的实例。
- WebView造成的内存泄露:因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的
destory()
方法来销毁它以释放内存。- Handler造成的内存泄漏:Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏。
- 线程造成的内存泄漏:异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成,那么将导致Activity的内存资源无法回收,造成内存泄漏。
- 资源未关闭造成的内存泄漏:对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
优化策略:
- 构造单例时尽量不要依赖于
Activity
引用- 静态引用时注意应用对象的置空或者少用静态引用
- 使用静态内部类+软引用代替非静态内部类
- 广播,EventBus等有注册和解注册的使用,一定成对使用
- 耗时任务、属性动画在
Activity
销毁时记得cancel
- 不再使用的Stream,File,Bitmap,Curosr等资源要及时关闭
Activity
销毁时WebView
的移除和销毁- Handler最好持有弱引用,再Activity释放时清空Message,取消Handler的Runnable
- 对于生命周期长于Activity的对象,考虑使用ApplicationContext
- 集合有加入就应该相对应有删除
耗电优化:
因为本人还没有接触到此方面的内容,所以推荐大家去官方查看:https://developer.android.google.cn/topic/performance/power
减小APK体积:
APK文件到底是一个什么东西?
我们在操作一个东西之前,总得知道它是什么东西吧?APK就是一个zip压缩包组成的东西,这个zip包包含了所有组成App的文件,所以文件中包含了javaClass文件,资源文件和编译后的资源文件。
APK的目录如下:
- META-INF/: 包含了
CERT.SF
和CERT.RSA
签名文件,以及MANIFEST.MF
manifest 文件.- assets/:包含了应用的
assets
,应用可以通过 AssetManager对象来获取这些资源.- res/:包含了没有被编译成
resources.arsc
的所有资源.- lib/:包含了用于软件处理的编译后的代码
还有如下的一些文件,但是只有Manifest文件时必须的:
- resources.arsc:包含了编译后的资源。这个文件包含了
res/values/
文件夹下面的所有XML文件内容,打包工具抽取了XML文件内容,并把它编译成二进制文件格式,并且进行压缩。- classes.dex:包含了class文件编译成的dex文件,这是可以被Dalvik/ART虚拟机识别的文件格式。
- AndroidManifest.xml:包含了Android核心manifest文件,此文件有应用的名字,版本,访问权限和引用的library库。
减小APK体积:
(前文介绍过的)Lint工具,清除冗余代码,无用资源等(其不会扫描assets文件)
最小化Lib库中资源的使用:ProGuard可以清楚lib中一些无用的代码,但是不能清楚lib中大的内部依赖(此方式为修改lib,删除无用代码等)
清理assets文件夹:Assert文件夹经常会放置一些不被编译的资源,时间久了,里面可能一些文件或者资源已经不用了,然而这个文件夹也是会被打包到apk里面的。所以定期清理这个里面的内容。
代码代替图片:eg:shape,layer-list,属性动画等
压缩图片:可以用pngquant工具
so的优化:我们可以通过配置gradle来制定只打包某些so
动态加载技术:也就是常说的插件化技术(本人没有学到这里,故请大家自行百度)