Android手写自己的路由SDK

实现自己的路由框架

​ 在较大型的Android app中常会用到组件化技术,针对不同的业务/基础功能对模块进行划分,从上到下为壳工程、业务模块、基础模块。其中业务模块依赖基础模块,壳工程依赖业务模块。同级的横向模块(比如多个业务模块)因为不能相互依赖,怎样实现它们之间的路由跳转呢?

​ 我尝试使用kotlin实现一下自己的路由框架,由简到繁、由浅入深。刚开始只求有功能,不求完美,一步步最终优化到比较完善的样子。

1.菜鸟版

在这里插入图片描述

​ 工程中只包含上图中的5个模块,其中main、businessa、businessb、routersdk均为Android Library,只有app是可运行的application。

​ app作为壳工程依赖着剩下的4个模块,main、businessa、businessb作为业务模块依赖着routersdk这个基础模块。

​ 此时依据模块之间的依赖关系,想要实现路由其实只需要在基础模块routersdk中创建一个Router类维护一个映射表并实现两个关键方法。

​ 一个方法register()用来注册路由跳转的键值对,键为path字符串,value为跳转的XXXActivity的class即可。

​ 另一个方法jumpTo()用来具体跳转,实现的时候传入对应的路由path参数,当path在映射表中时直接调用Intent的startActivity()方法完成跳转即可。

‘’

package com.lllddd.routersdkimport android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast/*** author: lllddd* created on: 2024/5/2 14:34* description:路由类*/
object Router {private val routerMap: MutableMap<String, Class<out Activity>> = mutableMapOf()fun register(key: String, value: Class<out Activity>) {routerMap[key] = value}fun jumpTo(activity: Activity, path: String, params: Bundle? = null) {if (!routerMap.containsKey(path)) {Toast.makeText(activity, "找不到路由目的页面!!!", Toast.LENGTH_LONG).show()return}val destinationActivity = routerMap[path]val intent = Intent(activity, destinationActivity)if (params != null) {intent.putExtras(params)}activity.startActivity(intent)}
}

​ 接着在Application的子类MyRouterApp中调用Router的注册方法,将3个业务模块的页面路由分别注册进Router中的路由表,那么路由的注册就已完成。

​ ‘’

package com.lllddd.myrouter.appimport android.app.Application
import com.lllddd.businessa.BusinessAMainActivity
import com.lllddd.businessb.BusinessBMainActivity
import com.lllddd.main.MainActivity
import com.lllddd.routersdk.Router/*** author: lllddd* created on: 2024/5/2 15:06* description:*/
class MyRouterApp : Application() {override fun onCreate() {super.onCreate()Router.register("businessa/main", BusinessAMainActivity::class.java)Router.register("businessb/main", BusinessBMainActivity::class.java)Router.register("main/main", MainActivity::class.java)}
}

​ 上方我们注册了3条路由关系。businessa/main对应BusinessAMainActivity,businessb/main对应BusinessBMainActivity,app/main对应MainActivity。

​ 此时假如要想在app模块中的MainActivity页面路由到businessa模块的BusinessAMainActivity页面或businessb模块的BusinessBMainActivity页面,只需要如下这样写。

​ ‘’

package com.lllddd.mainimport android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.lllddd.routersdk.Routerclass MainActivity : AppCompatActivity() {private lateinit var mBtnJumpA: Buttonprivate lateinit var mBtnJumpB: Buttonoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)initWidgets()}private fun initWidgets() {mBtnJumpA = findViewById(R.id.btn_jump_a)mBtnJumpA.setOnClickListener {val bundle = Bundle()bundle.putString("param", "好好学习")Router.jumpTo(this, "businessa/main", bundle)}mBtnJumpB = findViewById(R.id.btn_jump_b)mBtnJumpB.setOnClickListener {val bundle = Bundle()bundle.putString("param", "天天向上")Router.jumpTo(this, "businessb/main", bundle)}}
}

​ 此时我们只需要将path传给Router.jumpTo()作为参数就能正确跳转到同级别的业务模块中。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.进阶版

​ 菜鸟版方式很容易就让我实现了一个菜鸟版的路由框架。但是它存在一些很明显的问题,首先就是注册关系必须要在MyRouterApp(Application)类中去维护,那么在模块众多多人协作开发时完全没有解耦,造成了MyRouterApp难以维护,模块职责不清的问题。其次,app模块中实际上没有任何页面,只是一个壳工程,但是它也要依赖routersdk,这样的情况也不合理。

​ 我就在想能不能让路由关系注册这样的操作分散在各自的业务模块中,这样就能很好地解决上面两个问题。

​ 很自然的想到在routersdk模块中定义一个接口用来约束装载路由的方法。

‘’

package com.lllddd.routersdkimport android.app.Activity/*** author: lllddd* created on: 2024/5/2 20:12* description:各个业务模块装载路由信息的接口*/
interface ILoadRouters {/*** 装载路由信息** @param loadRouters 路由表*/fun loadRouters(routerMap: MutableMap<String, Class<out Activity>>)
}

​ 之后在每个业务模块中新建一个路由类实现该接口。

​ main模块中的实现类如下。

‘’

package com.lllddd.main.routerimport android.app.Activity
import com.lllddd.main.MainActivity
import com.lllddd.routersdk.ILoadRouters/*** author: lllddd* created on: 2024/5/2 20:21* description:业务main模块的路由装载类*/
class MainRouter : ILoadRouters {override fun loadRouters(routerMap: MutableMap<String, Class<out Activity>>) {routerMap["main/main"] = MainActivity::class.java}
}

​ businessa模块中的实现类如下。

‘’

package com.lllddd.businessa.routerimport android.app.Activity
import com.lllddd.businessa.BusinessAMainActivity
import com.lllddd.routersdk.ILoadRouters/*** author: lllddd* created on: 2024/5/2 20:16* description:业务模块A的路由装载类*/
class BusinessARouter : ILoadRouters {override fun loadRouters(routerMap: MutableMap<String, Class<out Activity>>) {routerMap["/businessa/main"] = BusinessAMainActivity::class.java}
}

​ businessb模块中的实现类如下。

‘’

package com.lllddd.businessb.routerimport android.app.Activity
import com.lllddd.businessb.BusinessBMainActivity
import com.lllddd.routersdk.ILoadRouters/*** author: lllddd* created on: 2024/5/2 20:19* description:业务模块B的路由装载类*/
class BusinessBRouter : ILoadRouters {override fun loadRouters(routerMap: MutableMap<String, Class<out Activity>>) {routerMap["businessb/main"] = BusinessBMainActivity::class.java}
}

​ 这样一来,我们只需要在Router类中增加一个init()方法,在该方法中调用各模块的loadRouters()方法即可。

‘’

package com.lllddd.routersdkimport android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast/*** author: lllddd* created on: 2024/5/2 14:34* description:路由类*/
object Router {private val routerMap: MutableMap<String, Class<out Activity>> = mutableMapOf()fun init() {ABusinessRouter().loadRouters(routerMap)BBusinessRouter().loadRouters(routerMap)MainRouter().loadRouters(routerMap)}//    fun register(key: String, value: Class<out Activity>) {
//        routerMap[key] = value
//    }fun jumpTo(activity: Activity, path: String, params: Bundle? = null) {if (!routerMap.containsKey(path)) {Toast.makeText(activity, "找不到路由目的页面!!!", Toast.LENGTH_LONG).show()return}val destinationActivity = routerMap[path]val intent = Intent(activity, destinationActivity)if (params != null) {intent.putExtras(params)}activity.startActivity(intent)}
}

​ 此时MyRouterApp中只需要直接调用Router的init()初始化方法即可。

‘’

package com.lllddd.myrouter.appimport android.app.Application
import com.lllddd.routersdk.Router/*** author: lllddd* created on: 2024/5/2 15:06* description:*/
class MyRouterApp : Application() {override fun onCreate() {super.onCreate()// 初始化路由SDKRouter.init()}
}

​ 思路虽然没错,但是Router中的init()方法却是飘红的,这里作为基础模块,怎么可能拿到业务模块的类引用从而实例化出ABusiniessRouter、BBusinissRouter、MainRouter呢?

​ 所以当前的init()方法一定是行不通的,既然基础模块不能直接使用上层业务模块中的类,那我们只能重新想办法。

​ 此处突然想到了一个好办法,那就是反射,我在这里反射出来ABusiniessRouter、BBusinissRouter、MainRouter这3个对象不就好了。说干就干,改造后的init()代码是这样的。

‘’

fun init() {
//        ABusinessRouter().loadRouters(routerMap)
//        BBusinessRouter().loadRouters(routerMap)
//        MainRouter().loadRouters(routerMap)val aBizClazz = Class.forName("com.lllddd.businessa.router.BusinessARouter")val aBizRouter = aBizClazz.newInstance()val methodABiz = aBizClazz.methods.find { it.name == "loadRouters" }methodABiz?.invoke(aBizRouter, routerMap)val bBizClazz = Class.forName("com.lllddd.businessb.router.BusinessBRouter")val bBizRouter = bBizClazz.newInstance()val methodBBiz = bBizClazz.methods.find { it.name == "loadRouters" }methodBBiz?.invoke(bBizRouter, routerMap)val mainClazz = Class.forName("com.lllddd.main.router.MainRouter")val mainRouter = mainClazz.newInstance()val methodMain = mainClazz.methods.find { it.name == "loadRouters" }methodMain?.invoke(mainRouter, routerMap)}

​ 看起来确实不再报错了,demo也正常运行。但是造成的问题是每次增减一个业务模块,就需要在基础模块routersdk的Router类的init()方法中增删代码,而且对应业务模块的路由类是通过反射在此逐一实例化的,用起来也不方便。

​ 那么有没有更好的办法呢?

​ 当然是有,这里需要结合类加载、PMS、反射的知识来综合处理。

​ 我只要想办法遍历整个应用apk,想办法找到满足规则com.lllddd.xxx.router.xxx.kt的kotlin文件完整包路径即可。

​ 此时就需要借助PMS拿到我们的apk。当应用安装后运行时,对应的apk文件其实是在下面这个路径中的

​ /data/app/com.lllddd.myrouter-m-SApQoUtVytou1_nl1aUA==/base.apk

​ 之后可以利用类加载技术中的DexFile匹配正则规则来遍历apk找到符合规则的类路径,即

​ com.lllddd.businessa.router.BusinessARouter
​ com.lllddd.businessb.router.BusinessBRouter
​ com.lllddd.main.router.MainRouter

​ 之后还是在Router的init()方法中利用反射调用每个XXXRouter的loadRouters()方法就能实现路由注册。

​ 我将相关的关键操作封装进ClassHelper类。

‘’

package com.lllddd.routersdkimport android.app.Application
import android.content.Context
import dalvik.system.DexFile
import java.util.regex.Pattern/*** author: lllddd* created on: 2024/5/2 21:43* description:类帮助者*/
class ClassHelper {companion object {/*** 获取当前的apk文件** @param context 应用上下文* @return apk文件路径列表*/private fun getSourcePaths(context: Context): List<String> {val applicationInfo = context.applicationInfoval pathList = mutableListOf<String>()// /data/app/com.lllddd.myrouter-m-SApQoUtVytou1_nl1aUA==/base.apkpathList.add(applicationInfo.sourceDir)if (applicationInfo.splitSourceDirs != null) {val array = applicationInfo.splitSourceDirsfor (ele in array) {pathList.add(ele)}}return pathList}/*** 根据Router类所在包名的正则规则,拿到所有Router的完整包名路径,以便后期反射调用** @param context 应用上下文* @param packageRegex Router类所在包名的正则规则* @return 所有Router的完整包名路径*/fun getFileNameByPackageName(context: Application, packageRegex: String): Set<String> {val set = mutableSetOf<String>()val pathList = getSourcePaths(context)val pattern = Pattern.compile(packageRegex)for (path in pathList) {var dexFile: DexFile? = nulltry {dexFile = DexFile(path)val entries = dexFile.entries()if (entries != null) {while (entries.hasMoreElements()) {val className = entries.nextElement()val matcher = pattern.matcher(className)if (matcher.find()) {set.add(className)}}}} finally {dexFile?.close()}}return set}}
}

​ 之后Router中的init()方法直接调用ClassHelper中的方法并遍历反射即可。

‘’

 fun init(application: Application) {// 方案1:飘红
//        ABusinessRouter().loadRouters(routerMap)
//        BBusinessRouter().loadRouters(routerMap)
//        MainRouter().loadRouters(routerMap)// 方案2:并不优雅
//        val aBizClazz = Class.forName("com.lllddd.businessa.router.BusinessARouter")
//        val aBizRouter = aBizClazz.newInstance()
//        val methodABiz = aBizClazz.methods.find { it.name == "loadRouters" }
//        methodABiz?.invoke(aBizRouter, routerMap)
//
//        val bBizClazz = Class.forName("com.lllddd.businessb.router.BusinessBRouter")
//        val bBizRouter = bBizClazz.newInstance()
//        val methodBBiz = bBizClazz.methods.find { it.name == "loadRouters" }
//        methodBBiz?.invoke(bBizRouter, routerMap)
//
//        val mainClazz = Class.forName("com.lllddd.main.router.MainRouter")
//        val mainRouter = mainClazz.newInstance()
//        val methodMain = mainClazz.methods.find { it.name == "loadRouters" }
//        methodMain?.invoke(mainRouter, routerMap)// 方案3:自动扫包val set = ClassHelper.getFileNameByPackageName(application,"com.lllddd.[a-zA-Z0-9]+\\.router\\.[a-zA-Z0-9]+")for (fileName in set) {val clazz = Class.forName(fileName)val router = clazz.newInstance()val method = clazz.methods.find { it.name == "loadRouters" }method?.invoke(router, routerMap)}}

3.完善版

经过进阶版的优化,我的路由框架已经能比较好的承接组件化后的路由需求了。但是在实际使用中会发现,每个模块都需要手动单独维护一个自己模块的XXXRouter类来进行路由的注册与管理,能不能再进一步优化,让业务开发只关注业务而不需要手动创建模块维度的路由类呢,那确实也有这样的方法。

​ 我们很容易想到注解。能否自定义一个注解,然后直接在XXXActivity的类上注解path路径,在应用冷启动运行时扫描带有注解的XXXActivity然后加到路由映射表中呢?当然是可以的,只需要简单改改即可。

​ 直接在routersdk中自定义一个注解RouterAnno。

``

package com.lllddd.routersdk/*** author: lllddd* created on: 2024/5/3 12:55* description:路由注解*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class RouterAnno(val path: String)

​ 其中的path就代表了,路由映射表中的键。

​ 使用时我们只需要将注解加到对应的页面类上即可。例如给主模块的MainActivity加一下注解。

‘’

@RouterAnno(path = "main/main")
class MainActivity : AppCompatActivity() {...
}

​ 在routersdk的ClassUtils新增一个根据包名查找Activity名的方法getActivityNamesByPackageName()。

​ ‘’

package com.lllddd.routersdkimport android.app.Application
import android.content.Context
import dalvik.system.DexFile/*** author: lllddd* created on: 2024/5/2 21:43* description:类帮助者*/
class ClassHelper {companion object {/*** 获取当前的apk文件** @param context 应用上下文* @return apk文件路径列表*/private fun getSourcePaths(context: Context): List<String> {val applicationInfo = context.applicationInfoval pathList = mutableListOf<String>()// /data/app/com.lllddd.myrouter-m-SApQoUtVytou1_nl1aUA==/base.apkpathList.add(applicationInfo.sourceDir)if (applicationInfo.splitSourceDirs != null) {val array = applicationInfo.splitSourceDirsif (array != null) {for (ele in array) {pathList.add(ele)}}}return pathList}/*** 获取所有的Activity完整类名** @param context 上下文* @return 所有的Activity完整类名集合*/fun getActivityNamesByPackageName(context: Application): Set<String> {val set = mutableSetOf<String>()val pathList = getSourcePaths(context)lateinit var dexFile: DexFiletry {for (path in pathList) {dexFile = DexFile(path)val entries = dexFile.entries()while (entries.hasMoreElements()) {val className = entries.nextElement()// 找到符合Activity命名规则的各类Activity,这种判断方法比较原始if (className.startsWith("com.lllddd.")&& className.endsWith("Activity")) {set.add(className)}}}} finally {dexFile.close()}return set}}
}

​ 思路就是获取base.apk中所有的前缀是com.lllddd.,结尾为Activity的类名称。

​ 然后在Router类的init()方法中利用反射解析注解RouterAnno,获取到path的值,然后注册进路由表即可。

​ ‘’

package com.lllddd.routersdkimport android.app.Activity
import android.app.Application
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast/*** author: lllddd* created on: 2024/5/2 14:34* description:路由类*/
object Router {private val routerMap: MutableMap<String, Class<out Activity>> = mutableMapOf()fun init(application: Application) {val startTime = System.currentTimeMillis()val set = ClassHelper.getActivityNamesByPackageName(application)for (fileName in set) {val clazz = Class.forName(fileName)if (clazz.isAnnotationPresent(RouterAnno::class.java)&& Activity::class.java.isAssignableFrom(clazz)) {val anno = clazz.getAnnotation(RouterAnno::class.java) ?: continueval key = anno.pathregister(key, clazz as Class<out Activity>)}}val endTime = System.currentTimeMillis()val duration = endTime - startTimeLog.d("Router", "duration >>>>>  $duration ms")}private fun register(key: String, value: Class<out Activity>) {routerMap[key] = value}fun jumpTo(activity: Activity, path: String, params: Bundle? = null) {if (!routerMap.containsKey(path)) {Toast.makeText(activity, "找不到路由目的页面!!!", Toast.LENGTH_LONG).show()return}val destinationActivity = routerMap[path]val intent = Intent(activity, destinationActivity)if (params != null) {intent.putExtras(params)}activity.startActivity(intent)}
}

​ 这样一来,也不需要在各个业务模块中写XXXRouter了,只需要在想要添加路由的XXXActivity页面类中加RouterAnno注解即可。

​ 这个是目前一步步粗略想到的一个简单自定义路由框架的粗浅实现思路。鉴于反射比较耗费性能,真正的路由框架肯定还要考虑更多方面,例如拦截器、路由回调处理、路由兜底策略、路由表内存优化、APT技术等等方面的细节。可以参考ARouter的相关思路。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小微公司可用的开源ERP系统

项目介绍 华夏ERP是基于SpringBoot框架和SaaS模式的企业资源规划&#xff08;ERP&#xff09;软件&#xff0c;旨在为中小企业提供开源且易用的ERP解决方案。它专注于提供进销存、财务和生产功能&#xff0c;涵盖了零售管理、采购管理、销售管理、仓库管理、财务管理、报表查询…

【SpringMVC 】什么是SpringMVC(二)?如何整合ssm框架以及使用mybatisPlus?

文章目录 SpringMVC第三章1、ssm整合1、基本步骤1-3步4-5步6步7步8-12步13步14-15步2、添加数据3、删除数据4、配置事务5、修改数据2、pageHelpe分页1、基本步骤第四章1、mybatisPlus1、基本步骤1-45-7892、基本方法的使用查询2、新ssm项目1、基本步骤1-5678-910-111213-15Spri…

[C++][数据结构]红黑树的介绍和模拟实现

前言 之前我们简单学习了一下搜索树和平衡搜索树&#xff0c;今天我们来学习一下红黑树 简介 概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着…

数据结构学习/复习8--树与二叉树的概念与基本性质练习

一、树 1.概念 2.树的表示 二、二叉树 1.二叉树的概念 2.与性质相关的题

有什么方便的教学口语软件?6个软件教你快速练习口语

有什么方便的教学口语软件&#xff1f;6个软件教你快速练习口语 以下是六个方便实用的教学口语软件&#xff0c;它们可以帮助您快速练习口语&#xff1a; AI外语陪练: 这是一款知名的语言学习软件&#xff0c;提供多种语言的口语练习课程。它采用沉浸式的学习方法&#xff0…

【iOS】NSOperation、NSOperationQueue

文章目录 前言一、NSOperation、NSOperationQueue 简介二、NSOperation、NSOperationQueue 操作和操作队列三、NSOperation四、NSOperationQueue五、NSOperationQueue 控制串行执行、并发执行六、 NSOperation 操作依赖七、NSOperation 优先级八、NSOperation、NSOperationQueu…

【python】基础语法

目录 一.注释和常见规则 二.变量及类型 1.数据类型 2.Numbers数字数据类型 3. 字符串类型 &#xff08;1&#xff09;字符串访问 &#xff08;2&#xff09;字符串拼接 4.List&#xff08;列表&#xff09;类型 &#xff08;1&#xff09; 定义 &#xff08;2&#…

STM32开启停止模式,用外部中断唤醒程序运行

今天学习了一下STM32的停止模式&#xff0c;停止模式下&#xff0c;所有外设的时钟和CPU的电源都会被关闭&#xff0c;所以会很省电&#xff0c;打破这种停止模式的方式就是外部中断可以唤醒停止模式。要想实现这个功能&#xff0c;其实设置很简单的&#xff0c;总共就需要两步…

websevere服务器从零搭建到上线(四)|muduo网络库的基本原理和使用

文章目录 muduo源码编译安装muduo框架讲解muduo库编写服务器代码示例代码解析用户连接的创建和断开回调函数用户读写事件回调 使用vscode编译程序配置c_cpp_properties.json配置tasks.json配置launch.json编译 总结 muduo源码编译安装 muduo依赖Boost库&#xff0c;所以我们应…

五月节放假作业讲解

目录 作业1&#xff1a; 问题&#xff1a; 结果如下 作业2&#xff1a; 结果: 作业1&#xff1a; 初始化数组 问题&#xff1a; 如果让数组初始化非0数会有问题 有同学就问了&#xff0c;我明明已经初始化定义过了&#xff0c;为啥还有0呀 其实这种初始化只会改变第一个…

Fluent 区域交界面的热边界条件

多个实体域公共交界面的壁面&#xff0c;Fluent 会分拆为 wall 和 wall-shadow 的两个壁面&#xff0c;两者为配对关系&#xff0c;分别从属于一个实体域。 配对面可使用热通量、温度、耦合三类热边界条件&#xff0c;前两者统称为非耦合热边界条件。 耦合为配对面默认的热边界…

MYSQL基础架构、执行过程分析、事务的实现、索引的选择、覆盖索引

本文是mysql45讲的1-5的总结 文章目录 基础架构连接器分析器优化器执行器SQL查询执行过程详细执行步骤 SQL更新执行过程重要的日志模块&#xff1a;redo log重要的日志模块&#xff1a;binlog阶段性提交 事务事务隔离的实现启动 索引数据库索引模型InnoDB索引组织结构主键选择…

【深度学习】第二门课 改善深层神经网络 Week 2 3 优化算法、超参数调试和BN及其框架

&#x1f680;Write In Front&#x1f680; &#x1f4dd;个人主页&#xff1a;令夏二十三 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;深度学习 &#x1f4ac;总结&#xff1a;希望你看完之后&#xff0c;能对…

Qt5 框架学习及应用 — 对象树

Qt 对象树 对象树概念Qt为什么使用对象树 &#xff1f;将对象挂到对象树上 对象树概念 对象树&#xff1a;对于树的概念&#xff0c;相信许多学过数据结构的同学应该都不会陌生。在学习数据结构的时候我们所接触的什么二叉树、多叉树、哈夫曼树、AVL树、再到红黑树、B/B树………

【LeetCode刷题】739. 每日温度(单调栈)

1. 题目链接2. 题目描述3. 解题方法4. 代码 1. 题目链接 739. 每日温度 2. 题目描述 3. 解题方法 用一个栈st保存每个数的下标&#xff0c;同时创建一个数组res保存结果&#xff0c;初始值都为0。循环遍历题目中的数组temperature。如果temperature[i] > st.top()&#x…

Vue入门到关门之Vue3项目创建

一、vue3介绍 1、为什么要学习vue3&#xff1f; vue3的变化&#xff1a; 首先vue3完全兼容vue2&#xff0c;但是vue3不建议用vue2的写法&#xff1b;其次&#xff0c;vue3拥抱TypeScript&#xff0c;之前vue2使用的JavaScript&#xff0c;ts完全兼容js 最后之前学的vue2 是…

pytest教程-36-钩子函数-pytest_collection_start

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_unconfigure钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_collection_start钩子函数的使用方法。 pytest_collection_start(session) 是一个 pytest 钩子函数&#xff0c;…

深度学习之DCGAN

目录 须知 转置卷积 DCGAN 什么是DCGAN 生成器代码 判别器代码 补充知识 LeakyReLU&#xff08;x&#xff09; torch.nn.Dropout torch.nn.Dropout2d DCGAN完整代码 运行结果 图形显示 须知 在讲解DCGAN之前我们首先要了解转置卷积和GAN 关于GAN在这片博客中已经很…

LLVM的ThinLTO编译优化技术在Postgresql中的应用

部分内容引用&#xff1a;https://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html LTO是什么&#xff1f; 链接时优化&#xff08;Link-time optimization&#xff0c;简称LTO&#xff09;是编译器在链接时对程序进行的一种优化。它适用于以文件为单位编译…

怎么ai自动答题?方法揭晓!

怎么ai自动答题&#xff1f;在数字化和信息化的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技术日新月异&#xff0c;逐渐渗透到我们生活的方方面面。其中&#xff0c;AI自动答题软件作为辅助学习的工具&#xff0c;受到了越来越多学生和考生的青睐。它们不仅能够帮…