Android APP性能调优 一本正经的胡说八道的前言
“一本正经地胡说八道”用日语怎么说?大概是「真面目にふざけている」吧。这篇日志大概就是这么一个意思?
一直以来都想对Android APP开发的性能调优做一下总结,其实性能调优涉及到多方面的工作,每次有一些心得我都会记录下来,零零散散记录了很多,最近发现许多地方重复了,感觉还是得做一下整理的,知识就是这么牢固起来的。
“APP卡顿”是一个问题,我们既需要知道怎么查找出哪里造成卡顿,也需要掌握规避这些卡顿的技巧,所以这个话题可以分为“如何定位APP中的性能问题”和“提高性能需要注意哪些点”这两部分,后续在陆续对这两点展开讨论吧,今天先从整体分析下问题存在的原因。
开始正题之前先让我吐一吐苦水吧。
我个人喜欢日语,所以学了很久的日语了,同样我也是因为喜欢Android,才开始跳进了Android开发这个坑。不过非常遗憾的是,就和“当初我在日语班里,周围大部分人只是因为日语专业比较轻松才选了它”一样,我周围的Android研发同事大部分用的是IOS手机,甚至有人问过我“我说你工资也不至于那么低吧,怎么每天都拿着安卓手机”。产品则是每次都说“你这个交互和IOS的不一样,我需求文档里写得清清楚楚,要保持一致的用户体验”。设计的话,我从来就没有遇见过拿安卓手机的。
我尝试说过Android不比IOS差,但是没人站在我这边。
自从Android系统诞生以来一直都有一个疑问,“为什么Android手机这么卡?”,无论Android设备的硬件再怎么升级,版本再怎么迭代,“Android比IOS卡顿”似乎成为一个板上钉钉的事实。Android手机真的卡么?
至少在Android Kitkat之前,许多Android开发者都会选择回避这个问题;Kitkat之后,有一部分开发者已经有底气回答这个问题了;随着Android Lollipop以及Marshmallow先后的出现,我觉得Android开发者都可以自信地回答说“Android不卡”了。
在一个公司内部分享会中,国内Android领域的大神罗升阳在分享他对ART模式的研究中说到,继Kitkat采用了ART模式后,Lollipop中,除了UI线程之外又提供了一条专门用于执行动画的线程,这样一来UI线程就可以专注于展示数据和响应用户的输入,这让Android的流畅度又上了一个档次。IOS是闭源的所以我们无从得知,但是有人分析说IOS之所以这么流畅很大一个原因就是它大概也采用了类似动画线程的机制。老罗也相信“Android系统不会比IOS卡,甚至已经比它还流畅”。
这可不是随便说说,我的Nexus5升级到Lollipop之后我觉得它已经可以匹敌同事的IPhone6了,前不久升级到Marshmallow内测版,我更是觉得它已经拉开IPhone6一个档次了。
那为什么许多人觉得Android用起来还是比IOS卡?注意老罗说“Android系统”比IOS流畅,而不是“Android应用”,言下之意就是Android系统本身不卡,卡的是设备开发商开发的ROM以及开发者开发的第三方APP。我个人觉得Android卡顿问题大致有以下的原因。
客观原因
Android设备系统升级速度慢
现在Lollipop甚至Kitkat的普及率还不算很高,更别谈Marshmallow了,这是Android碎片化的问题决定的,具体原因有兴趣的可以自己Google,网上一堆比我在这里吹的靠谱多的分析。
当然这里也有很大一部分是设备开发商的锅,Google开源的AOSP项目只能兼容Nexus系列手机的硬件,如果第三方设备开发商需要使用AOSP的话,最起码也要把AOSP里面的驱动部分改成能兼容自己的设备的,此外,他们还喜欢把系统UI风格做成自己家的,这起码也要自己写一个Launcher应用和自定义主题(这也是许多Android ROM被吐槽成换皮肤的原因);此外有一些特色功能,比如指纹设备,Android Marshmallow之前AOSP并没有这个功能,所以开发商就得自己出解决方案了。这一系列的工作,造成了开发商无法在Google发布Android的新版本之后迅速升级自家Android设备的系统。
Android APP需要做过多旧版本的兼容
Android4.0版本相比之前的版本性能上优化了许多,无奈我接触的Android产品都要求最低支持Android2.3,有一款SDK甚至要求支持到Android1.6而且不能使用Support库(今年可是2015年!你能想象一个手动写Thread去控制一个复杂的属性动画的效果有多糟糕吗?),所以Android开发者需要做一大堆向下兼容的工作,有时候为了保证在一些奇葩机型的兼容性,选择了保守的实现方式而不采用Android的新特性。向下兼容的逻辑使得APP即使在高级版本的Android系统上也要跑一堆没用的判断逻辑,如果这些逻辑出现在循环体内则更糟糕;采用保守的实现方式,使得APP无法发挥新版本Android的性能,即使用户手机升级了Android系统版本也享♂受新版本带来的体验。此外,即使许多新的APP产品都选择最低支持Android4.0,这使得许多新特性都不用Support库支持就能直接使用,但是许多手机设备还是无法升级到Android4.4版本的系统,即使有,很多ROM还是把ART模式给严格了,无法体验其脱胎换骨版的顺畅。
大量采用“黑科技”
最近支付宝被Google Play下架了,原因就是其“从Play市场以外的服务器下载可执行代码”,意思就是它使用了动态加载技术。Android的动态加载也不是新鲜的事物了,我的项目中也采用过,简单来说原理就是Android APP采用“APK空壳+可执行代码”的开发方式,APK只是一个空壳,用户安装过一次后就不用重新安装了,如果需要升级APP,APK空壳会从服务器下载新的可执行代码,更换本地的即可完成升级(具体实现方案现在网上一堆教程,也可以参考我Github上的相关项目)。采用这种开发方式,开发者可以迅速完成用户安装好的APP的升级,在某种意义上提高了用户的体验,但是开发者的开发方式也会变得比较“绕”,开发成本增加了不少,为了保证兼容性也会放弃使用Android的一些无法在动态加载框架上使用的功能,所以体验往往比不上“正统”的Android开发方式。
常驻内存、全家桶以及互相唤醒
缺少了Google Play的约束,一些Android APP就变得肆意妄为了,这一点在BAT系中显得格外明显。常驻内存就接受到服务器的推送信息的成功率就比较高了,用户明明关了一些APP,但是它们就不想退出,就算我们手动关闭了它们,一旦重新启动、网络变化等,它们就又重新启动了,占着本来就珍贵的手机内存,很快就不够用了,这也是Android卡顿的一大原因。“百度全家桶”你怕不怕?,一旦安装了百度家族启动的一个APP,就会偷偷帮你安装上全家族的APP,就算是我这种做Android研发的都经常中招,不用说普通的用户了。更可恨的是这些APP还会互相唤醒,Andriod Lollipop之后你可以彻底关闭一个APP,除非你手动启动它否则它无法自启动,但是家族APP之间互相唤醒使得这成为了可能,“绿色守护”等后台清理神器在“互相唤醒”大法之下也没辙了。
H5内容、硬生生把Android应用做成IOS应用等等
Android原生与H5界面交互的框架已经很成熟了,许多中大型的APP的有H5的界面,特别是淘宝、天猫、京东这种购物类的,这些APP有大量和时间相关的活动,在节日之前开发一个新版本的APP再发布到应用市场显得太蠢了,H5网页这种随时可以更新在线内容的技术最适合这种活动了。然而H5界面的性能在还是肛不过原生界面,特别是各种黑科技附身的Lollipop之后的时代,特别是当一个界面有H5的东西也有原生的东西那就更加糟糕了。此外,国内设计师都喜欢把Android的美术图设计得和IOS一样,这里不讨论这样做的原因,然而结果就是Android的开发需要使用大量的自定义View,这样一来,许多系统自带控件的加速效果(特别是当用到系统公共资源的时候)就没有什么卵用了,而且往往这些自定义View都有或多或少的Bug,可能设计得不合理,也可能有内存泄露,这些都会影响APP的性能。
主观原因
上面说的客观原因除了由于Android的碎片化问题之外,其实有很大部分是开发者有意为之,但是我相信这并不是Android开发的责任,我们是都是清白的,都是无奈的,错的不是我们,是世界啊!╮( ̄▽ ̄")╭。也不是产品经理的锅,而是大环境造就的。国内互联网的竞争堪比电商,不这么做,不抢占用户的手机的话根本就无法生存,就算百度不去做,阿里也会去……所以勉强可以把这些归类为客观因素。但是瞎BB了这么多,都不是我想说的主要内容,我想许多人都不感兴趣,我只是一时来兴致了敲了这么多字,感觉如果不贴出来的话不就亏了嘛。
…………
……
…
既然你都看到这了,顺便把主观原因部分也看完吧_(-ω-`_)⌒)_。
主观原因主要是由于开发过程中的过错导致的,总体上来说大致有以下几方面的原因
没做好异步
APP要流畅的话就好让它保持高度相应,CPU要能及时响应UI线程的操作。不过不可能把所有非UI的工作统统扔到异步任务里面去,有时候一些工作直接在UI线程搞会非常方便,而且太多后台线程也会造成大量的性能开销。这是一个矛盾,这时候就需要成熟的异步任务框架咯,如果这个框架没写好的话,就可能导致后台任务混乱,产生多余的线程开销,UI线程得不到及时的响应,更甚,如果有异步任务造成内存泄露,内存不够用很快就卡顿了,甚至直接OOM嗝屁了。
内存泄露以及GC
内存是非常宝贵的,再多也不嫌多。所以当一个对象离开他的作用域后,我们一定立刻回收它占有的内存,最理想的状态是对每一个对象都能做到这样,如果有一个对象做不到,就说明他泄露了。Android中,Activity对象以及Bitmap的像素数据往往占用非常大的内存,如果这两者发生泄漏会导致可用内存急剧减少,那卡顿则就无法避免。此外,即使没有严重的内存泄露,但是频繁创建对象和回收对象的话,会引发虚拟机频繁的GC,GC占用比较大的资源开销,同样也可能会导致卡顿。总的来说,开发过程中要对内存的使用保持敏感,知根知底。
没做好界面的布局优化
设计师给出的同一张美术图可以有多张的布局实现,不同方案之间的性能可能相差很远。ListView等列表控件的Adapter也有许多优化点,许多人容易疏忽。
没做好代码优化
这就不只是Android领域的问题啦,某些特定的业务应该采用最优算法,把时间复杂度降到最低,尽量避免指数级别的时间复杂度,尽量以空间换时间,必要的时候使用Native库来提高算法。
其他
一开始我也提到了,其实性能调优涉及到多方面的工作,比如一些静态的网络资源要做好缓存不要重复请求;频繁数据库操作的话最好使用异步任务,SQLite默认实在UI线程直接操作数据库的;使用反射也会比较性能,不过反射有时候确实挺方便的,特别是项目庞大的时候,这个看取舍;过度使用“设计模式”也会有额外的开销,设计意味着更多接口和多态,业务跑起来需要额外的空间和时间;尽量不要开多进程,进程之间的通讯比同一进程之间的互调消耗的性能非常多,一般项目只要一个进程就够了,有推送的可以多一个推送进程;此外,不限制与Android客户端开发,H5、服务器的优化也能提高APP的性能。
性能问题的排查方法
这个在后面的具体分析中,再结合实际遇到的问题一起介绍吧。
关于性能优化的技术点,欢迎大家到这个日志里补充:[收集向] Android 性能调优的技术点