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

文章目录

  • 前言
  • 一、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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

类的继承性(Java)

本篇学习面向对象语言的第二特性——继承性。 1 .为什么需要继承 我们来举个例子&#xff1a;我们知道动物有很多种&#xff0c;是一个比较大的概念。在动物的种类中&#xff0c;我们熟悉的有猫(Cat)、狗(Dog)等动物&#xff0c;它们都有动物的一般特征&#xff08;比如能够吃…

leetcode498 对角线遍历

题目 给你一个大小为 m x n 的矩阵 mat &#xff0c;请以对角线遍历的顺序&#xff0c;用一个数组返回这个矩阵中的所有元素。 示例 输入&#xff1a;mat [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,4,7,5,3,6,8,9] 解析 本题目主要考察的就是模拟法&#xff0c;首…

【动态规划】斐波那契数列模型 {动态规划的解题思路;动态规划的优化方案;动态规划的基础题型}

一、经验总结 动态规划题型的解题思路&#xff1a; 状态表示&#xff1a;dp[i]的含义是什么。通过解题经验和题目要求得到&#xff0c;一般有以下两个方向&#xff1a; 以i位置为起点以i位置为终点 状态转移方程&#xff1a;dp[i]怎么求。根据距离i位置最近的一步划分问题&am…

公司面试题总结(六)

31.说一说 webpack 的构建流程是什么&#xff1f; ⚫ 初始化流程&#xff1a; ◼ 从配置文件和 Shell 语句中读取与合并参数 ◼ 初始化需要使用的插件和配置插件等执行环境所需要的参数 ⚫ 编译构建流程&#xff1a; ◼ 从 Entry 发出&#xff0c;针对每个 Module 串行…

【 ClickHouse】 ClickHouse数据类型(整型、浮点型、布尔型、Decimal型、字符串、数组、时间类型)(二)

ClickHouse数据类型 整型 固定长度的整型&#xff0c;包括有符号整型或无符号整型。 1)整型范围&#xff1a; Int8 - [-128 : 127] Int16 - [-32768 : 32767] Int32 - [-2147483648 : 2147483647] Int64 - [-9223372036854775808 : 9223372036854775807]2)无符号整型范围&…

【linux】内核从tcp层调用IP层摸索中

合入代码&#xff1a; 登录 - Gitee.com 这是运行日志&#xff1a; https://gitee.com/r77683962/linux-6.9.0/raw/master/test_log/kern_tcp_ip.log 日志截取部分&#xff08;也不知道对不对&#xff0c;凭感觉走。。。。&#xff09;

关机充电动画:流程与定制

关机充电动画&#xff1a;流程与定制 基于MTK平台Android 11分析 生成logo.bin 关机充电动画是由一系列的bmp图片组成的&#xff0c;这些图片资源存在于vendor/mediatek/proprietary/bootable/bootloader/lk/dev/logo目录下&#xff08;当然不仅保护关机充电动画&#xff0c…

自动驾驶仿真:Carsim转向传动比设置

文章目录 一、转向传动比概念二、设置转向传动比1、C factor概念2、Steer Kinematics概念3、传动比计算公式 三、转向传动比验证 一、转向传动比概念 转向传动比&#xff08;Steering Ratio&#xff09;表示方向盘转动角度与车轮转动角度之间的关系。公式如下&#xff1a; 转向…

乘法与位运算

目录 描述 输入描述&#xff1a; 输出描述&#xff1a; 参考代码 描述 题目描述&#xff1a; 进行一个运算单元的电路设计&#xff0c;A[7:0]*11111011&#xff0c;尽量用最少的资源实现&#xff0c;写出对应的 RTL 代码。 信号示意&#xff1a; A信号输入 B 信号…

[自动驾驶 SoC]-4 特斯拉FSD

FSD, 参考资料来源FSD Chip - Tesla - WikiChip 另外可参考笔者之前分享文章&#xff1a;[自动驾驶技术]-6 Tesla自动驾驶方案之硬件&#xff08;AI Day 2021&#xff09;&#xff0c;​​​​​​​[自动驾驶技术]-8 Tesla自动驾驶方案之硬件&#xff08;AI Day 2022&#xf…

DGit的使用

将Remix连接到远程Git仓库 1.指定克隆的分支和深度 2.清理&#xff0c;如果您不在工作区上工作&#xff0c;请将其删除或推送至 GitHub 或 IPFS 以确保安全。 为了进行推送和拉取&#xff0c;你需要一个 PAT — 个人访问令牌 当使用 dGIT 插件在 GitHub 上推送、拉取、访问私…

Studying-代码随想录训练营day13| 二叉树理论基础、二叉树递归遍历、二叉树迭代遍历、二叉树层序遍历

第十三天&#xff0c;&#x1f4aa;(ง •_•)ง&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 二叉树理论基础 二叉树的种类&#xff1a; 二叉树的存储方式 二叉树的遍历方式 二叉树的定义&#xff1a; 二叉树递归遍历 二叉树的迭代遍历 二叉树的层次遍历 …

数据库新技术【分布式数据库】

文章目录 第一章 概述1.1 基本概念1.1.1 分布式数据库1.1.2 数据管理的透明性1.1.3 可靠性1.1.4 分布式数据库与集中式数据库的区别 1.2 体系结构1.3 全局目录1.4 关系代数1.4.1 基操1.4.2 关系表达式1.4.3 查询树 第二章 分布式数据库的设计2.1 设计策略2.2 分布设计的目标2.3…

深度学习入门5——为什么神经网络可以学习?

在理解神经网络的可学习性之前&#xff0c;需要先从数学中的导数、数值微分、偏导数、梯度等概念入手&#xff0c;从而理解为什么神经网络具备学习能力。 1.数值微分的定义 先从导数出发理解什么是梯度。某一点的导数直观理解就是在该点的切线的斜率。在数学中导数表示某个瞬…

Spring的事务步骤

一、事务处理方案&#xff1a; Spring框架中提供的事务处理方案&#xff1a;一共有两种&#xff1a; 1.适合中小项目使用的&#xff0c; 注解方案&#xff1a; 注解的方式做事务用起来简单&#xff0c;灵活&#xff0c;方便&#xff0c;中小型项目中用它比较方便&#xff0c…

白酒:酒文化的教育价值与实践

酒文化作为中国传统文化的重要组成部分&#xff0c;具有丰富的教育价值。云仓酒庄的豪迈白酒作为酒文化的品牌之一&#xff0c;在传承与发展中不断挖掘和发挥酒文化的教育价值。 首先&#xff0c;豪迈白酒有责任传承丰富的历史文化知识。从酒的起源、酿造技艺、酒器文化到酒礼酒…

Github上传大于100M的文件(ubuntu教程)

安装Git-lfs Git Large File Storage (LFS) 使用 Git 内部的文本指针替换音频样本、视频、数据集和图形等大文件&#xff0c;同时将文件内容存储在 GitHub.com 或 GitHub Enterprise 等远程服务器上。官网下载&#xff1a;https://git-lfs.github.com/ ./install.sh上传 比如…

教你如何安装 IntelliJ IDEA

安装 IntelliJ IDEA 的步骤通常如下&#xff0c;这里提供的是基于 Windows 系统的安装指南。 下载 IntelliJ IDEA 1. 访问 JetBrains 官方网站&#xff1a;[https://www.jetbrains.com/idea/download/](Download IntelliJ IDEA – The Leading Java and Kotlin IDE) 2. 选择适…

一文带你入门【论文排版】利器·LaTeX |Macos

小罗碎碎念 我在刚开始写公众号的时候&#xff0c;写过一期推文&#xff0c;详细的讲解过如何使用LaTeX快速的进行论文排版。不过当时用的是windows的系统&#xff0c;这一次把Mac端的教程补上。 windows系统教程 https://zhuanlan.zhihu.com/p/677481269 LaTeX是一种流行的排…

【UIDynamic-动力学-UICollisionBehavior-action Objective-C语言】

一、我们说,这个碰撞行为啊,collision,它里边还有一个属性,叫做action,它能够干什么,它能够实时的去监听, 1.实时的去监听,我们当前的这个view的一个frame的变化, 它会调用action的方法,实际上,action方法,它是一个block,然后呢,view的frame变化的时候,它会一直…