做请柬的网站,dede网站首页,医院网站内链优化,docker安wordpress作者#xff1a;95分技术 启动优化是Android优化老生常谈的问题了。众所周知#xff0c;android的启动是指用户从点击 icon 到看到首帧可交互的流程。
而启动流程 粗略的可以分为以下几个阶段
fork创建出一个新的进程创建初始化Application类、创建四大组件等 走Applicatio… 作者95分技术 启动优化是Android优化老生常谈的问题了。众所周知android的启动是指用户从点击 icon 到看到首帧可交互的流程。
而启动流程 粗略的可以分为以下几个阶段
fork创建出一个新的进程创建初始化Application类、创建四大组件等 走Application.onCreate()创建launchActivity 走完onCreate、onStart、onResume生命周期
往深往细里面钻研 这里可以有非常多的‘黑科技’能操作mutilDex优化message调度优化json预热之类的方案非常多。
本文只解决一个点针对Application.onCreate()做优化。
一、技术背景
随着业务的发展堆叠application中初始化方法越来越臃肿。代码全都堆在一起例如
initARouter(app)
initAutoSize()
initFlipper()
initNetworkConfig()
launch()
configXXX()
ServiceManager.init(app)
initJVerification(app)
initHotFix(app)
initAPM(app)
initBugly(app)
....省略大量初始化代码当大量的初始化方法这样累加在一起必然会导致启动变慢。这是第一个问题启动变慢
随着项目的组件化逐步进行这里就存在了一个新问题。为了业务解耦每个业务模块需要不同的功能例如商品模块需要分享物流定位模块需要地图等。但是这些功能并非全部业务组件都用到的东西放到主工程Application不合适。这是第二个问题业务上的解耦
所以我们需要一个启动时简单、高效的初始化组件的方法这也是为什么设计这套startup的原因。
二、算法基础
要解决启动变慢的问题主要有两个思路延迟加载和异步加载。当然大部分库都是需要在进入首页之前初始化完成的否则会产生一些异常。所以我们这里首先解决如何去异步加载的问题。
2.1 : 有向无环图 DAG有向无环图能够管理任务之间的依赖关系并调度这些任务似乎能够满足本节开始的诉求那么我们先了解下这种数据结构。 顶点在DAG中每个节点sdk1/sdk2/sdk3…都是一个顶点 边连接每个节点的连接线 入度每个节点依赖的节点数形象来说就是有几根线连接到该节点例如sdk2的入度是1sdk5的入度是2。 我们从图中可以看出是有方向的但是没有路径再次回到起点因此就叫做有向无环图 2.2 : 拓扑排序 拓扑排序用于对节点的依赖关系进行排序主要分为两种DFS深度优先遍历这也是我们的方案、BFS广度优先遍历如果了解二叉树的话对于这两种算法应该比较熟悉。 我们就拿这张图来演示拓扑排序算法的流程 1:首先找到图中入度为0的顶点那么这张图中入度为0的顶点就是task1然后删除 2:删除之后再次找到入度为0的顶点这个时候有两个入度为0的顶点task2和task3所以拓扑排序的结果不是唯一的 3:依次递归直到删除全部入度为0的顶点完成拓扑排序 三、技术方案
3.1 : 接口设计
要把我们启动任务拆分为若干个小task去调度启动首先设计我们的task基类。
interface ITask : ITaskCallBack {/**
* 任务name
*/
val taskName: String/**
* 任务是否完成
*/
val isCompleted: Boolean/**
* 是否要block启动
*/
val needAwait: Boolean/**
* 任务初始化进程
*/
val process: RunProcess/**
* 任务是否可用
*/
val enable: Boolean/**
* 是否在主线程执行
*/
val runOnMainThread: Boolean/**
* 是否需要同意隐私协议后再执行
*/
val needPrivateAgree: Boolean/**
* 依赖的task
*/
fun dependsTaskList(): ListString/**
* 任务被执行的时候回调
*/
fun run(application: Application)}并且提供实现Task.class
abstract class Task(override val taskName: String) : ITask {private var completed: AtomicBoolean AtomicBoolean(false)override val isCompleted: Booleanget() completed.get()/ ** * 默认运行在主进程 */ override val process: RunProcessget() RunProcess.MAIN/ ** * 默认阻塞启动 */ override val needAwait: Boolean true/ ** * 默认运行 */ override val enable: Boolean true/ ** * 默认运行在子线程 */ override val runOnMainThread: Boolean false/ ** * 默认需要同意隐私协议后初始化 */ override val needPrivateAgree: Boolean true/ ** * 用来在前置任务完成之前阻塞当前task */ private val countDownLatch: CountDownLatch by lazy { CountDownLatch(dependsTaskList().size)} override fun dependsTaskList() emptyList String()override fun runProcessName(): ListString emptyList( )/ ** * 当前任务开始等待 直至依赖项全部完成再开始执行 */ internal fun await() {if (dependsTaskList().isNotEmpty( ))countDownLatch.await()}/ ** * 通知某个依赖项完成 */ internal fun countdown() {if (dependsTaskList().isNotEmpty( ))countDownLatch.countDown()}override fun onAdd() {}CallSuperoverride fun onStart() {completed.set(false)}CallSuperoverride fun onFinish() {completed.set(true)}override fun toString(): String {return $taskName(enable$enable, runOnMainThread$runOnMainThread, needPrivateAgree$needPrivateAgree ,dependsTaskList${dependsTaskList()})}}我们提供实现Task类去定义启动任务注意定义各种参数。
启动配置 Startup.debug(BuildConfig.DEBUG).privateAgreeCondition { Storage.APP_FIRST_PRIVATE_DIALOG }.start(app)3.2 : 线程管理设计
首先我们的任务分为两种模式运行在主线程和运行在子线程。
既然不能保证每个任务都在主线程中执行那么就需要对任务做配置
interface ITask {/**
* 是否在主线程执行
*/
val runOnMainThread: Boolean
}3.2.1 : 线程池
既然要在子线程初始化一些任务那么我们必须维护一个线程池。
CPU密集型也是指计算密集型大部分时间用来做计算逻辑判断等CPU动作的程序称为CPU密集型任务。该类型的任务需要进行大量的计算主要消耗CPU资源。这种计算密集型任务虽然也可以用多任务完成但是任务越多花在任务切换的时间就越多CPU执行任务的效率就越低所以要最高效地利用CPU计算密集型任务同时进行的数量应当等于CPU的核心数。占据CPU的时间片过多的话会影响性能所以这里控制了最大 并发 防止主线程的时间片减少
IO密集型任务指任务需要执行大量的IO操作涉及到网络、磁盘IO操作对CPU消耗较少。有好多任务其实占用的CPU time非常少所以使用缓存线程池,基本上来者不拒
这里我们选用的是CPU****密集型任务的线程池。
threadList.forEach {
if (it.isCompleted) {setNotifyChildren(it)} else {threadPoolExecutor.execute(TaskRunnable(application, task it))}
}
mainList.forEach {
if (it.isCompleted) {setNotifyChildren(it)} else {TaskRunnable(application, task it).run()}
} 3.2.2 : 任务分发
有一些task是依赖于别的task的需要在其他task初始化完成后才能初始化自己。比如预取任务必须要在网络初始化完成后再执行。
而往往这些任务可能是运行在不同的线程里的那就有一个大问题任务之间的执行顺序或者说分发。比如sdk4是耗时任务可以放在子线程中执行但是又依赖sdk2的初始化这种情况下我们其实不能保证每个任务都是在主线程中执行的需要等待某个线程执行完成之后再执行下个线程我们先看一个简单的问题假如有两个线程 AB A线程需要三步完成当执行到第二步的时候开始执行B线程这种情况下该怎么处理
答案是 CountDownLatch。
相信大家对CountDownLatch并不陌生。它的原理就是会阻塞当前并等待所有的线程都执行完成之后再执行下一个任务。
我们先看task的配置
interface ITask : ITaskCallBack {/**
* 依赖的task
*/
fun dependsTaskList(): ListString
}dependsTaskList表示该task要等待这些task初始化完成后再完成string是依赖task的taskName。通过字符串解耦。
这里简单看一下启动的流程只看一些关键代码
step1
private fun executeTasks(application: Application, list: ListTask) {//。。。//这里是子线程任务
threadList.forEach {threadPoolExecutor.execute(TaskRunnable(application, task it))}//这里是主线程任务
mainList.forEach {TaskRunnable(application, task it).run()}
}step2
class TaskRunnable(private val application: Application,private val task: Task
) : Runnable {override fun run() {// 前置任务没有执行完毕的话等待执行完毕的话往下走task.await()//......// 执行任务task.run(application)//.......// 通知子任务当前任务执行完毕了相应的计数器要减一。Startup.notify(task)}
}step3
class Task{/*** 用来在前置任务完成之前阻塞当前task*/private val countDownLatch: CountDownLatch by lazy {CountDownLatch(dependsTaskList().size)}/*** 当前任务开始等待 直至依赖项全部完成再开始执行*/internal fun await() {if (dependsTaskList().isNotEmpty())countDownLatch.await()}/*** 通知某个依赖项完成*/internal fun countdown() {if (dependsTaskList().isNotEmpty())countDownLatch.countDown()}
}当我们Startup启动的时候首先会对所有的task实例进行拓扑排序那些被其他Task所依赖且自身不依赖于其他Task的Task必然会先进队列执行这里保证了我们的task不会被互相阻塞。
同时我们有一个childrenMapkey是所有被其他task所依赖的taskvalue是所有依赖于key的task的list。这个map是当被依赖的task执行完成的用于唤醒被阻塞的task。
当我们的task被执行的时候首先我们会执行Task的await()。如果该task存在依赖task,会阻塞。直到所有的依赖task都执行完毕。而我们是怎么去判断依赖的task都执行完毕的呢 这里就用到了上面说的childrenMap了。
当每个task执行结束的时候我们会调用Startup的setNotifyChildren方法然后去childrenMap中去查找依赖于此task的其他task调用其conutdown方法。使其计数器countDownLatch减1,而countDownLatch的count就是其依赖的task的size。当其每个依赖的task都执行完发出notifyChildren信号后阻塞放开开始执行。
同时上面也说了经过拓扑排序后被依赖的task一定先进队列这样也避免了cpu线程池中被阻塞的线程塞满的情况也就是互相阻塞一直等待的情况。
3.2.3 : 提前释放
application初始化中的场景非常复杂这里存在一种场景我们的application不需要等待某个task执行完后再结束。也就是某些必要task执行完了不等待其他task执行完直接进入页面。
流程如图 这里在task中也有配置
interface ITask{/*** 是否要block启动*/val needAwait: Boolean
}当然 这个task一定要是运行在子线程的啊。一个任务不能即运行在主线程又不阻塞主线程。
这里需要注意当你的task的needAwait为false且runOnMainThread为true的时候会直接报错 太扯了。
而具体实现看代码
private fun executeTasks(application: Application, list: ListTask) {if (list.isEmpty()) throw StartupException(tasks不能为空)taskMap.clear()taskChildMap.clear()val sortResult TaskSortUtil.getSortResult(list, taskMap, taskChildMap)sortResult.forEach {
if (it.runOnMainThread) {mainList.add(it)} else {threadList.add(it)}}countDownLatch CountDownLatch(1)executeMonitor.recordProjectStart()listeners.forEach {
it.onProjectStart()}
threadList.forEach {
if (it.isCompleted) {notifyChildren(it)} else {threadPoolExecutor.execute(TaskRunnable(application, task it))}}
mainList.forEach {
if (it.isCompleted) {notifyChildren(it)} else {TaskRunnable(application, task it).run()}}
countDownLatch?.await()
}internal fun notifyChildren(task: Task) {taskChildMap[task.taskName]?.forEach {
taskMap[it.taskName]?.countdown()}
if (task.needAwait) {finishTask.incrementAndGet()}val taskSize if (isPrivateAgree) {totalAwaitTaskSize.get()} else {noPrivateTask.sumBy { if (it.needAwait) 1 else 0 }
}if (finishTask.get() taskSize) {countDownLatch?.countDown()executeMonitor.recordProjectFinish()onGetMonitorRecordCallback?.onGetProjectExecuteTime(executeMonitor.projectCostTime)onGetMonitorRecordCallback?.onGetTaskExecuteRecord(executeMonitor.executeTimeMap)listeners.forEach {
it.onProjectFinish()}
}
}原理很简单启动的时候会开启一个countLatch去阻塞住主线程并当所有需要阻塞主线程的任务完成后放开并视为启动结束。
3.3 : 业务模块自动注册
伴随着项目的逐步组件化各个模块之间充分解耦。当我们在各个module去定义好自己的初始化task后存在一个严重的问题。我们需要在主application里面去感知收集到这些task并且对之进行拓扑排序。
当然我们可以去一一依赖并手动创建new出来这些task并add到我们的容器里但是这样有一些严重的耦合问题而且会导致一些重复依赖bug。并且这样极不优雅且代码侵入性极强当task一多我们要手写几十行的addTask代码很不优雅 。
AutoRegister很好很强大大家想了解的可以去github上阅读源码简单直白来说就是五个字 字节码插桩
使用autoRegister方法 自定义了一个AutoRegister接口
interface AutoRegister然后将我们自定义的启动task去实现AutoRegister接口即可完成自动注册。 3.4 : 进程管理设计 不同启动任务运行的进程可能不一致这里是通过task的process字段控制。
interface ITask : ITaskCallBack {/**
* 初始化进程
*/
val process: RunProcess
}sealed class RunProcess(val processNames: ListString) {abstract fun check(application: Application, processName: String?): Boolean//仅主进程初始化object MAIN : RunProcess(emptyList( )) {override fun check(application: Application, processName: String?): Boolean {return application.packageName processName}}//所有进程都初始化object ALL : RunProcess(emptyList( )) {override fun check(application: Application, processName: String?): Boolean {return true}}//非进程初始化object OTHER : RunProcess(emptyList( )) {override fun check(application: Application, processName: String?): Boolean {return application.packageName ! processName}}//指定进程初始化class SPECIAL(processNames: ListString) : RunProcess(processNames) {override fun check(application: Application, processName: String?): Boolean {return processName in processNames}}
}顾名思义 启动进程mode有四种仅主进程初始化仅非主进程初始化所有进程都初始化仅特定进程初始化。
当引入进程概念的时候又新增了一个问题当前task和依赖的task不在同一个进程初始化可能会导致异常。这里在自动注册的时候已经判断好了如果进程有异常会主动抛异常大家定义task的时候注意就好了。
3.5 : 非自动任务的处理
当前app大都有隐私合规的需求当我们初次冷启动app的时候不能一股脑全部初始化有些task需要用户同意了隐私协议后才能初始化。
为了解决隐私合规的问题在task中我们提供了配置项
interface ITask {/ ** * 是否需要同意隐私协议后再执行 */ val needPrivateAgree: Boolean
}Startup类中同时也提供了两个方法
object Startup {/*** 判断当前是否同意隐私协议* param condition 返回是否同意隐私协议*/fun privateAgreeCondition(condition: () - Boolean) apply {privateCondition condition}/*** 当用户同意隐私协议后 调用方法进行下一步sdk初始化*/fun notifyPrivateAgree(application: Application) {val currentTaskList noPrivateTask needPrivateTasksexecuteTasks(application, currentTaskList)}
}其中 privateAgreeCondition是配置方法我们需要在调用start方法之前配置好当启动时会根据privateCondition的返回值去决定是否去启动那些需要同意协议后才能初始化的task
notifyPrivateAgree是当用户同意协议后去手动调用去继续初始化下一步需要同意协议的task
四 、上线效果与总结
在app内部新增启动分析页面 把启动过程中的任务和耗时做了一个简单可视化页面启动流程一目了然。 同时在数据平台观察最新的上报数据 可以看到启动过程中 Application的onCreate方法耗时下降接近一倍大幅提升用户启动时的体验同时方案设计也保留了充分的拓展性后续新增启动项时也可以快速高效的接入这套框架保证启动效果不劣化。
启动优化一直都是Android性能优化中最重要的一环简称APP的门面担当。在app上线后与用户接触的第一个功能就是APP的启动它的启动时长直接就决定了用户后续是否会继续使用。在APP性能优化中除了启动优化很重要以外其他的优化技术也很重要像内存优化、卡顿优化、网络优化、安全优化……等等。
为了帮助到大家更好的全面清晰的掌握好性能优化准备了相关的学习路线以及核心笔记还该底层逻辑https://qr18.cn/FVlo89 大家可以进行参考学习
性能优化核心笔记https://qr18.cn/FVlo89
启动优化 内存优化 UI优化 网络优化 Bitmap优化与图片压缩优化https://qr18.cn/FVlo89 多线程并发优化与数据传输效率优化 体积包优化
《Android 性能监控框架》https://qr18.cn/FVlo89 《Android Framework学习手册》https://qr18.cn/AQpN4J
开机Init 进程开机启动 Zygote 进程开机启动 SystemServer 进程Binder 驱动AMS 的启动过程PMS 的启动过程Launcher 的启动过程Android 四大组件Android 系统服务 - Input 事件的分发过程Android 底层渲染 - 屏幕刷新机制源码分析Android 源码分析实战