TheRouter 框架原理

news2024/12/23 12:13:04

TheRouter 框架入口方法

通过InnerTheRouterContentProvider 注册在AndroidManifest.xml中,在应用启动时初始化

    <application>
        <provider
            android:name="com.therouter.InnerTheRouterContentProvider"
            android:authorities="${applicationId}.therouter.TheRouteContentProvider"
            android:exported="false" />
    </application>

入口方法为TheRouter.init(applicationContext)

    /**
     * TheRouter初始化方法。内部流程:<br>
     * 同步流程:<br>
     *     1. 首先初始化FlowTask的内置事件,BEFORE_THEROUTER_INITIALIZATION,以及依赖这个Task的全部任务。
     *         这个事件的目的是在TheRouter的路由初始化前做某些操作,例如修改路由表、添加路由拦截器等……
     *     2. 初始化跨模块依赖表
     *     3. 初始化路由表
     * 异步流程:<br>
     *     1. 调用FlowTask的外部事件
     *     2. 添加 @Autowired 路由解析器
     */
    @JvmStatic
    fun init(context: Context?) {
        if (!inited) {
            ......
            addFlowTask(context, digraph)
            ......
        }
    }

在初始化前先调用addFlowTask(context, digraph)方法。

在跳转到对应方法实现处发现,该方法为空方法,没有具体业务逻辑。

@file:JvmName("TheRouterServiceProvideInjecter")

package a

import android.content.Context
import com.therouter.flow.Digraph

/**
 * Created by ZhangTao on 18/2/24.
 */
fun trojan() {}
fun autowiredInject(obj: Any?) {}
fun addFlowTask(context: Context?, digraph: Digraph) {}
fun initDefaultRouteMap() {}

在学习了相关博客后,发现这里暗藏了TheRouter设计的巧妙之处,也正是因为这个特点,TheRouter框架在没有使用反射机制情况下,可以动态注入逻辑。

我们在按照TheRouter框架添加了各种注解后,在编译期间,会自动生成一个合成类,将我们添加了@FlowTask(taskName = "xxx")注解的方法全部放到一个静态public static void addFlowTask(android.content.Context context, com.therouter.flow.Digraph digraph)方法中。

Debug 时,编译器生成的对应合成文件在目录

编译时生成对应方法如下

	public static void addFlowTask(android.content.Context context, com.therouter.flow.Digraph digraph) {
		digraph.addTask(new com.therouter.flow.Task(false, "base_init", "", new com.therouter.flow.FlowTaskRunnable() {
			@Override
			public void run() {
				com.ugreen.modulesbase.base.BaseInitTaskKt.initBase(context);
			}

			@Override
			public String log() {
				return "com.ugreen.modulesbase.base.BaseInitTaskKt.initBase(context);";
			}
		}));
		digraph.addTask(new com.therouter.flow.Task(false, "init_device_info", "base_init", new com.therouter.flow.FlowTaskRunnable() {
			@Override
			public void run() {
				com.ugreen.modulesbase.base.BaseInitTaskKt.initDeviceInfo(context);
			}

			@Override
			public String log() {
				return "com.ugreen.modulesbase.base.BaseInitTaskKt.initDeviceInfo(context);";
			}
		}));
		digraph.addTask(new com.therouter.flow.Task(false, "base_init_language", "", new com.therouter.flow.FlowTaskRunnable() {
			@Override
			public void run() {
				com.ugreen.modulesbase.base.BaseInitTaskKt.initLanguage(context);
			}

			@Override
			public String log() {
				return "com.ugreen.modulesbase.base.BaseInitTaskKt.initLanguage(context);";
			}
		}));
		digraph.addTask(new com.therouter.flow.Task(false, "base_init_smartRefresh", "", new com.therouter.flow.FlowTaskRunnable() {
			@Override
			public void run() {
				com.ugreen.modulesbase.base.BaseInitTaskKt.initSmartRefresh(context);
			}

			@Override
			public String log() {
				return "com.ugreen.modulesbase.base.BaseInitTaskKt.initSmartRefresh(context);";
			}
		}));
		digraph.addTask(new com.therouter.flow.Task(false, "base_init_titleBar", "", new com.therouter.flow.FlowTaskRunnable() {
			@Override
			public void run() {
				com.ugreen.modulesbase.base.BaseInitTaskKt.initTitleBar(context);
			}

			@Override
			public String log() {
				return "com.ugreen.modulesbase.base.BaseInitTaskKt.initTitleBar(context);";
			}
		}));
	}

这个方法名是不是很熟悉,只看它的方法名和入参数,这和前面提到的空方法一模一样。

是的,你没有看错,这里编译期间生成的方法就是要通过字节码插桩技术替换空方法实现。

是不是很巧妙。

那么接着看TheRouter的init方法,开始执行digraph.beforeSchedule()方法

    /**
     * 由于initSchedule执行比较耗时需要放到异步,而Before需要在路由表初始化之前执行,需要同步
     * 所以单独列出一个方法,检测dependsOn只有beforTheRouterInit的任务,提前执行
     */
    fun beforeSchedule() {
        val virtualFlowTask = getVirtualTask(TheRouterFlowTask.BEFORE_THEROUTER_INITIALIZATION)
        virtualTasks[TheRouterFlowTask.BEFORE_THEROUTER_INITIALIZATION] = virtualFlowTask
        virtualFlowTask.run()

        tasks.values.forEach {
            if (!it.async && it.dependencies.size == 1
                && it.dependencies.contains(TheRouterFlowTask.BEFORE_THEROUTER_INITIALIZATION)
            ) {
                // 此时一定在主线程,所以直接调用
                it.run()
            }
        }
    }

该方法同步执行,首先检测出dependsOn为beforTheRouterInit的任务,开始执行

查看源码发现,即使接入端未添加该dependsOn注解,这里也会默认创建一个virtualTask任务,这里不是很理解,后续补充。

beforeSchedule在执行完成virtualFlowTask后,开始执行依赖该task的任务,这里过滤条件要求,任务为同步执行且只依赖beforTheRouterInit的任务列表。

 接着看TheRouter的init方法,通过execute执行一个异步的代码片段

            execute {
                debug("init", "TheRouter.init() method do @FlowTask init")
                digraph.initSchedule()
                debug("init", "TheRouter.init() method do @FlowTask schedule")
                runInitFlowTask()
            }

initSchedule具体内容如下

    /**
     * 初始化方法
     */
    fun initSchedule() {
        for (task in tasks.values) {
            fillTodoList(task)
        }
        inited = true
        pendingTaskRunnableList.forEach {
            it.run()
        }
    }

这里重点方法如下

    private fun fillTodoList(root: Task) {
        if (!root.isDone()) {
            val dependsSet = getDepends(root)
            if (isNotEmpty(dependsSet)) {
                if (loopDependStack.contains(root)) {
                    throw IllegalArgumentException(
                        "TheRouter::Digraph::Cyclic dependency " + getLog(
                            loopDependStack,
                            root
                        )
                    )
                }
                loopDependStack.add(root)
                for (depend in dependsSet) {
                    fillTodoList(depend)
                }
                loopDependStack.remove(root)
                if (!todoList.contains(root)) {
                    todoList.add(root)
                }
            } else {
                if (!todoList.contains(root)) {
                    todoList.add(root)
                }
            }
        }
    }

通过递归调用将task依赖任务转化成一个todoList队列里面,这样就能保证被依赖的task放在队列的前面,依赖的放置在队列后面。关于这个队列具体如何作用后续继续说明

继续回到execute 代码片段里,继续执行runInitFlowTask方法

/**
 * 当TheRouter初始化时,执行的FlowTask
 */
fun runInitFlowTask() {
    TheRouter.runTask(TheRouterFlowTask.THEROUTER_INITIALIZATION)
}

接着继续查看Init方法,执行routerInject.asyncInitRouterInject(context)方法

该方法就是将路由相关的进行注入,例如配置的路由拦截器和自定义的拦截器

在asyncInitRouterInject方法中,判断mInterceptors为空时会从dex 文件中解析对应的拦截器,并实例化放入到mInterceptors对象里面,mInterceptors = TheRouterLinkedList<Interceptor>(16),最终也就是放入到该列表中,这里最多支持16个拦截器对象

    fun asyncInitRouterInject(context: Context?) {
        execute {
            trojan()
            if (mInterceptors.isEmpty()) {
                initServiceProvider(context)
            }
        }
    }

继续接着执行Init方法,开始执行asyncInitRouteMap()方法,该方法主要是用来加载路由配置

/**
 * 在异步初始化路由表
 */
fun asyncInitRouteMap() {
    execute {
        debug("RouteMap", "will be add route map from: initDefaultRouteMap()")
        initDefaultRouteMap()
        initedRouteMap = true
        if (initTask == null) {
            initRouteMap()
        } else {
            debug("RouteMap", "will be add route map from: RouterMapInitTask")
            initTask?.asyncInitRouteMap()
        }
        executeInMainThread {
            sendPendingNavigator()
        }
    }
}

这里为异步执行,首先执行一个initDefaultRouteMap的空方法,这里也应该是在编译期间通过字节码插桩替换,不过这里没有发现具体逻辑。

如何initTask为空,也就是前面空方法没有具体业务实现,那么这里就会从assets资源目录里面读取对应的路由配置,assets目录下var ROUTE_MAP_ASSETS_PATH = "therouter/routeMap.json" 在编译时会生成这样一个json文件

接着回来继续执行Init方法

            execute {
                context?.apply {
                    (applicationContext as Application).registerActivityLifecycleCallbacks(TheRouterLifecycleCallback)
                }
                parserList.addFirst(DefaultObjectParser())
                parserList.addFirst(DefaultServiceParser())
                parserList.addFirst(DefaultUrlParser())
                parserList.addFirst(DefaultIdParser())
            }

这里注册全局的Activity生命周期监听,注册各种解析器,在编译期间根据添加@Autowired注解进行代码生成

这里使用不是很理解,后面补充。

以上就是TheRouter Init方法整体初始化流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/982670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SSM的医院门诊预约挂号系统的设计与

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着医院管理的日益复…

【2023最新版】DataGrip安装及使用教程

目录 一、Jetbrains学生认证 二、DataGrip下载及安装 1. 使用Jetbrains toolbox a. 安装Jetbrains toolbox b. 安装DataGrip 2. 直接安装 a. 官网下载 b. 安装 三、DataGrip的使用 1. 配置默认设置 2. 安装插件 一、Jetbrains学生认证 JetBrains学生认证是JetBrain…

LeetCode 1004.最大连续1的个数

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 硬往题目介绍上边去想的话其实非常困难&#xff0c;如果换种方式思考就会简单许多。 若我们将思想转化为&#xff0c;找出最长的子串(里面含有的0的数量最大为k)&#xff0c;然后返…

WebDAV之π-Disk派盘 + 天天

天天是一款非常简单实用的每日打卡助手,旨在帮助用户制定和跟踪每日或每周的打卡任务,同时提供了自由的选择空间以适应用户的不同需求和偏好。 以下是天天应用的一些主要特性和功能: 1. 优美简洁的用户界面:没有广告,无需网络,可以离线使用,应用大小极小,仅需不到1MB的…

【网络教程】GitHub搜索技巧大揭秘

文章目录 1. 使用关键词优化搜索2. 结合布尔运算符3. 利用星号扩展搜索4. 高级搜索语法5. 按照星标数量搜索6. 使用文件类型搜索7. 在特定分支上搜索8. 使用文件名搜索9. 搜索贡献者10. 使用标签筛选仓库在开发过程中,我们经常需要在GitHub上查找代码、库或相关文档。本文将介…

SNMP的监控

SNMP的监控 一、SNMP 介绍1.1 什么是SNMP1.2 SNMP的组件1.2.1 网络管理系统 NMS&#xff08;Network Management System&#xff09;1.2.2 代理进程&#xff08;Agent&#xff09;1.2.3 被管对象&#xff08;Managed Object&#xff09;1.2.4 管理信息库MIB&#xff08;Managem…

Vue3【Provide/Inject】

前言 自从使用了Provide/Inject代码的组织方式更加灵活了&#xff0c;但是这个灵活性的增加伴随着代码容错性的降低。我相信只要是真的在项目中引入Provide/Inject的同学&#xff0c;一定一定有过或者正在经历下面的状况&#xff1a; 注入名&#xff08;Injection key&#x…

第二证券:经济利好,联储“利空”

当地时刻9月6日&#xff0c;美股接连9月低迷局面&#xff0c;三大指数低开低走团体收跌。 宏观经济数据方面&#xff0c;美国8月ISM非制造业PMI意外升至半年新高&#xff0c;接连八个月扩张&#xff0c;工作指数升至2021年11月来最高&#xff0c;新订单指数也创六个月新高&…

NATAPP内网穿透之接口测试

下载 工具下载 下载对应系统版本的软件&#xff1a; NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 认证文件下载 使用本地配置文件config.ini - NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 解压完之后&#xff0c;将配置文件放到解压后的文件夹中 申请免费隧…

[Linux]文件系统

[Linux]文件系统 文件系统是操作系统的一部分&#xff0c;负责组织、存储和管理存储在外部设备上的文件和目录&#xff0c;也就是操作系统管理外设中的文件的策略。本文讲解的是Ext2文件系统。Linux操作系统使用的就是Ext系列的文件系统。 文章目录 [Linux]文件系统了解磁盘结构…

集美大学计算机改考408!福建省全面改考,仅剩一个自命题院校

9月5日&#xff0c;集美大学发布通知&#xff0c;0835软件工程、0854电子信息2024考试科目发生变更&#xff01;由822数据结构调整为408计算机学科专业基础 https://zsb.jmu.edu.cn/info/1532/4701.htm 直接由一门改为考四门&#xff0c;难度升级不小。 目前福建省内计算机考…

23年下半年软考系统集成(中项)报名已开始!

2023下半年软考考试报名今日播报如下&#xff1a; 【新增报名地区】宁夏 【正在报名地区】辽宁、江西、新疆、新疆兵团、内蒙古、河北、西藏、山东、云南、河南、河北、西藏、山东、云南、河南、江苏、黑龙江、大连、广东、海南、四川、宁夏 为什么现在报名系统集成项目管理工程…

redis 配置与优化

目录 一、关系数据库和非关系型数据库 二、关系型数据库和非关系型数据库区别 三、非关系型数据库产生背景 四、redis 1、概念 2、redis的优点 3、redis为什么这么快 五、redis安装与配置 一、关系数据库和非关系型数据库 关系型数据库&#xff1a;关系型数据库是一个结…

防雷检测的作用和意义

防雷检测是指对建筑物的雷电防护装置进行检测的活动&#xff0c;其目的是确定防雷装置是否符合国家标准或行业标准、设计文件的要求&#xff0c;查出事故隐患&#xff0c;防止或减少雷电灾害事故的发生。防雷检测是防雷工程的重要组成部分&#xff0c;也是保障建筑物安全运行的…

作为产品经理,有必要考PMP或者NPDP么?

产品经理的核心竞争力是什么? 三点&#xff1a;知识、能力和决策 懂得越多&#xff0c;能力越强&#xff0c;决策越正确&#xff0c;核心竞争力越强。一般来说&#xff0c;看的越多&#xff0c;做的越多&#xff0c;实践出经验才是王道&#xff0c;但是&#xff0c;总有看不…

postgresql数据库定时备份到远程数据库

postgresql数据库定时备份到远程数据库 1.老规矩&#xff0c;服务器目录结构: conf目录无内容 profile: # /etc/profile: system-wide .profile file for the Bourne shell (sh(1)) # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).if [ "id -u"…

uView实现全屏选项卡

// 直接复制粘贴即可使用 <template><view><view class"tabsBox"><u-tabs-swiper ref"uTabs" :list"list":current"current"change"tabsChange":is-scroll"false"></u-tabs-swiper&g…

录音工具哪个好用?亲身测评,推荐这几个

“电脑录音用什么工具呀&#xff0c;前几天录制的视频声音没有录进去&#xff0c;现在需要重新补录声音&#xff0c;但是找不到合适的录音工具&#xff0c;就想问问大家&#xff0c;有没有好用的录音工具推荐呀&#xff1f;” 现如今&#xff0c;录音工具在我们的生活和工作中…

ant-table组件表格数据做合计行,并固定在表格底部

需求描述 某些时候&#xff0c;我们表格展示数值型数据的时候&#xff0c;可能会想做一个合计处理&#xff0c;这样能直观的了解到当前列的总计数据信息。 遍观Table组件&#xff0c;官方是没有提供这个功能的&#xff0c;这就需要我们自己处理了 需求分析 根据表格的特性&a…

链表逆置题

本题要求实现一个函数&#xff0c;将给定单向链表逆置&#xff0c;即表头置为表尾&#xff0c;表尾置为表头。链表结点定义如下&#xff1a; struct ListNode { int data; struct ListNode *next; }; 函数接口定义&#xff1a; struct ListNode *reverse( struct ListNode *hea…