路由框架 ARouter 原理及源码解析

news2024/11/18 17:37:26

文章目录

  • 前言
  • 一、ARouter 简介
  • 二、ARouter 使用
    • 1.添加依赖和配置
    • 2.添加注解
    • 3.初始化SDK
    • 4.发起路由操作
  • 三、ARouter 成员
    • 1. PostCard 明信片
    • 2. Interceptor 拦截器
    • 3. Warehouse 路由仓库
    • 4. ARouter 注解处理
  • 四、ARouter 原理
  • 五、ARouter 源码分析
    • 1. ARouter 初始化
      • 1.1 ARouter#init()
      • 1.2 _ARouter#init()
      • 1.3 LogisticsCenter#init()
      • 1.4 ClassUtils#getFileNameByPackageName()
    • 2. ARouter 路由跳转
      • 2.1 ARouter.getInstance()
      • 2.2 ARouter#build()
      • 2.3 _ARouter#build()
      • 2.3 _ARouter#navigation()
        • 2.3.1 LogisticsCenter#completion()
        • 2.3.2 _ARouter#_navigation()
    • 3. ARouter 获取 Service
  • 总结
  • 参考


前言

在日常开发中,随着项目业务越来越复杂,项目中的代码量也越来越多,如何维护、扩展、解耦等成了一个非常头疼问题。为解决此问题而衍生出的诸如:插件化组件化模块化等热门技术。 使用组件化来改造项目时的难点,就是实现各个组件之间的通讯,通常解决方案采用路由中间件,来实现页面之间的跳转关系。本文要解析的 ARouter 路由框架就是众多解决方案中比较优秀的一个开源库,并且是国人团队开发的,所以中文文档非常详细,以便使用者快速接入。


一、ARouter 简介

ARouter 是阿里开源的一款用于帮助 Android App 进行组件化改造的路由框架,支持模块间的路由、通信、解耦,是 Android 平台中对页面、服务提供路由功能的中间件,以实现在不同模块的 Activity 之间跳转。

ARouter 功能介绍:

  1. 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
  2. 支持多模块工程使用
  3. 支持添加多个拦截器,自定义拦截顺序
  4. 支持依赖注入,可单独作为依赖注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射关系按组分类、多级管理,按需初始化
  8. 支持用户指定全局降级与局部降级策略
  9. 页面、拦截器、服务等组件均自动注册到框架
  10. 支持多种方式配置转场动画
  11. 支持获取 Fragment
  12. 完全支持 Kotlin 以及混编
  13. 支持第三方 App 加固(使用 arouter-register 实现自动注册)
  14. 支持生成路由文档
  15. 提供 IDE 插件便捷的关联路径和目标类
  16. 支持增量编译(开启文档生成后无法增量编译)
  17. 支持动态注册路由信息

ARouter 的典型应用场景有:

  1. 从外部URL映射到内部页面,以及参数传递与解析
  2. 跨模块页面跳转,模块间解耦
  3. 拦截跳转过程,处理登陆、埋点等逻辑
  4. 跨模块API调用,通过控制反转来做组件解耦

ARouter 工作原理:
ARouter 工作原理
ARouter 架构:

ARouter 源码架构

  • appARouter 提供的一个测试 Demo
  • arouter-annotation:模块中声明了很多注解信息和一些枚举类
  • arouter-apiARouter 的核心 api,转换过程的核心操作都在这个模块里面
  • arouter-compilerAPT 处理器,用来自动生成路由表
  • arouter-gradle-plugin:编译期使用的 Plugin 插件,主要作用是用于编译器自动加载路由表,节省应用的启动时间

ARouter 架构图


二、ARouter 使用

1.添加依赖和配置

android {
    defaultConfig {
            ...
        javaCompileOptions {
            annotationProcessorOptions {
                // ARouter参数
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
    // 替换成最新版本, 需要注意的是 api 要与 compiler 匹配使用,均使用最新版可以保证兼容
    compile 'com.alibaba:arouter-api:1.2.4'
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.4'
        ...
}

2.添加注解

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级:/xx/xx
@Route(path = "/first/activity")
public class FirstActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        ......
    }
}

3.初始化SDK

if (BuildConfig.DEBUG) {    // 这两行必须写在init之前,否则这些配置在init过程中将无效
	ARouter.openLog();      // 打印日志
	ARouter.openDebug();    // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化

4.发起路由操作

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.first_button) {
        	// 1. 应用内简单的跳转
            ARouter.getInstance().build("/first/activity").navigation();
            // 2. 跳转并携带参数
    		ARouter.getInstance().build("/test/1")
                .withLong("key1", 666L)
                .withString("key3", "888")
                .withObject("key4", new Test("Jack", "Rose"))
                .navigation();
        }
    }
}

三、ARouter 成员

1. PostCard 明信片

public final class Postcard extends RouteMeta {
    // Base	
    private Uri uri;				// 使用 Uri 方式发起路由
    private Object tag;             // A tag prepare for some thing wrong. inner params, DO NOT USE!
    private Bundle mBundle;         // 需要传递的参数使用 Bundle 存储
    private int flags = 0;          // 启动 Activity 的标志,如:NEW_FALG
    private int timeout = 300;      // 路由超时时间
    private IProvider provider;     // 使用 IProvider 的方式跳转
    private boolean greenChannel;	// 绿色通道,可以不经过拦截器
    // 序列化服务serializationService:需要传递Object自定义类型对象,就需要实现这个服务
    private SerializationService serializationService; 
    private Context context;        // 在使用应用程序或活动之前,需检查实例类型
    private String action;			// Activity 跳转的 Action

    // Animation
    private Bundle optionsCompat;   // Activity 的过渡动画
    private int enterAnim = -1;
    private int exitAnim = -1;
    ......
}

Postcard 明信片,跟我们去各大旅游景点购买然后寄给朋友的明信片是一样的,包含需要传递的参数、跳转方式等等,PostCard 继承自 RouteMeta,来看看其包含哪些重要参数:

public class RouteMeta {
    private RouteType type;         // 路由类型:如Activity,Fragment,Provider等
    private Element rawType;        // 路由原始类型,在编译时用来判断
    private Class<?> destination;   // 目标 Class 对象
    private String path;            // 路由注册的 path
    private String group;           // 路由注册的 group 分组
    private int priority = -1;      // 路由执行优先级,priority 越低,优先级越高,这个一般在拦截器中使用
    private int extra;              // 额外数据
    private Map<String, Integer> paramsType;  // 参数类型,例如 Activity 中使用 @Autowired 的参数类型
    private String name;			// 路由名字,用于生成 javadoc   // 参数配置(对应paramsType).

    private Map<String, Autowired> injectConfig;  // 缓存注入配置(对应paramsType)
    ......
}

RouteMeta 主要存储的是一些目标对象的信息,这些对象是在路由注册的时候才会生成。

2. Interceptor 拦截器

ARouter 中存在一套拦截器机制,所有的路由调用在 completion 的过程中都需经过自定义的一系列拦截器,实现一些 AOP 切面编程。因此先来看看其拦截器机制的实现,首先看一下 IInterceptor 接口类:

public interface IInterceptor extends IProvider {

    /**
     * The operation of this interceptor.
     *
     * @param postcard meta
     * @param callback cb
     */
    void process(Postcard postcard, InterceptorCallback callback);
}

IInterceptor 继承了 IProvider,所以其也是一个服务类型,只需要实现 process() 方法就可以实现拦截操作,在其内部对 Postcard 进行处理,拦截器的执行是通过 InterceptorServiceImpl#doInterceptions() 方法实现的:

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
    private static boolean interceptorHasInit;
    private static final Object interceptorInitLock = new Object();

    @Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
			// 检查拦截器 IInterceptor 的初始化状态,如还未初始化完成,则等待 10s,如仍未初始化完成则报错
            checkInterceptorsInitStatus(); 

            if (!interceptorHasInit) { // 回调通知拦截器初始化耗时太多异常
                callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                return;
            }

            LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                	// 构建值为 interceptors 个数的 CountDownLatch 计数器,用于倒数计数,子线程每执行成功一个
                	// 调用 CountDownLatch.countDown() 方法,interceptors 的个数减 1,直到 interceptors 的个数减为 0
                	// CountDownLatch.await() 就会自动解除等待状态, 不再阻塞主线程, 进入运行状态
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                    	// 调用 _execute() 方法执行一个拦截器的 iInterceptor.process() 方法
                        _execute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt((Throwable) postcard.getTag());
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

    /**
     * Excute interceptor
     *
     * @param index    current interceptor index
     * @param counter  interceptor counter
     * @param postcard routeMeta
     */
    private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
        	// 获取 index 对应的拦截器 IInterceptor
            IInterceptor iInterceptor = Warehouse.interceptors.get(index); 
            // 调用拦截器 IInterceptor#process() 方法执行拦截操作,对 Postcard 进行处理
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown(); // 计数器 CountDownLatch 的 interceptors 的个数减 1
                    // 继续调用 _execute() 方法,只是 index 的数值 + 1,来执行下一个拦截器的 iInterceptor.process() 方法
                    _execute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    // Last interceptor execute over with fatal exception.
					// save the exception message for backup.
                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception);   
                    counter.cancel();
                }
            });
        }
    }
    ......
}

InterceptorServiceImpl#doInterceptions() 方法中,通过 ThreadPoolExecutor 线程池和 CountDownLatch 计数器,实现对每个拦截器 IInterceptor 调用其 process() 方法执行拦截操作,对 Postcard 进行处理。

3. Warehouse 路由仓库

Warehouse 意为仓库,用于存放被 @Route@Interceptor 注释的路由相关的信息,也就是我们关注的 destination 等信息。

class Warehouse {
    // 保存所有 IRouteGroup 实现类的class对象,在 ARouter 初始化中赋值,key 是 path 第一级
    //(IRouteGroup 实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    // 保存所有路由元信息 RouteMeta,是在 completion 中赋值,key是path
    // 首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务
    static Map<String, RouteMeta> routes = new HashMap<>();

    // 保存所有服务provider实例,在completion中赋值,key是IProvider实现类的class
    static Map<Class, IProvider> providers = new HashMap<>();
    // 保存所有provider服务的元信息(实现类的class对象),在 ARouter 初始化中赋值,key是IProvider实现类的全类名
    // 主要用于使用IProvider实现类的class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // 保存所有拦截器实现类的class对象,在 ARouter 初始化时收集到,key是优先级 
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    // 保存所有拦截器,在 ARouter 初始化完成后立即创建
    static List<IInterceptor> interceptors = new ArrayList<>();
    ......
}

其中 groupsIndexprovidersIndexinterceptorsIndexARouter 初始化时就准备好的基础信息,为业务中随时发起路由操作(Activity 跳转、服务获取、拦截器处理)做好准备。

4. ARouter 注解处理

ARouter 通过注解处理器 AnnotationProcessor 配合 AutoService 来实现的,并通过 JavaPoet 实现对 Java 文件的编写。

AnnotationProcessor:注解处理器是一种工具,它通过检索源代码中的注解信息,执行特定的代码生成任务或对代码进行检查。ARouter 使用注解处理器在源码编译的阶段,通过 APT 获取被 @Route@Interceptor 等注解的路由相关信息,在对应模块下动态下动态生成 .java 源文件,通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。详细可以看看 arouter-complier 包下的具体实现。

AutoService:是 Google 开发的一个自动生成 SPI(全称是 Service Provider Interface) 清单文件的框架,用来自动帮我们注册 APT 文件(全称是 Annotation Process Tool,或者叫注解处理器,AbstractProcessor 的实现类)。

JavaPoetJava 诗人,名字是不是很优雅,JavaPoet 是由 Square 推出的开源 Java 代码生成框架,能够提供 Java 生成源代码文件的能力,通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式替代掉繁琐冗杂的重复工作。

SPIService Provider Interface 的简称,是 JDK 默认提供的一种将接口和实现类进行分离的机制。这种机制能将接口和实现进行解耦,大大提升系统的可扩展性。

SPI 机制约定:当一个 Jar 包需要提供一个接口的实现类时,该 Jar 包需要在 META-INF/services 目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该 JarMETA-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。


四、ARouter 原理

ARouter 工作原理主要包括:路由表生成、路由匹配和页面跳转三个方面:

  • ARouter 通过注解处理器来生成路由表,在每个组件的 build.gradle 文件中,配置注解处理器的依赖和配置信息,在编译时,注解处理器会扫描项目中所有使用了 @Route 注解的类,然后根据注解中的信息生成一个路由表。该路由表包含了每个被注解标记的类对应的路由信息,如:路径、组件名、优先级等;
  • ARouter 在运行时会根据路由表来进行路由匹配,当需要跳转到某个页面时,通过调用 ARouter.getInstance().build(path) 方法来获取一个路由构建器,构建器中的 path 参数对应了要跳转的页面路径。随后 ARouter 根据该路径在路由表中进行匹配,找到对应的 Class 对象并返回;
  • ARouter 通过反射来进行页面跳转,当找到目标 Class 对象后,调用 navigation() 方法进行页面跳转,ARouter 自动调用目标页面的构造方法来创建一个实例,并且会根据传递的参数来进行参数的注入(ARouter.withString() 方法)。 然后 navigation() 方法的内部调用 startActivity(intent) 方法进行页面跳转,至此便实现两个相互没有依赖的 module 顺利的启动对方的 Activity 的目标。

五、ARouter 源码分析

1. ARouter 初始化

ARouter 在使用前需要通过调用 ARouter#init() 方法并传入 Application 进行初始化:

1.1 ARouter#init()

public final class ARouter {
    private volatile static ARouter instance = null;
    private volatile static boolean hasInit = false;
    public static ILogger logger;

	/**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
        if (!hasInit) {
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            // 继续调用 _ARouter.init() 函数进行初始化
            hasInit = _ARouter.init(application);

            if (hasInit) {
            	// ARouter 创建了一个 InterceptorServiceImpl 服务的实例对象,后面讲到拦截器的时候会用到
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }
}

继续调用 _ARouter#init() 函数进行初始化:

1.2 _ARouter#init()

final class _ARouter {
    static ILogger logger = new DefaultLogger(Consts.TAG);
    private volatile static _ARouter instance = null;
    private volatile static boolean hasInit = false;
    private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();
    private static Handler mHandler;
    private static Context mContext;
    
    protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());

        return true;
    }
}

内部初始化并赋值一些 mContextmHandler 以及字段信息,最重要的是调用 LogisticsCenter#init(mContext, executor) 函数来初始化。

1.3 LogisticsCenter#init()

public class LogisticsCenter {
    private static Context mContext;
    static ThreadPoolExecutor executor;
    private static boolean registerByPlugin;

	/**
     * LogisticsCenter init, load all metas in memory. Demand initialization
     */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            // 首先使用 AGP 插件进行路由表的自动加载
            loadRouterMap();
            if (registerByPlugin) { // 如果 registerByPlugin 被设置为true,说明使用的是插件加载,直接跳过
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else { 
            	// loadRouterMap() 中设置 registerByPlugin 为 false,因此调用下面步骤加载
                Set<String> routerMap;

                // 如果是 debug 模式或者是新版本的,则每次都会去加载 routerMap,这会是一个耗时操作
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // 获取 arouter-compiler 模块生成的存储 ClassName 集合的 routerMap
                    // 根据指定的 packageName 获取 package 下的所有 ClassName
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                    	// 存入 SP 缓存中
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // 路由表更新完成后保存新版本名
                } else {
                	// 如果是其他的情况,如:release 模式下,已经缓存了 ClassName 列表,则直接去文件中读取
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();
				// 遍历 routerMap 获取 ClassName
                for (String className : routerMap) {
                	// 如果className = "com.alibaba.android.arouter.routes.ARouter$$Root"格式
                	// 则加载类构建对象后通过 loadInto() 方法将路由组信息添加到 Warehouse.groupsIndex 中
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    	// 如果className = "com.alibaba.android.arouter.routes.ARouter$$Interceptors"格式
                    	// 则加载类构建对象后通过 loadInto() 方法将拦截器信息添加到 Warehouse.interceptorsIndex 中
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // 如果className = "com.alibaba.android.arouter.routes.ARouter$$Providers"格式
                        // 则加载类构建对象后通过 loadInto() 方法将服务 Provider 信息添加到 Warehouse.providersIndex 中
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }
			......
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }
}

LogisticsCenter#init() 函数执行流程如下:

  • 获取 com.alibaba.android.arouter.routes 下存储 ClassName 的集合 routerMap。如果是 debug 模式或者是新版本的,则每次都会调用 ClassUtils#getFileNameByPackageName() 函数根据指定的 packageName 获取 package 下的所有 ClassName 并保存到集合 routerMap 中;如果不是 debug 模式且之前已经解析过,则直接从 SP 中读取(已有缓存)。注意debug 模式每次都需要更新,因为类会随着代码的修改而变动。
  • 遍历 routerMap 中的 ClassName,如果是 RouteRoot,则加载类构建对象后通过 loadInto() 方法将路由组信息添加到 Warehouse.groupsIndex 中;如果是 InterceptorGroup,则加载类构建对象后通过 loadInto() 方法将拦截器信息添加到 Warehouse.interceptorsIndex 中;如果是 ProviderGroup,则加载类构建对象后通过 loadInto() 方法将服务 Provider 信息添加到 Warehouse.providersIndex 中。

1.4 ClassUtils#getFileNameByPackageName()

public class ClassUtils {
    private static final String EXTRACTED_NAME_EXT = ".classes";
    private static final String EXTRACTED_SUFFIX = ".zip";

    /**
     * 通过指定包名,扫描包下面包含的所有的ClassName
     *
     * @param context     U know
     * @param packageName 包名
     * @return 所有class的集合
     */
    public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
        final Set<String> classNames = new HashSet<>();
		// 通过 getSourcePaths 方法获取 dex 文件 path 集合
        List<String> paths = getSourcePaths(context);
        // 通过 CountDownLatch 对 path 的遍历处理进行控制
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());
		// 遍历 path,通过 DefaultPoolExecutor 并发对 path 进行处理
        for (final String path : paths) {
            DefaultPoolExecutor.getInstance().execute(new Runnable() {
                @Override
                public void run() {
                    DexFile dexfile = null; // 加载 path 对应的 dex 文件
                    try {
                        if (path.endsWith(EXTRACTED_SUFFIX)) {
                            // NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                            // 如果是 .zip 结尾的文件通过 DexFile.loadDex 进行加载
                            dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                        } else {
                        	// 否则通过 new DexFile 加载
                            dexfile = new DexFile(path);
                        }

                        Enumeration<String> dexEntries = dexfile.entries();
                        // 遍历 dex 中的 Entry
                        while (dexEntries.hasMoreElements()) {
                        	// 如果是对应的 package 下的类,则添加到 classNames 中
                            String className = dexEntries.nextElement();
                            if (className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (Throwable ignore) {
                        Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                    } finally {
                        if (null != dexfile) {
                            try {
                                dexfile.close();
                            } catch (Throwable ignore) {
                            }
                        }
                        // 计数器 CountDownLatch 的 paths 的个数减 1
                        parserCtl.countDown();
                    }
                }
            });
        }
		// 所有 path 处理完成后,CountDownLatch.await() 自动解除等待状态, 不再阻塞主线程, 进入运行状态继续向下走流程
        parserCtl.await();

        Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
        return classNames;
    }
}

ClassUtils#getFileNameByPackageName() 函数的执行流程如下:

  • 通过 getSourcePaths() 方法获取 dex 文件的 path 集合;
  • 创建了一个 CountDownLatch 计数器控制 dex 文件的并行处理,以加快速度;
  • 遍历 path 列表,通过 DefaultPoolExecutor 线程池对 path 并行处理;
  • 加载 path 对应的 dex 文件,并对其内部的 Entry 进行遍历,若发现了对应 package 下的 ClassName,将其加入结果集合 classNames 中。

流程至此,ARouter 初始化过程就完成了对自动生成的路由相关类 RouteRootInterceptorProviderGroup 的加载,并对它们通过反射构造后将信息加载进了 Warehouse 类中。

2. ARouter 路由跳转

以第二节 ARouter 用法中的发起路由操作的代码为例:

// 1. 应用内简单的跳转
ARouter.getInstance().build("/first/activity").navigation();

首先看一下 ARouter#getInstance() 函数获取 ARouter 实例对象:

2.1 ARouter.getInstance()

public final class ARouter {
    private volatile static ARouter instance = null;
    private volatile static boolean hasInit = false;

    private ARouter() {
    }    

    public static ARouter getInstance() {
        if (!hasInit) {
            throw new InitException("ARouter::Init::Invoke init(context) first!");
        } else {
            if (instance == null) {
                synchronized (ARouter.class) {
                    if (instance == null) {
                        instance = new ARouter();
                    }
                }
            }
            return instance;
        }
    }
}

首先检查 ARouter 是否已经初始化,如果已经初始化则直接新建 ARouter 实例对象。

2.2 ARouter#build()

public Postcard build(String path) {
	return _ARouter.getInstance().build(path);
}

_ARouter#getInstance() 函数跟 ARouter 的类似,不再继续贴代码,继续转调 _ARouter 的同名 build() 方法:

2.3 _ARouter#build()

final class _ARouter {
    /**
     * Build postcard by path and default group
     */
    protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
        	// 获取 PathReplaceService 接口的实例,该接口需要用户来实现,若没有实现则返回 null
        	// 若有实现则调用其 forString() 方法传入用户的 RoutePath 进行路径的预处理
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            // 通过 extractGroup() 方法对字符串 path 进行截取处理,取出 Route Group 的名称部分
            // 然后继续调用重载的 build() 方法,传入路径 path、Route Group 的名称部分
            return build(path, extractGroup(path), true);
        }
    }
    
    /**
     * Build postcard by path and group
     */
    protected Postcard build(String path, String group, Boolean afterReplace) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            if (!afterReplace) {
                PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
                if (null != pService) {
                    path = pService.forString(path);
                }
            }
            return new Postcard(path, group);
        }
    }
}

_ARouter#build() 的重载方法中,由于入参 afterReplacetrue,因此根据入参 pathgroup 直接新建 Postcard 实例对象并返回。

2.3 _ARouter#navigation()

通过上面的分析可知,ARouter#navigation() 方法,最终也是委托给 _ARouter#navigation() 进行处理的,因此不再贴 ARouter#navigation() 方法的代码,直接看 _ARouter#navigation() 方法的实现:

final class _ARouter {
	/**
     * Use router navigation.
     *
     * @param context     Activity or null.
     * @param postcard    Route metas
     * @param requestCode RequestCode
     * @param callback    cb
     */
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
        if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
            return null; // 预处理失败,取消页面跳转
        }

        // 如果没有传入 Context,则使用 ARouter 初始化时传入的 Application 作为 Context
        postcard.setContext(null == context ? mContext : context);
        try {
        	//通过 LogisticsCenter.completion() 方法对 postcard 进行补全
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());

            if (debuggable()) {
                // Show friendly tips for user.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(mContext, "There's no route matched!\n" +
                                " Path = [" + postcard.getPath() + "]\n" +
                                " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                    }
                });
            }
			// 通过 NavigationCallback 对 navigation 的过程进行监听
            if (null != callback) {
                callback.onLost(postcard);
            } else {
                // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }

            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }
		// 如果设置了 greenChannel,会跳过所有拦截器的执行
        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            // 调用 InterceptorService.doInterceptions() 方法,对 postcard 的所有拦截器进行执行
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(postcard, requestCode, callback);
        }
        return null;
    }
}

_ARouter#navigation() 方法的执行流程如下:

  • 通过 LogisticsCenter#completion() 方法对 Postcard 进行补全;
  • 如果 Postcard 没有设置 greenChannel,则对 Postcard 的拦截器进行执行,执行完成后调用 _navigation 方法真正实现跳转;如果设置了 greenChannel,即绿色免检通道,则直接跳过所有拦截器,直接调用 _navigation 方法来跳转。
2.3.1 LogisticsCenter#completion()
public class LogisticsCenter {
	public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }
		// 通过 Warehouse.routes.get 由 postcard.path 路径尝试获取 RouteMeta
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) { // 若 routeMeta 为 null,可能是并不存在,或是还没有加载进来
            if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // 加载路由并将其缓存到内存中,然后 routeMeta 中删除
                try {
					// 调用 addRouteGroupDynamic() 方法,如果 Warehouse.groupsIndex 中包含 postcard.getGroup(),但是还未加载
					// 则将其移除后,重新 loadInto 进来
                    addRouteGroupDynamic(postcard.getGroup(), null);
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }
                // 重新调用 completion() 方法对其进行补全
                completion(postcard);
            }
        } else {
        	// 如果找到了对应的 routeMeta,将它的信息设置进 postcard 中
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            Uri rawUri = postcard.getUri();
            if (null != rawUri) { // 将获取到的 uri 中的参数设置进 bundle 中
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                	// 针对由 @Param 注释的参数,按其类型设置值
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need auto inject. 保存需要自动注入的参数名
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }
			// 对 provider 和 fragment,进行特殊处理
            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // 如果是一个 provider,尝试从 Warehouse 中查找它的类并构造对象,然后将其设置到 provider
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // 如果没有找到 provider 的实例对象
                        IProvider provider;
                        try { // 新建 provider 实例对象并初始化,然后加入到 Warehouse.providers 中
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            logger.error(TAG, "Init provider failed!", e);
                            throw new HandlerException("Init provider failed!");
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider 需要跳过所有的拦截器,因此需设置 greenChannel 为 true
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment 也不需要拦截器,因此设置 greenChannel 为 true
                default:
                    break;
            }
        }
    }
}

LogisticsCenter#completion() 的执行流程如下:

  • 通过 Warehouse.routes.get 由 path 路径尝试获取 RouteMeta 对象;
  • 若获取不到 RouteMeta 对象,可能是不存在或是还没有进行加载(第一次都未加载),尝试获取 RouteGroup 调用其 loadInto 方法将 RouteMeta 加载进 Warehouse.routes,最后调用 completion 重新尝试补全;
  • 若获取到 RouteMeta 对象,则将 RouteMeta 的信息设置到 postcard 中,其中会将 rawUri 的参数设置进 Bundle
  • 对于 ProviderFragment 特殊处理,其中 Provider 会从 Warehouse 中加载并构造它的对象,然后设置到 postcard注意ProviderFragment 都会跳过拦截器。
2.3.2 _ARouter#_navigation()
final class _ARouter {
	private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();
        switch (postcard.getType()) { // 根据 postcard 的 type 来分别处理
            case ACTIVITY:
                // 对 Activity,构造 Intent,将参数设置进去
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // 设置 flags
                int flags = postcard.getFlags();
                if (0 != flags) {
                    intent.setFlags(flags);
                }

                // 不是 activity,需要设置 FLAG_ACTIVITY_NEW_TASK,新建一个栈
                if (!(currentContext instanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // 设置 Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) {
                    intent.setAction(action);
                }

                // 切换到主线程,调用 startActivity() 方法,启动新的页面
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });
                break;
            case PROVIDER:
            	// provider 类型的直接返回对应的 provider
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
            	// 对于 broadcast、contentprovider、fragment,构造对象,设置参数后返回
                Class<?> fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }
                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }
}

_ARouter#_navigation() 函数的执行流程如下:

  • 对于 Activity,会构造一个 Intent 并将之前 postcard 中的参数设置进去,之后会切换到主线程,调用 startActivity() 方法,启动新的页面;
  • 对于 Provider,直接返回其对应的 provider 对象;
  • 对于 BroadcastContentProviderFragment,反射构造对象后,将参数设置进去并返回。

通过分析 ARouter 的初始化和路由跳转的整体逻辑,可以发现其实整体还是不复杂的,实际上就是对 ActivityFragment 的调转过程进行了包装。

3. ARouter 获取 Service

ARouter 除了可以通过 ARouter#getInstance()#build()#navigation() 这样的方式实现页面跳转之外,还可以通过ARouter#getInstance()#navigation(XXService.class) 这样的方式实现跨越组件的服务获取,来看看其是如何实现的:

public <T> T navigation(Class<? extends T> service) {
	return _ARouter.getInstance().navigation(service);
}

ARouter 还是委托给 _ARouter 来实现:

final class _ARouter {
	protected <T> T navigation(Class<? extends T> service) {
        try {
        	// 通过 LogisticsCenter.buildProvider 传入 service.class 的 name 构建一个 postcard
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());

            // Compatible 1.0.5 compiler sdk.
            // 早期版本没有使用完全限定名来获取服务
            if (null == postcard) {
                // 没有服务,或者这个服务是旧版本,因此再次通过 LogisticsCenter.buildProvider 
                // 传入 simpleName 来构建 postcard
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }

            if (null == postcard) {
                return null; // 仍然获取不到则返回 null
            }

            // Set application to postcard.
            postcard.setContext(mContext);
			// 对 postcard 进行补全,前面分析过
            LogisticsCenter.completion(postcard);
            // 通过 postcard.getProvider 获取对应的 Provider 并返回
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }
}

_ARouter#navigation() 函数,首先通过 LogisticsCenter#buildProvider() 方法传入 service.class 的类名 name 构建出一个 postcard。如果获取不到,则再次通过 LogisticsCenter#buildProvider() 方法传入 simpleName 来构建出一个 postcard。随后通过 LogisticsCenter#completion() 方法对 postcard 进行补全,最后通过 postcard#getProvider() 方法获取对应的 Provider 实例对象。


总结

ARouter 路由调用时序图
结合 ARouter 路由调用时序图,对 ARouter 的路由过程做个总结:

  1. 通过 ARouter 中的 build(path) 方法构建出一个 Postcard,或直接通过其 navigate(serviceClass) 方法构建一个 Postcard
  2. 通过 Postcard 中提供的一系列方法对这次路由进行配置,包括携带的参数,是否跳过拦截器等;
  3. 通过 ARouter#navigation() 方法完成路由的跳转,其步骤如下:
    - 通过 LogisticsCenter#completion() 方法根据 Postcard 的信息结合 Warehouse 中加载的信息对 PostcardDestinationType 等信息进行补全,这个过程中会实现对 RouteMeta 信息的装载,并且对于未跳过拦截器的类会逐个调用拦截器进行拦截器处理;
    - 根据补全后 Postcard 的具体类型,调用对应的方法进行路由的过程(如:对于 Activity 调用其 startActivity() 方法,对于 Fragment 构建对象并调用其 setArgument() 方法)。

参考

github.com/alibaba/ARouter

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

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

相关文章

美国ARC与延锋安全合作,推动汽车安全气囊技术新突破

在汽车安全领域&#xff0c;安全气囊作为关键被动安全配置&#xff0c;对于保障乘客生命安全至关重要。随着汽车工业的快速发展和科技创新的持续推进&#xff0c;安全气囊技术的升级与革新显得尤为重要。2022年10月25日&#xff0c;美国ARC公司与延锋安全携手合作&#xff0c;共…

产品交付能力提升的探索与分享

在当前激励的市场竞争环境下&#xff0c;对项目交付的成本和毛利要求越来越高。如何能快速高效地完成项目交付已然成为我们矢志追求的目标。抛开人为因素对项目交付效率的影响&#xff0c;产品本身的交付能力才是关键。因此&#xff0c;在设计新产品时需要考虑其便捷交付性&…

那些年我为了考PMP踩过的坑.....

说到考PMP我尊嘟很难过且伤心&#xff0c;众所周知&#xff0c;报考PMP都是要报机构的而且还是PMI认证的机构&#xff0c;所以在报考PMP过程中选的机构我可以说踩过了很多坑了...... Q&#xff1a;包过吗&#xff1f; 大家千万不要信某某机构说的包过噱头&#xff0c;真的很坑…

具身智能的视觉-语言-动作模型综合综述论文

近期arXiv公开了关于具身智能&#xff08;Embodied AI&#xff09;中的视觉-语言-动作模型&#xff08;Vision-Language-Action Models&#xff0c;简称VLAs&#xff09;的综合综述论文。介绍了VLAs的概念&#xff0c;它们是为了处理多模态输入而设计的模型&#xff0c;包括视觉…

用AI绘画生成网上爆火的治愈系插画,竟然轻松月入两万?!

大家好&#xff0c;我是向阳 一个月的时间&#xff0c;涨粉 2w&#xff0c;太猛了。这类作品&#xff0c;不仅涨粉能力强&#xff0c;变现能力也很强。 在第 11天的时候&#xff0c;就已经开始接商单变现了。 而这类账号&#xff0c;不仅仅只有接商单这一种变现模式。至于其…

Spire.PDF for .NET【文档操作】演示:如何删除 PDF 中的图层

借助Spire.PDF&#xff0c;我们可以在新建或现有pdf文档的任意页面中添加线条、图像、字符串、椭圆、矩形、饼图等多种图层。同时&#xff0c;它还支持我们从pdf文档中删除特定图层。 Spire.PDF for .NET 是一款独立 PDF 控件&#xff0c;用于 .NET 程序中创建、编辑和操作 PD…

用python和HY(lisp)代码为例学习什么是递归?

什么是递归&#xff1f; 看ANSI Common Lisp手册&#xff0c;里面提到递归第二章&#xff1a;欢迎来到 Lisp — ANSI Common Lisp 中文版&#xff0c;说&#xff1a;不要把递归看作一个普通函数来理解&#xff0c;因为普通函数经常被当成一个“机器”&#xff0c;原料从入口进…

2021数学建模A题目–“FAST”主动反射面的形状调节

A 题——“FAST”主动反射面的形状调节 思路&#xff1a;该题主要是通过利用伸缩杆调整FAST反射面&#xff0c;给出合适的调整方案 程序获取 第一题问题思路与结果&#xff1a; 当待观测天体S位于基准球面正上方&#xff0c;结合考虑反射面板调节因素&#xff0c;确定理想抛物…

消息队列的对比及适配的应用场景

消息队列的对比及适配的应用场景## 特性 / 消息队列KafkaRabbitMQActiveMQRedis消息模型发布-订阅、流处理队列、发布-订阅队列、发布-订阅发布-订阅协议支持自定义TCP协议、REST代理AMQP、STOMP、MQTTAMQP、OpenWire、STOMP、MQTT自定义协议可用性非常高&#xff0c;分区和副…

域权限维持之伪造域控

2022年1月10日&#xff0c;国外安全研究员Kaido发文称发现了一种新的伪造域控方式&#xff0c;安全研究员只需要新建一个机器账户&#xff0c;然后修改机器账户的UserAccountControl属性为8192。活动目录就会认为这个机器账户就是域控&#xff0c;然后就可以使用这个新建的机器…

STM32多功能交通灯系统:从原理到实现

一、功能说明 本交通灯系统采用先进的stm32f103c8t6微处理器为核心控制单元。系统设置东南西北四个方向各配置两位数码管&#xff0c;用以精准展示5至99秒的时间范围&#xff0c;并且允许用户根据实际需求进行灵活调整。 在信号灯配置方面&#xff0c;每个方向均配备左转、直…

在进行JD(京东)电商API大数据采集,针对商品详情数据、SKU数据以及价格分析时,关键是数据的准确性、完整性和分析的深度

一、项目背景 网上购物已经成为大众生活的重要组成部分。人们在电商平台上浏览商品并购物&#xff0c;产生了海量的用户行为数据&#xff0c;用户对商品的评论数据对商家具有重要的意义。利用好这些碎片化、非结构化的数据&#xff0c;将有利于企业在电商平台上的持续发展&…

已成功见刊检索的国际学术会议论文海报展示(2)

【先投稿先送审】第四届计算机、物联网与控制工程国际学术会议&#xff08;CITCE 2024) 大会官网&#xff1a;www.citce.org 时间地点&#xff1a;2024年11月1-3日&#xff0c;中国-武汉 收录检索&#xff1a;EI Compendex&#xff0c;Scopus 主办单位&#xff1a;四川师范…

独立农作物区域-第13届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第86讲。 独立农作物区域&a…

食品企业仓储式批发零售一体化解决方案

食品企业需要有效应对日益复杂的市场挑战和消费者需求的快速变化的挑战并提升市场竞争力&#xff0c;仓储式类的批发零售一体化需求应运而生。这一全新的商业模式不仅整合了传统的批发和零售模式&#xff0c;还优化了供应链管理和客户体验&#xff0c;成为食品行业发展的新引擎…

docker安装Jumpserver

docker安装Jumpserver 简介 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。 JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产&#xff0c;包括&#xff1a; SSH: Linux / Unix / 网络设备 等&#xff1b; Windows:…

中科数安 |-透明加密软件_无感透明加密 - 源头有保障

中科数安的透明加密软件是一款专为保护企业数据安全而设计的高级产品&#xff0c;它采用了无感透明加密技术&#xff0c;确保源头数据的安全可靠。 ——www.weaem.com 以下是该软件的主要特点和功能概述&#xff1a; 无感透明加密&#xff1a; 中科数安的透明加密软件能够在用…

分类预测 | Matlab实现GA-XGBoost遗传算法优化XGBoost的多特征分类预测

分类预测 | Matlab实现GA-XGBoost遗传算法优化XGBoost的多特征分类预测 目录 分类预测 | Matlab实现GA-XGBoost遗传算法优化XGBoost的多特征分类预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现GA-XGBoost遗传算法优化XGBoost的多特征分类预测&#xff0c;…

Spring Boot集成Minio插件快速入门

1 Minio介绍 MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&…

【机器学习300问】125、什么是双向循环神经网络(BRNN)?什么是深度循环神经网络(DRNN)?

一、双向循环神经网络 &#xff08;1&#xff09;诞生背景 双向循环神经网络&#xff08;Bidirectional Recurrenct Neural Network, BRNN&#xff09;是在深度学习领域发展起来的一种特殊类型的循环神经网络&#xff08;RNN&#xff09;&#xff0c;它诞生的背景是为了解决传…