Android 启动优化框架

介绍

AndroidStartup
本框架目的为提供 Android APP 在启动时的启动流程优化,统一管理和配置各初始化任务按照开发时所规划的依赖顺序和配置执行,可作为组件化的一个公共组件。

前言

我们所开发的 Android 项目,在APP启动的时候往往需要提前初始化一些业务模块、第三方SDK等,简单的写法是在 项目自定义的 ApplicationonCreate() 方法中根据可能的先后顺序一股脑的写上初始化代码,这样会让各模块的初始化工作太过于集中执行,可能引起主线程的耗时,因而非常有必要(尤其对于中大型项目来说)将项目中需要初始化的各功能模块及第三方SDK等的初始化任务按照一定的先后依赖顺序、有条理的 按照分任务的方式进行设计、规划。

使用

class LocalApp : Application() {
    
    override fun onCreate() {
        super.onCreate()
        val gloabalExecutor: Executor = ThreadPoolExecutor(
            1,
            3,
            1,
            TimeUnit.MINUTES,
            SynchronousQueue()
        )
        StartupTasksManager.getTasksManager()
            .withContext(this) //设置上下文 Context
            .withTaskExecutor(gloabalExecutor) //配置启动任务的全局 Executor(线程池)
            .withTask(Task1())
            .withTask(Task5())
            .withTask(Task3())
            .withTask(Task4())
            .withTask(Task2())
            .withTask(BuglyInitTask())
            .startUp() //开始启动任务
    }
}

依赖

项目的根目录下的 build.gradle 中添加 maven仓库(由于Jcenter已废,基本都有maven的默认仓库)

buildscript {
    
    repositories {
        google()
        mavenCentral() //增加 maven仓库
    }
}

再在 app 模块目录下的 build.gradle 文件中增加对本组件库的依赖

dependencies {
    implementation 'io.github.feer921:AndroidStartup:1.0'
}

AS提示需要 同步工程,同步成功后,即依赖成功。

核心类、APIs

初始化任务被定义为 启动任务接口,所有的启动初始化任务皆为该接口的子类:

interface IStartupTask<T> : ITaskStateIndicate{

    /**
     * 执行任务
     * @return [T] 执行了启动任务可能的 返回数据
     */
    fun doStartupTask(context: Context): T

    /**
     * 本启动任务所依赖的其他 task的数量,本任务所依赖的其他任务数量小于1时(即没有依赖其他任务)则优先执行
     */
    fun getDependentTaskCount(): Int


    /**
     * 本启动任务所依赖的其他启动任务的 Class数据集
     * 作用为:在本启动任务执行前,需要在所依赖其他任务执行完后再执行
     */
    fun dependentTask(): List<Class<out IStartupTask<*>>>?


}

其中类范型<T> 表示启动任务的执行时 fun doStartupTask(context: Context): T 可能需要返回执行结果,而 ITaskStateIndicate 接口意义为 启动任务的状态指示,为了让各任务子类对外标示优先级、运行需求的线程是否为主线程、提供自定义的任务执行器 Executor(线程池) 等:

interface ITaskStateIndicate {
    /**
     * 标识任务的执行优先级
     * def = Process.THREAD_PRIORITY_DEFAULT == 0
     */
    fun indicateTaskPriority(): Int = android.os.Process.THREAD_PRIORITY_DEFAULT

    /**
     * 标识启动任务是否依赖主线程的执行
     */
    fun isDependonMainThread(): Boolean

    /**
     * 启动任务所依赖的任务 执行器 [Executor]
     */
    fun dependonTaskExecutor(): Executor? = null

    /**
     * 标记当前启动任务先等一等
     */
    fun letHoldOn()

    /**
     * 当当前任务的上游任务完成时通知当前任务将可以执行、动作
     */
    fun letWillAction()
}

进而为了方便开发者编写自定义的启动任务类,框架有必要默认提供一个抽象的启动任务类,以提供通用的接口实现

abstract class AStartupTask<T>: IStartupTask<T> {
    protected val mTag: String by lazy(LazyThreadSafetyMode.NONE) {
        javaClass.simpleName
    }

    /**
     * 本启动任务所依赖的上游任务在完成后的 CountDown,当所有依赖的上游任务都完成后才通知本任务执行
     */
    private val mDependentTaskCountDown by lazy(LazyThreadSafetyMode.NONE){
        CountDownLatch(getDependentTaskCount())
    }

    /**
     * 本启动任务所依赖的其他启动任务的数量
     * 之所以增加这个属性,避免在调用[getDependentTaskCount]时每次都去调用[dependentTask]
     * 重复生成 List<>
     * def = -1,子类可以赋值,本父类默认会调用[dependentTask] 来查询一次
     */
    protected var mDependentTaskCount = -1

    /**
     * 本启动任务所依赖的其他 task的数量,本任务所依赖的其他任务数量小于1时(即没有依赖其他任务)则优先执行
     */
    override fun getDependentTaskCount(): Int {
        if (mDependentTaskCount == -1) {
            mDependentTaskCount = dependentTask()?.size ?: 0
        }
        return mDependentTaskCount
    }

    /**
     * 本启动任务所依赖的其他启动任务的 Class数据集
     * 作用为:在本启动任务执行前,需要在所依赖其他任务执行完后再执行
     */
    override fun dependentTask(): List<Class<out IStartupTask<*>>>? {
        return null
    }

    //----------------
    /**
     * 标记当前启动任务先等一等
     */
    override fun letHoldOn() {
        try {
            mDependentTaskCountDown.await()
        }catch (ex: InterruptedException){
            ex.printStackTrace()
        }
    }

    /**
     * 当当前任务的上游任务完成时通知当前任务将可以执行、动作
     */
    override fun letWillAction() {
        mDependentTaskCountDown.countDown()
    }
    //----------------

    override fun toString(): String {
        return "task: $mTag,mDependentTaskCount = $mDependentTaskCount"
    }
}

开发使用本框架基本上只需要 继承 以上该抽象启动类。

编写自定义的启动任务类

简单示例,项目中需要对 Bugly 此类第三方SDK的初始化,我们取名为 BuglyInitTask:

class BuglyInitTask: AStartupTask<String>() {
    /**
     * 执行任务
     * @return [T] 执行了启动任务可能的 返回数据
     */
    override fun doStartupTask(context: Context): String {
        // Bugly.init()
        return "bugly sdk init finish"
    }

    /**
     * 标识启动任务是否依赖主线程的执行
     */
    override fun isDependonMainThread(): Boolean {
        return true
    }

    override fun dependentTask(): List<Class<out IStartupTask<*>>>? {
        return listOf(Task1::class.java)
    }
}

当然如果所自定义的启动任务还有其他的配置需求,可以查看其父类以重写实现。

单例模式启动任务组(简单)

大多数情况下,我们的项目中把1个~N个启动初始化任务集只规划成一组即可,由框架中 StartupTasksManager 懒汉式单例进行添加与管理:

// API有节选: 
/** <P>DESC:
 * 启动任务管理者(单例)
 * </p>
 * ******************(^_^)***********************
 */
class StartupTasksManager private constructor(): ITaskListener{
  companion object Builder{
        private val mTasksManager: StartupTasksManager by lazy(LazyThreadSafetyMode.NONE){
            StartupTasksManager()
        }

        /**
         * 添加启动任务
         */
        fun withTask(startupTask: IStartupTask<*>): StartupTasksManager {
            return getTasksManager().withTask(startupTask)
        }

        /**
         * 获取 启动任务管理器
         */
        fun getTasksManager(): StartupTasksManager = mTasksManager
  }
  
  /**
     * 添加启动任务
     */
    @MainThread
    fun withTask(startupTask: IStartupTask<*>): StartupTasksManager {
        mAddedTasksList.add(startupTask)
        return this
    }

    /**
     * 开始启动、执行各启动任务
     */
    @MainThread
    fun startUp(){
        if (mAppContext == null || mAddedTasksList.isEmpty()) {
            return
        }
        sortStartupTasks(mAddedTasksList)
        mAddedTasksList.clear()//这个数据集不需要了
        mSortedTasksList.forEach { task ->
            val taskRunner = TaskRunner(appContext = mAppContext!!, task, this)
            if (task.isDependonMainThread()){
                taskRunner.run()
            } else {
                task.dependonTaskExecutor() ?: mTaskExecutor?.execute(taskRunner)
            }
        }
    }
}

再开发编写完各自定义的启动任务后,在相应的位置(一般为 Applicaton 初始化的位置) 按照前方的 API 进行配置、启动:

class LocalApp : Application() {
    
    override fun onCreate() {
        super.onCreate()
        val gloabalExecutor: Executor = ThreadPoolExecutor(
            1,
            3,
            1,
            TimeUnit.MINUTES,
            SynchronousQueue()
        )
        StartupTasksManager.getTasksManager()
            .withContext(this) //设置上下文 Context
            .withTaskExecutor(gloabalExecutor) //配置启动任务的全局 Executor(线程池)
            .withTask(Task1())
            .withTask(Task5())
            .withTask(Task3())
            .withTask(Task4())
            .withTask(Task2())
            .withTask(BuglyInitTask())
            .startUp() //开始启动任务
    }
}

分组模式启动任务组(启动任务再分组)

这种模式即对应的项目中确实可能存在比较复杂的启动任务需求,比如我们需要把 Task1,Task2,Task3 看成一组启动任务集,另把 Task4,Task5,BuglyInitTask 看成另外一组任务集,然后再分别启动,可能这些分组的任务集之间又存在依赖顺序关系(框架中暂时还未实现),这个需求目前被 StartupTasksOrganizer 类看成任务集的组织者来实现:

//API代码有节选
/** <P>DESC:
 * 启动任务组织者(每个组织者组织一组启动任务)
 * </p>
 * ******************(^_^)***********************
 */
class StartupTasksOrganizer(private val mAppContext: Context?,private val mAddedTaskList: List<IStartupTask<*>>):ITaskListener {

    private var mTasksRelationMapper: StartupTasksRelationMapper? = null


    /**
     * 开始启动一组 启动任务
     */
    @MainThread
    fun startUp(){
        if (mAppContext == null || mAddedTaskList.isEmpty()) {
            return
        }
        //排序
        mTasksRelationMapper = StartupTasksManager.sortStartupTasksAndResult(mAddedTaskList).apply {
            sortedTasks.forEach { task ->
                val taskRunner = TaskRunner(mAppContext,task,this@StartupTasksOrganizer)
                if (task.isDependonMainThread()){
                    taskRunner.run()
                } else {
                    task.dependonTaskExecutor() ?: theSameTaskExecutor()?.execute(taskRunner)
                }
            }
        }
    }
  
  /**
     * 一组启动任务的构建者
     */
    companion object class TasksBuilder{
        private val mAddedTasks by lazy(LazyThreadSafetyMode.NONE){
            mutableListOf<IStartupTask<*>>()
        }

        private var mContext: Context? = null

        fun withTask(task: IStartupTask<*>): TasksBuilder{
            mAddedTasks.add(task)
            return this
        }

        fun build(context: Context): TasksBuilder {
            this.mContext = context
            return this
        }

        fun startUp(){
            StartupTasksOrganizer(mContext,mAddedTasks).startUp()
        }
    }
}

可见由 构建者模式的 TasksBuilder 来进行构建,则分组模式代码示例:

                //分组模式下的代码示例:
        //第一组:
        StartupTasksOrganizer.TasksBuilder()
            .withTask(Task1())
            .withTask(Task2())
            .withTask(Task3())
            .build(this)
            .startUp()
        //第二组:
        StartupTasksOrganizer.TasksBuilder()
            .withTask(Task4())
            .withTask(Task5())
            .withTask(BuglyInitTask())
            .build(this)
            .startUp()

至此,本启动任务框架基本上介绍完了,具体可以走查源码(有着较良好的代码注释),感谢提供任何建议,为谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容