Android 启动优化(一) - 有向无环图是原理

简介: Android 启动优化(一) - 有向无环图是原理

Android 启动优化(一) - 有向无环图


Android 启动优化(二) - 拓扑排序的原理以及解题思路


Android 启动优化(三)- AnchorTask 开源了


Android 启动优化(四)- AnchorTask 是怎么实现的


Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了


Android 启动优化(六)- 深入理解布局优化


这几篇文章从 0 到 1,讲解 DAG 有向无环图是怎么实现的,以及在 Android 启动优化的应用。


推荐理由:现在挺多文章一谈到启动优化,动不动就聊拓扑结构,这篇文章从数据结构到算法、到设计都给大家说清楚了,开源项目也有非常强的借鉴意义。


前言


说到 Android 启动优化,大家第一时间可能会想到异步加载。将耗时任务放到子线程加载,等到所有加载任务加载完成之后,再进入首页。


多线程异步加载方案确实是 ok 的。但如果遇到前后依赖的关系呢。比如任务2 依赖于任务 1,这时候要怎么解决呢。


最简单的方案是将任务1 丢到主线程加载,然后再启动多线程异步加载。


如果遇到更复杂的依赖呢。


任务3 依赖于任务 2, 任务 2 依赖于任务 1 呢,这时候你要怎么解决。更复杂的依赖关系呢


e99278eb962fa574c97298b909f1fec6_ec0105a19f24eec6f8045bd919a3325d.png


总不能将任务 2,任务 3 都放到主线程加载吧,这样多线程加载的意义就不大了。


有没有更好的方案呢?


答案肯定是有的,使用有向无环图。它可以完美解决先后依赖关系。


重要概念


有向无环图(Directed Acyclic Graph, DAG)是有向图的一种,字面意思的理解就是图中没有环。常常被用来表示事件之间的驱动依赖关系,管理任务之间的调度。


b399ba7d379054fdb68b960e0dd7bbe5_e62cb7b6d27b105b964237c763ebe319.png


顶点:图中的一个点,比如顶点 1,顶点 2。


边:连接两个顶点的线段叫做边,edge。


入度:代表当前有多少边指向它。


在上图中,顶掉 1 的入度是 0,因为没有任何边指向它。 顶掉 2 的入度是 1, 因为 顶掉 1 指向 顶掉 2. 同理可得出 5 的入度是 2,因为顶掉 4 和顶点 3 指向它


拓扑排序:拓扑排序是对一个有向图构造拓扑序列的过程。它具有如下特点。


  • 每个顶点出现且只出现一次。
  • 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面


由于有这个特点,因此常常用有向无环图的数据结构用来解决依赖关系。


上图中,拓扑排序之后,任务2肯定排在任务1之后,因为任务2依赖 任务1, 任务3肯定在任务2之后,因为任务3依赖任务2。


拓扑排序一般有两种算法,第一种是入度表法,第二种是 DFS 方法。下面,让我们一起来看一下怎么实现它。


入度表法


入度表法是根据顶点的入度来判断是否有依赖关系的。若顶点的入度不为 0,则表示它有前置依赖。它也常常被称作 BFS 算法


算法思想


  • 建立入度表,入度为 0 的节点先入队
  • 当队列不为空,进行循环判断
  • 节点出队,添加到结果 list 当中
  • 将该节点的邻居入度减 1
  • 若邻居节点入度为 0,加入队列
  • 若结果 list 与所有节点数量相等,则证明不存在环。否则,存在环


实例讲解


下图所示的有向无环图,采用入度表的方法获取拓扑排序过程。


a2f6cea46a779ed2c0a73d5b99a5af0c_52e6e96e1368c68a93315d69b878d940.png


首先,我们选择入度为 0 的顶点,这里顶点 1 的入度为 0,删除顶点 1 之后,图变成如下。


2980fc93380a8c892ea8a013cd5aa6ff_03d2a39fbfe9ddb637fc1d422b2d2d12.png


这时候,顶点 2 和顶点 4 的入度都为 0,我们可以随便删除一个顶点。(这也就是为什么图的拓扑排序不是唯一的原因)。这里我们删除顶点 2,图变成如下:


02c0c159ca598ab2982197b363fb95ae_5ef56ab2832d0e7d83c572ca434b54c7.png


这时候,我们再删除顶点 4,图变成如下:


f5b24f51292f9a8b1e9d2d454c238875_913a0c44e2bcd7cce01347dfaf0c1dea.png


选择入度为 0 的顶点 3,删除顶点 3 之后,图标称如下,


728168cccab6cfca9258ec1b6eaaa8e3_baad3ee3957062b3e1d8986309982394.png


最后剩余顶点5,输出顶点5,拓扑排序过程结束。最终的输出结果为:


16973a3b98ea262d6bb30467e6fa8763_abc498668f7222dcd33c87666f1b058e.png


到此,优先无环图的入度法的流程已经讲解完毕。你清楚了嘛。


代码的话,下期会一起给出。


时间复杂度


设 AOE 网有 n 个事件,e 个活动,则算法的主要执行是:


  • 求每个事件的ve值和vl值:时间复杂度是O(n+e) ;
  • 根据ve值和vl值找关键活动:时间复杂度是O(n+e) ;


因此,整个算法的时间复杂度是O(n+e)


DFS 算法


从上面的入度表法,我们可以知道,要得到有向无环图的拓扑排序,我们的关键点要找到入度为 0 的顶点。然后接着删除该结点的相邻所有边。再遍历所有结点。直到入度为 0 的队列为空。这种方法其实是 BFS。


说到 BFS,我们第一时间就想到 DFS。与 BFS 不同的是,DFS 的关键点在于找到,出度为0的顶点。


总结如下,深度优先搜索过程中,当到达出度为0的顶点时,需要进行回退。在执行回退时记录出度为0的顶点,将其入栈。则最终出栈顺序的逆序即为拓扑排序序列。


算法思想


  • 对图执行深度优先搜索。
  • 在执行深度优先搜索时,若某个顶点不能继续前进,即顶点的出度为0,则将此顶点入栈。
  • 最后得到栈中顺序的逆序即为拓扑排序顺序。


实例讲解


同样,以下图讲解 DFS 算法的过程。


6f2cbac92b956aaa30da14ea8dda1b71_b90100eb7e2c69d490e551017e2e55a4.png


(1) 从顶点 1 开始出发,开始执行深度优先搜索。顺序为1->2->3->5。


(2)深度优先搜索到达顶点5时,顶点5出度为0。将顶点5入栈。


(3)深度优先搜索执行回退,回退至顶点3。此时顶点3的出度为0,将顶点3入栈。


173ccf829eb0017c5f8200333fd5d2dd_cda41ca4d5752ab8834067aea409bb0d.png


(4)回退至顶点2,顶点2出度为0,顶点2入栈。


7252e96f306d167b073970605d87fd95_17f1eba4a588ea3c59e81dad502b778c.png


(5)回退至顶点1,顶点1可以前进位置为顶点4,顺序为1->4。


(6)顶点4出度为0,顶点4入栈。


3832e328de08eb976259d96a8ed89dc5_e3706c0f5c674d3bb74107cbe169c0c7.png


(7)回退至顶点1,顶点1出度为0,顶点1入栈。

6dd0531fe2c86001c5307483c8f41211_7ae49b7e90ab96e389e6fa8716763fe6.png


(8)栈的逆序为1->4->2->3->5。此顺序为拓扑排序结果。


7548a117be483952dc529ae6e8469221_ceba78bee0f258bb0d93dc3c4de488f6.png


时间复杂度


时间复杂度分析:首先深度优先搜索的时间复杂度为O(V+E),而每次只需将完成访问的顶点存入数组中,需要O(1),因而总复杂度为O(V+E)。


小结


有向无环图的拓扑排序其实并不难,难度中等。通常,我们一般使用 BFS 算法来解决,DFS 算法比较少用。


对于 BFS(入度表法),它的核心思想是


  1. 选择一个没有输入边(入度为0)的源顶点(若有多个则任选一个),
  2. 将它和它的输出边删除。重复源顶点的删除操作,直到不存在入度为0的源顶点为止。
  3. 最终,检测图中的顶点个数,若还有顶点存在则算法无解,否则顶点的删除顺序就是拓扑排序的输出顺序。


github


如果你觉得对你有所帮助,可以关注我的微信公众号程序员徐公,下一篇,将输出 Android 启动优化(二) - 拓扑排序的原理以及解题思路


相关文章
|
3天前
|
Java 数据库 Android开发
【专栏】构建高效 Android 应用:探究 Kotlin 多线程优化策略
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
7天前
|
缓存 监控 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第23天】 在竞争激烈的移动市场中,一个高效的Android应用是吸引并保留用户的关键。本文将探讨如何通过一系列技术手段和最佳实践来优化Android应用的用户体验和性能表现。我们将深入分析响应式UI设计、内存管理、多线程处理以及最新的Android框架特性,揭示它们如何共同作用以减少应用延迟,提高响应速度,并最终提升整体用户满意度。
|
7天前
|
安全 Shell Android开发
Android系统 init.rc sys/class系统节点写不进解决方案和原理分析
Android系统 init.rc sys/class系统节点写不进解决方案和原理分析
22 0
|
7天前
|
安全 Android开发
Android13 Root实现和原理分析
Android13 Root实现和原理分析
33 0
|
7天前
|
Java Android开发
Android系统 获取用户最后操作时间回调实现和原理分析
Android系统 获取用户最后操作时间回调实现和原理分析
15 0
|
7天前
|
存储 Java Android开发
Android系统 设置第三方应用为默认Launcher实现和原理分析
Android系统 设置第三方应用为默认Launcher实现和原理分析
20 0
|
1天前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin协程的优化实践
【4月更文挑战第29天】在移动开发领域,尤其是Android平台上,性能优化一直是开发者关注的重点。近年来,Kotlin语言凭借其简洁性和功能性成为Android开发的热门选择。其中,Kotlin协程作为一种轻量级的并发处理机制,为编写异步代码、网络请求和数据库操作提供了极大的便利。本文将深入探讨Kotlin协程在Android应用中的性能优化技巧,帮助开发者构建更加高效的应用程序。
|
1天前
|
移动开发 API Android开发
Android应用性能优化实战
【4月更文挑战第28天】在移动开发领域,一个流畅的用户体验是至关重要的。对于Android开发者而言,应用的性能优化是一项既挑战性也极其重要的工作。本文将深入探讨Android应用性能优化的多个方面,包括内存管理、UI渲染、多线程处理以及电池效率等,旨在为开发者提供实用的性能提升策略和具体的实施步骤。通过分析常见的性能瓶颈,并结合最新的Android系统特性和工具,我们的目标是帮助读者打造更加高效、响应迅速的Android应用。
|
3天前
|
缓存 监控 Android开发
Android 应用性能优化实战
【4月更文挑战第27天】 在竞争激烈的移动应用市场中,性能优越的应用更能吸引和保留用户。针对Android平台,本文将深入探讨影响应用性能的关键因素,并提供一系列实用的优化策略。我们将从内存管理、UI渲染、多线程处理以及电池使用效率等方面入手,通过具体案例分析如何诊断常见问题,并给出相应的解决方案。文中所提技巧旨在帮助开发者构建更加流畅、高效的Android应用。
15 2
|
6天前
|
移动开发 Java Android开发
构建高效Android应用:采用Kotlin协程优化网络请求
【4月更文挑战第24天】 在移动开发领域,尤其是对于Android平台而言,网络请求是一个不可或缺的功能。然而,随着用户对应用响应速度和稳定性要求的不断提高,传统的异步处理方式如回调地狱和RxJava已逐渐显示出局限性。本文将探讨如何利用Kotlin协程来简化异步代码,提升网络请求的效率和可读性。我们将深入分析协程的原理,并通过一个实际案例展示如何在Android应用中集成和优化网络请求。