Android 启动优化(四)- 手把手教你实现 AnchorTask

简介: Android 启动优化(四)- 手把手教你实现 AnchorTask

上一篇博客介绍了 AnchorTask 的基本使用,今天,让我们一起看一下怎么实现它。


原理简介


AnchorTask,锚点任务,它的实现原理是构建一个有向无环图,拓扑排序之后,如果任务 B 依赖任务 A,那么 A 一定排在任务 B 之前。


了解原理之前,请必须先了解有向无环图和多线程的一些基本知识,不然,下文,你基本是看不懂的。


一个共识

a49a8fb3cd4e2f5e63ce04ef8d6b340e_d75a4a340206542c8b2598a68479424f.png


  • 前置任务:任务 3 依赖于任务 0,1,那么任务 3 的前置任务是任务 0, 1
  • 子任务:任务 0 执行完之后,任务 3 才能执行,那么称呼任务 3 为 任务 0 的子任务


如何构建一个有向无环图


这里我们采用 BFS 方法实现,算法思想大概是这样的


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


多线程中,任务执行是随机的,那如何保证任务被依赖的任务先于任务执行呢?


这里要解决的主要有三个问题


  1. 首先我们要解决一个问题,当前任务有哪些前置任务,这个可以用 list 存储,代表它依赖的任务 list。当它所依赖的任务 list 没有执行完毕,当前任务需要等待。
  2. 当前任务执行完毕之后,所有依赖它的子任务需要感知到。我们可以用一个 map 来存储这种关系,key 是当前任务,value 是依赖于当前任务的集合(list)
  3. 多线程当中,等待和唤醒功能,有多种方式可以实现。wait、notify 机制,ReentrantLock Condition 机制,CountDownLatch 机制。这里我们选择 CountDownLatch 机制,因为 CountDownLatch 有点类似于计数器,特别适合这种场景。


具体实现


IAnchorTask


首先,我们定义一个 IAnchorTask 接口,主要有一个方法


  • isRunOnMainThread(): Boolean 表示是否在主线程运行,默认值是 false
  • priority(): Int 方法 表示线程的优先级别,默认值是 Process.THREAD_PRIORITY_FOREGROUND
  • needWait() 表示当我们调用 AnchorTaskDispatcher await 时,是否需要等待,return true,表示需要等待改任务执行结束,AnchorTaskDispatcher await 方法才能继续往下执行。
  • fun getDependsTaskList(): List<Class<out AnchorTask>>? 方法返回前置任务依赖,默认值是返回 null.
  • fun run() 方法,表示任务执行的时候


interface IAnchorTask : IAnchorCallBack {
    /**
     * 是否在主线程执行
     */
    fun isRunOnMainThread(): Boolean
    /**
     * 任务优先级别
     */
    @IntRange(
        from = Process.THREAD_PRIORITY_FOREGROUND.toLong(),
        to = Process.THREAD_PRIORITY_LOWEST.toLong()
    )
    fun priority(): Int
    /**
     * 调用 await 方法,是否需要等待改任务执行完成
     * true 不需要
     * false 需要
     */
    fun needWait(): Boolean
    /**
     * 当前任务的前置任务,可以用来确定顶点的入度
     */
    fun getDependsTaskList(): List<Class<out AnchorTask>>?
    /**
     * 任务被执行的时候回调
     */
    fun run()
}


它有一个实现类 AnchorTask,增加了 await 和 countdown 方法


  • await 方法,调用它,当前任务会等待
  • countdown() 方法,如果当前计数器值 > 0,会减一,否则,什么也不操作
abstract class AnchorTask : IAnchorTask {
    private val countDownLatch: CountDownLatch = CountDownLatch(getListSize())
    private fun getListSize() = getDependsTaskList()?.size ?: 0
    companion object {
        const val TAG = "AnchorTask"
    }
    /**
     * self call,await
     */
    fun await() {
        countDownLatch.await()
    }
    /**
     * parent call, countDown
     */
    fun countdown() {
        countDownLatch.countDown()
    }
}


排序实现


无环图的拓扑排序,这里采用的是 BFS 算法。具体的可以见 AnchorTaskUtils#getSortResult 方法,它有三个参数


  • list 存储所有的任务
  • taskMap: MutableMap<Class<out AnchorTask>, AnchorTask> = HashMap()存储所有的任务,key 是 Class,value 是 AnchorTask
  • taskChildMap: MutableMap<Class<out AnchorTask>, ArrayList<Class<out AnchorTask>>?> = HashMap(),储存当前任务的子任务, key 是当前任务的 class,value 是 AnchorTask 的 list


算法思想


  1. 首先找出所有入度为 0 的队列,用 queue 变量存储
  2. 当队列不为空,进行循环判断。
  1. 从队列 pop 出,添加到结果队列
  2. 遍历当前任务的子任务,通知他们的入度减一(其实是遍历 taskChildMap),如果入度为 0,添加到队列 queue 里面
  1. 当结果队列和 list size 不相等试,证明有环


@JvmStatic
    fun getSortResult(
        list: MutableList<AnchorTask>, taskMap: MutableMap<Class<out AnchorTask>, AnchorTask>,
        taskChildMap: MutableMap<Class<out AnchorTask>, ArrayList<Class<out AnchorTask>>?>
    ): MutableList<AnchorTask> {
        val result = ArrayList<AnchorTask>()
        // 入度为 0 的队列
        val queue = ArrayDeque<AnchorTask>()
        val taskIntegerHashMap = HashMap<Class<out AnchorTask>, Int>()
        // 建立每个 task 的入度关系
        list.forEach { anchorTask: AnchorTask ->
            val clz = anchorTask.javaClass
            if (taskIntegerHashMap.containsKey(clz)) {
                throw AnchorTaskException("anchorTask is repeat, anchorTask is $anchorTask, list is $list")
            }
            val size = anchorTask.getDependsTaskList()?.size ?: 0
            taskIntegerHashMap[clz] = size
            taskMap[clz] = anchorTask
            if (size == 0) {
                queue.offer(anchorTask)
            }
        }
        // 建立每个 task 的 children 关系
        list.forEach { anchorTask: AnchorTask ->
            anchorTask.getDependsTaskList()?.forEach { clz: Class<out AnchorTask> ->
                var list = taskChildMap[clz]
                if (list == null) {
                    list = ArrayList<Class<out AnchorTask>>()
                }
                list.add(anchorTask.javaClass)
                taskChildMap[clz] = list
            }
        }
        // 使用 BFS 方法获得有向无环图的拓扑排序
        while (!queue.isEmpty()) {
            val anchorTask = queue.pop()
            result.add(anchorTask)
            val clz = anchorTask.javaClass
            taskChildMap[clz]?.forEach { // 遍历所有依赖这个顶点的顶点,移除该顶点之后,如果入度为 0,加入到改队列当中
                var result = taskIntegerHashMap[it] ?: 0
                result--
                if (result == 0) {
                    queue.offer(taskMap[it])
                }
                taskIntegerHashMap[it] = result
            }
        }
        // size 不相等,证明有环
        if (list.size != result.size) {
            throw AnchorTaskException("Ring appeared,Please check.list is $list, result is $result")
        }
        return result
    }

AnchorTaskDispatcher


AnchorTaskDispatcher 这个类很重要,有向无环图的拓扑排序和多线程的依赖唤醒,都是借助这个核心类完成的。


它主要有几个成员变量


// 存储所有的任务
    private val list: MutableList<AnchorTask> = ArrayList()
    // 存储所有的任务,key 是 Class<out AnchorTask>,value 是 AnchorTask
    private val taskMap: MutableMap<Class<out AnchorTask>, AnchorTask> = HashMap()
    // 储存当前任务的子任务, key 是当前任务的 class,value 是 AnchorTask 的 list
    private val taskChildMap: MutableMap<Class<out AnchorTask>, ArrayList<Class<out AnchorTask>>?> =
        HashMap()
    // 拓扑排序之后的主线程任务
    private val mainList: MutableList<AnchorTask> = ArrayList()
    // 拓扑排序之后的子线程任务
    private val threadList: MutableList<AnchorTask> = ArrayList()
    //需要等待的任务总数,用于阻塞
    private lateinit var countDownLatch: CountDownLatch
    //需要等待的任务总数,用于CountDownLatch
    private val needWaitCount: AtomicInteger = AtomicInteger()


它有一个比较重要的方法 setNotifyChildren(anchorTask: AnchorTask) ,有一个方法参数 AnchorTask,它的作用是通知该任务的子任务,当前任务执行完毕,入度数减一。


/**
     *  通知 child countdown,当前的阻塞任务书也需要 countdown
     */
    fun setNotifyChildren(anchorTask: AnchorTask) {
        taskChildMap[anchorTask::class.java]?.forEach {
            taskMap[it]?.countdown()
        }
        if (anchorTask.needWait()) {
            countDownLatch.countDown()
        }
    }


接下来看一下 start 方法


fun start(): AnchorTaskDispatcher {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw AnchorTaskException("start method should be call on main thread")
        }
        startTime = System.currentTimeMillis()
        val sortResult = AnchorTaskUtils.getSortResult(list, taskMap, taskChildMap)
        LogUtils.i(TAG, "start: sortResult is $sortResult")
        sortResult.forEach {
            if (it.isRunOnMainThread()) {
                mainList.add(it)
            } else {
                threadList.add(it)
            }
        }
        countDownLatch = CountDownLatch(needWaitCount.get())
        val threadPoolExecutor =
            this.threadPoolExecutor ?: TaskExecutorManager.instance.cpuThreadPoolExecutor
        threadList.forEach {
            threadPoolExecutor.execute(AnchorTaskRunnable(this, anchorTask = it))
        }
        mainList.forEach {
            AnchorTaskRunnable(this, anchorTask = it).run()
        }
        return this
    }

它主要干几件事


  • 检测是否在主线程,不是抛出异常,这里为什么要检测在主线程呢?主要是构建有向无环图的过程,我们必须保证是线程安全的
  • 获取有向无环图的拓扑排序
  • 根据拓扑排序的排序结果,执行相应的任务。可以看到在执行任务的时候,我们使用 AnchorTaskRunnable 包裹起来


class AnchorTaskRunnable(
    private val anchorTaskDispatcher: AnchorTaskDispatcher,
    private val anchorTask: AnchorTask
) : Runnable {
    override fun run() {
        Process.setThreadPriority(anchorTask.priority())
        //  前置任务没有执行完毕的话,等待,执行完毕的话,往下走
        anchorTask.await()
        anchorTask.onStart()
        // 执行任务
        anchorTask.run()
        anchorTask.onFinish()
        // 通知子任务,当前任务执行完毕了,相应的计数器要减一。
        anchorTaskDispatcher.setNotifyChildren(anchorTask)
    }
}


AnchorTaskRunnable 有点类似于装饰者模式,多线程依赖的执行关系在这里都得到体现,只有几行代码


  1. 前置任务没有执行完毕的话,等待,执行完毕的话,往下走
  2. 执行任务
  3. 通知子任务,当前任务执行完毕了,相应的计数器(入度数)要减一。


总结


AnchorTask 的原理不复杂,本质是有向无环图与多线程知识的结合。


  1. 根据 BFS 构建出有向无环图,并得到它的拓扑排序
  2. 在多线程执行过程中,我们是通过任务的子任务关系和 CounDownLatch 确保先后执行关系的
  1. 前置任务没有执行完毕的话,等待,执行完毕的话,往下走
  2. 执行任务
  3. 通知子任务,当前任务执行完毕了,相应的计数器(入度数)要减一。


AnchorTask 源码已经更新到 github,AnchorTask


特别鸣谢


在实现这个开源框架的时候,借鉴了以下开源框架的思想。AppStartFaster 主要是通过 ClassName 找到相应的 Task,而阿里 alpha 是通过 taskName 找到相应的 Task,并且需要指定 ITaskCreator。两种方式各有优缺点,没有优劣之说,具体看使用场景。


android-startup

alpha

AppStartFaster


相关文章
|
6月前
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
89 10
|
5月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
92 20
Android经典面试题之图片Bitmap怎么做优化
|
4月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
93 4
|
3月前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
5月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
5月前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
99 2
|
6月前
|
Ubuntu Android开发
安卓系统调试与优化:(一)bootchart 的配置和使用
本文介绍了如何在安卓系统中配置和使用bootchart工具来分析系统启动时间,包括安装工具、设备端启用bootchart、PC端解析数据及分析结果的详细步骤。
303 0
安卓系统调试与优化:(一)bootchart 的配置和使用
|
5月前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
116 0
|
5月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
199 0
|
6月前
|
存储 缓存 前端开发
安卓开发中的自定义控件实现及优化策略
【8月更文挑战第31天】在安卓应用的界面设计中,自定义控件是提升用户体验和实现特定功能的关键。本文将引导你理解自定义控件的核心概念,并逐步展示如何创建一个简单的自定义控件,同时分享一些性能优化的技巧。无论你是初学者还是有一定经验的开发者,这篇文章都会让你对自定义控件有更深的认识和应用。

热门文章

最新文章

  • 1
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
  • 2
    Android历史版本与APK文件结构
  • 3
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 5
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 6
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
  • 7
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 8
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
  • 9
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 10
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 1
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    24
  • 2
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    31
  • 3
    Android历史版本与APK文件结构
    119
  • 4
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    27
  • 5
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    23
  • 6
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    55
  • 7
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    36
  • 8
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    71
  • 9
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    116
  • 10
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
    29