前言
之前有和各位同学分享过启动的两篇文章:
第一篇《Android启动这些事儿,你都拎得清吗?》从源码的角度分析了启动流程。
第二篇《进阶应用启动分析,这一篇就够了!》讲了了如何使用工具测量启动流程。
今天我将结合自己的过往工作经验,分享一下常见的启动优化和一些黑科技的实操。
一、准备
在正式讲优化的方法之前,默认各位同学已经掌握了:
- 启动的源码分析
- 启动时长的监控
因为在实际的分析过程,一定是我们懂得了自己应用的启动阶段的各个耗时点,然后对这些流程分析,最终做出针对性的优化策略。
最简单来讲,我们自己的应用的启动时长怎么定义的,启动的开始点在哪里,结束点在哪里。举个例子,我们App之前定义的两个点:
- 开始点:拦截的
ActivityThread
里面的消息机制Application
创建的点 - 结束点:第一个
Activity
的onWindowsFocusChanged
方法
看一下 Android 官方的应用启动图:
我们知道,onWindowsFocusChanged
回调发生在 Activity#onCreate
之后,又在第一帧vSync之前,也就是途中的的 Displayed Time 和 reportFullyDrawn 之间。所以对于我来说,就可以知道我们的应用的优化范围在 Application#onCreate
和闪屏页,后面就可以持续的对这一块儿做优化。
另外一个重点就是关于启动工具的选择。对于启动流程的分析,我强烈建议使用 Android Studio Profiler 工具,使用其中的 TraceSystemCall 功能,优点如下:
- 分析各种系统资源:CPU使用情况、显示(Vsync信号、卡顿市场)、一些核心函数的耗时。
- 函数插桩:对于想关注的其他方法耗时,可以通过函数插装来实现。
系统资源的分析:
可以看到,系统资源的展示是比较全面的。
二、常用优化
先聊聊常用的优化策略吧。
1、梳理冗余逻辑
梳理冗余逻辑这个词看着比较简单,实际做起来也是不难,主要是各种业务的权衡与取舍。
有如下两点。
1.1 去除历史包袱
做启动优化的第一步是梳理启动业务流程,如果我们开发的是一个中大型应用,那么其中的很多流程是把握不准的,因此我之前的策略是和同事在周会上review代码,将启动过程的业务一个个的过,标记下不用的业务,然后在后续开发中下线。
对于更大型的团队,如大众点评,遇到相关的不熟的业务,不仅要和团队内沟通,还需要和团队外的其他业务方进行沟通,了解相关的启动业务的使用情况。
1.2 了解业务使用时机
梳理完启动业务的流程以后,我们需要对启动的业务的使用有一定的了解。对于时间偏长的任务,我们去思考一下,这个任务真的有必要在启动中去使用吗?是否可以使用懒加载,这可能需要和相关的业务方进行Argue。
2、启动框架
我想各位同学一定知道,在启动过程中,如果遇到耗时任务,可以根据情况放到异步线程,但是异步线程也会有一些特殊情况:
- 时机保证:有一些重要的异步线程任务,如何保证在在启动结束后,能够及时使用到对应的功能。
- 效率保证:如何保证异步线程的数量。
- 任务时机:有一些任务是有依赖关系的,如何保证任务的执行顺序。
- …
这也是我们使用启动框架原因,利用多核的CPU + 多线程,高效率、并行、有序、按时执行启动任务。
如果要做好一个启动框架,有几个重要的点:
2.1 基础框架
我之前使用过的启动框架有:android-startup。
在这个框架中,启动任务分为三种:
- 主线程重要的任务:
Application#onCreate
结束前主线程执行完成 - 子线程重要任务:交给线程池执行,也会在
Application#onCreate
结束前执行完成 - 字线程不重要的任务:交给线程池后台执行
还有一些点处理的比较好:
- 任务排序:很好的处理了任务依赖关系,如果发生了循环依赖,可以在任务的拓扑排序阶段,就对外抛出异常
- 记录任务耗时:统计好各个任务的时长,记录下来,后期有需要,可以上传到埋点
然后,这个框架有一些点还需要改进:
- 主线程不重要任务:我们可能还有主线程不太重要的任务,这个时候可以交给
idleHandler
执行 - 更多的执行时机:这个启动框架主要针对的时机是Application#onCreate,我们可以有更多的时机,比如首页初始化、首页空闲时、次级页面打开时,通过划分更多的时机,可以缓解CPU、线程池和内存的压力,从而降低启动时长。
2.2 动态调整启动任务的执行顺序
通常启动框架去执行启动任务的时候,顺序都是确定的。有时,我们会对外进行广告投放,想给用户一个比较有吸引力的落地页,比如我在腾讯体育看到一个京东的目的商品的广告投放页,像这样:
这个时候落地页可能是一个活动页,那这个活动页的技术栈和首页的技术栈大概率是不一样的,那么我们是否可以针对活动页的技术栈调整一下启动任务顺序,从而降低启动时长。
简单来说,这个步骤有如下几步:
- 加入标记:在对外投放的Deeplink中,加入相关的标记
- 识别标记:在Android或者iOS启动过程中,可以通过Hook的方式或者其他方式,在启动的早期,拿到相关的参数
- 改变任务执行优先级:可以在系统中静态注册相关标记下的另外一套任务执行级顺序,或者动态下发也可以
核心的想法就是跟落地页相关的技术栈的任务时机往前挪,不相关的任务往后挪,从而保证启动时长的最低。
3、线程梳理
在启动过程中,如果线程资源不加以限制,线程数量可能就有几百个,这会有什么问题呢?
- 资源消耗过高:每个线程都需要一定的系统资源,包括内存、CPU时间等
- 上下文切换开销:操作系统需要在多个线程之间进行上下文切换,以便让每个线程都有机会执行。频繁的上下文切换会带来额外的开销,影响应用程序的整体性能
线程的数量可以在性能分析工具中查看。具体的治理策略有:
- 避免使用new Thread的方式创建线程
- 对于一些可以替换线程池的第三方库,替换成内部使用的线程池
- 将第三方SDK中开源库,在核心线程空闲的时候,也能够进行释放
先讲一下第一点,如果项目团队不大,开发的人员都在一个项目中开发,那么我们使用全局搜索就可以定位new Thread的位置,但对于第三方库中的创建却无从定位。那如果是大的项目,每个团队都有自己的开发模块,这种怎么定位呢?
Booster有给我们具体的解决方案,在字节码Transform的时候,将线程的调用方,然后传递给线程,运行的时候给它打印出来。
再简单讲一下第三点吧,可以看Booster框架,它提供了一些思路,也是在Transform的时候,将第三发的线程池的allowCoreThreadTimeOut
设置为true
,让它可以在空闲的时候能够进行释放,除此以外,还可以:
- 线程池的
corePoolSize
设置为0 - 为
maxPoolSize
设置上限
4、闪屏页优化
根据我的经验,闪屏一般有两种:
- 有对应业务的闪屏:比如说可以自定义闪屏页、或者承接开屏广告的工作,如起点读书,B站
- 纯闪屏:如大众点评,京东类
4.1 纯闪屏页
对于纯闪屏的应用,给人的感觉就是启动速度非常快,因为启动第一个Activity可能就是我们的首页。
这里有一个优化措施就是利用StartWindow机制,简单介绍一下,在Android的启动过程中,在第一个Activity真正显示之前,系统会会提供一个页面来进行过渡,我们称之为StartWindow。StartWindow会在应用的第一个Activity绘制完成以后被移除。
默认情况下根据主题而定,白色或者黑色,我们也可以设置成自定义的颜色或者图片。
具体的优化策略:
- 启动的时候,为首个Activity提供一个带闪屏页的主题。
- 在进入Activity以后,在onCreate方法中设置透明主题。
通过在onCreate中设置透明主题,我们可以减少绘制一层背景,通常我们在首页中,也不需要带背景。
4.2 携带业务的闪屏页
对于承接业务的闪屏页,这类应用启动的第一个页面一般就不是首页,它就是一个单独的闪屏页Activity,里面会有一些广告处理的逻辑。
这里也有一些具体的优化措施,除了上述的StartWindow机制以外,还有:
- 将xml布局的方式改成动态的创建View
- 预加载闪屏页的背景图片
一般这类的闪屏页的元素也比较简单,将xml布局改成动态创建View,可以减少读取xml文件和反射创建View的时间,在中低端的效果还是比较明显的。
5、系统资源处理
系统资源指的是锁竞争、IO治理、CPU治理,通过监控这些数据,然后分析一下其中的不合理之处,这个其实是一个细活,需要通过Perfetto和Profiler工具查看。
6、Baseline Profile
早期的Android虚拟机采用的是Dalvik,为了提高Java执行的效率,在虚拟机中采用JIT(Just in time)技术,在运行的时候将高频的方法编译成机器码,但是JIT编译的机器码是存在内存中的,下次冷启动,这些数据会丢失,对于类似服务端长期运行的Java应用来讲,提效明显。对于Android应用来讲,应用可能需要经常重启,显然就不是那么友好了。
AOT(Ahead of time)是一种预编译机制,可以将Apk中的字节码编译成二进制的机器码,减少运行时间。Android 7.0以前,在安装的时候,会将全部的字节码编译成机器码,但这会有两个问题:
- 安装时间长
- 安装包体积大
Android 7.0以后支持JIT和AOT并存的编译模式,其中AOT中有两种编译策略值得关注:
- quicken:应用安装时的编译模式,相对编译速度较快,占用空间合理
- speed-profile:系统后台触发的编译模式,按照用户的习惯进行特定的优化
所谓的Baseline Profile,指的是提前扫描我们的热点代码,生成配置文件,然后在安装的时候,对这些热点代码做AOT,可以看一下谷歌官方给的流程图:
这个是借助Google Play实现的,所以针对国内的应用,可行吗?
根据网易云音乐得出来的结果,AOT其实有两种场景:
- 安装时AOT:在安装或者更新过程中,提前对这些热点代码aot,从而降低我们的启动时长,国内厂商对这一块儿支持的比较少,仅少数厂商支持。
- 还有一种场景就是启动后对Profile文件进行aot,流程如下图:
从网易云优化的结果来看,第一种方案提升明显,可以降低30%的启动时长(应该是安装或者更新后的首次启动时长),第二种仅有5%。
三、黑科技
我们再来聊聊黑科技,其中的一些策略需要投入比较长的时间,一个人还是比较难搞定的。
1、Apk资源重排
1.1 背景
Android底层运行着Linux系统,当App启动时,需要通过Linux系统从磁盘中加载很多文件到内存中,比如代码、资源文件(Manifest文件、布局、图片),把这些文件加载到内存中。
Linux加载文件有两种方式:
- 普通文件读取
- 内存映射
两种文件加载方式都会把文件内容加载到pagecache中,如果读取文件已经在pagecache中,就不会发生真正的磁盘IO,而是直接从pagecache中读取,这就大大提升读的速度。
流程如下:
1.2 内部优化策略
为了提升磁盘读取效率,Linux采取了预读机制。简单来说:
- 单个文件的第一次读取,系统读入所请求页面的后面几个页面作为缓存。
- 如果下次要读取的页面不在缓存中,则表明此次的文件访问不是顺序访问,系统会采用之前的同步预读方式。
- 如果读的页面命中,系统会把之前预读的页面扩大一倍,但是这个过程时异步的。
如果我们启动过程中,读取的apk文件按实际加载顺序排列,就能充分的利用Linux预读机制,减少启动过程中的磁盘IO,从而降低启动时间。
1.3 技术策略
那么我们能做的就是统计这些资源文件的命中率,代码文件其实受AOT影响,拿到启动资源文件的顺序以后,重新打包,中间涉及的流程还是挺复杂的。
可以参考:《支付宝 App 构建优化解析:通过安装包重排布优化 Android 端启动性能》
2、dex2aot触发
dex2aot指的就是我们在Baseline Profile方案中说的aot,aot的时机有很多种,常见的有:
- 安装或者更新的时候触发
- 应用空闲的时候,处在后台触发aot
- 系统空闲
这些其实是系统帮我们触发的,并且也具有不确定性,我们是否可以让应用处在后台的时候主动触发这些流程吗?
答案是肯定的,查看源码的时候发现,aot的流程都是由PackageManagerService触发的,其中的函数pefromdexOpt可以通过一些手段被我们主动触发。
3、启动阶段抑制GC
在启动过程中,我们希望合理的使用CPU,避免启动过程中CPU被一些任务长时间的占用。下图是通过使用字节的Btrace结合Perfetto分析得出来启动流程中的HeapTaskDaemon
执行情况:
我们可以发现了HeapTaskDaemon线程占用了比较高CPU时间片,这个线程实际上是虚拟机执行GC操作的。
简单介绍一下,HeapTaskDaemon是一个守护线程,随着Zygote线程一起启动,HeapTaskDaemon做的就是无限从执行GC的HeapTask
集合里面取任务执行,对于需要延时的任务,会阻塞到目标执行。
那么我们可以通过获取系统的HeapTask,并让这个HeapTask休眠,同样能达到抑制HeapTaskDaemon线程执行的目的。这个过程比较复杂,可以参考:
需要指出的是,在 Android 8.0 以后,在应用启动的时候,会默认执行TriggerPostForkCCGcTask,该任务可以将GC延后2秒执行,所以我们看到,上面的HeapTaskDaemon并不是一开始就执行的。所以我们需要分析一下,在启动还没完成的场景下,就GC的场景是不是很多。
4、保活
现在大部分包活策略,都不太行的通了,但是之前看过某个开源项目,安装以后,即使用户手动点击强行停止,该软件也能重新启动。
开源项目:
AndroidKeepAlive:github.com/fgkeepalive…
TechMerger里面的一篇文章也有介绍,原因如下:
地址:mp.weixin.qq.com/s/E038lXvQw…
里面的作者反编译后得出的结论是,文中的流氓软件主要做了:
- 被杀后重启:通过高优先级的native进程进行监听
- 通过各种手段提高进程优先级
而其中提高进程的优先级有:
- UI进程与Service进程分离
- 使用MediaPlayer播放无声音乐
- 使用AccountManager备份数据
- 注册无障碍服务
- 注册设备管理器
可以看到,流氓软件还是做了很多东西,有兴趣的读者可以看一下原文。
总结
本文中涉及到的很多内容都没有深入讲解,只是提供了一个思路和一些策略,希望做一个抛砖引玉。如果你有更好的想法,欢迎评论区留言。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/22564,转载请注明出处。
评论0