ARouter——原理分析整理

组件化框架设计之阿里巴巴开源路由框架——ARouter原理分析(一) 阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 背景 当项目的业务越来越复杂,业务线越来越多的时候,就需要按照业务线去分不同的模块去开发,这样专门的人负责专门的业务模块,最终上线由壳工程去负责进行组合打包各个module,完成业务的快速迭代。整个过程会涉及到各个模块间进行通信,比如订单模块和个人中心模块,可能会... 阅读详情

ARouter

概述

在每个需要对其他module提供调用的Activity中,都要声明类似下面@Route注解,我们称之为路由地址。

@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {}
@Route(path = "/module1/module1main")
public class Module1MainActivity extends AppCompatActivity {}

现在我们有三个module

  • APP——主工程
  • QRCode——扫码Module
  • Gallery——项目模块

对于其他module需要使用ARouter跳转至QRCode Activity时

我们需要声明一个QRCode Activity的路由地址

public static final String QRCODE_SCAN_PATH = "/qrcode/scan";

然后在对应的QRCode的Activity上添加注解

@Route(path = RouteHub.QRCode.QRCODE_SCAN_PATH)

然后调用以下方法就可以完成跳转了

ARouter.getInstance().buildRouteHub.QRCode.QRCODE_SCAN_PATH).navigation(this)

这样,两个模块不用相互有任何直接的依赖,就可以进行转跳,模块与模块之间就相互独立了。

原理

APT

ARouter的使用非常方便,得益于APT。

APT的作用是在编译阶段扫描并处理代码中的注解,然后根据注解输出Java文件。

ARouter为了方便实现注解处理器还额外用了两个库。

一个是JavaPoet,他提供了调用对象方法的方式生成需要的代码,而不再需要人为的用StringBuilder去拼接代码,再使用IO写入文件。

第二个是Auto-Service,他提供了简便的方式去注册APT,避免了原本繁琐的注册步骤。

@Route

Route的定义:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route{
    /**
     * Path of route
     */
    String path();
    ……
}
  • @Target({ElementType.TYPE})——表示这个annotation是修饰类的
  • @Retention(RetentionPolicy.CLASS)——表示需要保留到编译时

Route中有一个主要的参数path,他表示了Activity的路由地址。

@Route(path = RouteHub.QRCode.QRCODE_SCAN_PATH)

这样编译时能获取到Route所注解的类,并且能获取到path路径。

RouteProcessor

RouteProcessor是对@Route处理的地方。

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor

解释

  • auto-service——这个库为Processor完成了自动注册
  • @SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})——表明了当前Processor是出里那些注释的

RouteProcessor继承于BaseProcessor,在init方法中获取到了每个模块的moduleName。


// Attempt to get user configuration [moduleName]
Map<String, String> options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
  moduleName = options.get(KEY_MODULE_NAME);
  …………
}

RouteProcessor的process方法是对注解处理的地方,它直接获取了所有Route注解的元素。

Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);

拿到被标注的元素后就会进入this.parseRoutes(routeElements);方法。这个方法使用JavaPoet生成Java文件。如果不用这个库我们也可以使用StringBuilder去写Java文件的内容。

我们先来看看他生成的类。

IRouteGroup


public class ARouter$$Group$$qrcode implements IRouteGroup {
  @Override
  public void loadInto(Map<String,RouteMeta> atlas) {
    atlas.put("/qrcode/scan",
      RouteMeta.build(
        RouteType.ACTIVITY, 
        ScanQRActivity.class,
        "/qrcode/scan", 
        "qrcode", 
        new java.util.HashMap<String,Integer>(){{put("tag", 8); }}, -1, -2147483648));
  }
}

关于RouteMeta: RouteMeta是包含了@Route所注解的元素的必要信息,最明显的就是ScanQRActivity.class,有了它,我们就可以通过Intent跳转到这个Activity了。

ARouter$$Group$$qrcode这个类继承自IRouteGroup ,他实现了接口中的loadInto方法。

loadInto方法逻辑很简单,传入一个map,将注解的path值作为key,将元素(RouteMeta)作为value作为Value放入map。

如果完成了这个方法,就完成了Activity的注册。

IRouteRoot

RouteProcessor还产生了ARouter$$Root$$qrcode这个类

public class ARouter$$Root$$qrcode implements IRouteRoot {
  @Override
  public void loadInto(Map<String,Class<? extends IRouteGroup>> routes) {
    routes.put("qrcode",ARouter$$Group$$qrcode.class);
  }
}

它实现了IRouteRoot接口,内容非常相似。通过loadInto方法,往Map中插入以group名为Key,IRouteGroup实现类为Value的内容。

group默认就是path中第一个斜杠之后的内容(@Route(path="/group/xxx"))

如果调用了这个方法,那么可以通过group拿到IRouteGroup实现类的class,有了class实例化之后就能通过前面所说的拿到Activity.class了。

整体的结构是下图的样子

在这里插入图片描述

我们看到,其实直接可以从IRouteGroup 传入的Map获得我们的RouteMeta,那么为什么还需要IRouteRoot的loadInto再把IRouteGroup放到Map里呢。这个大家后面就会知道。

process

回过头来再看RouteProcessor的process方法:

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironmentroundEnv) {
  if(CollectionUtils.isNotEmpty(annotations)) {
    //获取所有Route注解元素
    Set<? extends Element>routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
    try {
      this.parseRoutes(routeElements);
    } catch (Exception e) {
      logger.error(e);
    }
    return true;
  }
  return false;
}

process方法将所有的Route注解元素放进了parseRoutes方法用于生成IRouteGroup和IRouteRoot。

这里面使用JavaPoet提供的类,通过方法调用的形式生成代码。

生成代码

使用了JavaPoet提供的类,通过方法调用的形式生成代码,这里看的不是太懂,简单过一遍。

构建IRootGroup的loadInto方法

/*
Build method : 'loadInto'
*/
MethodSpec.BuilderloadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
  .addAnnotation(Override.class)
  .addModifiers(PUBLIC)
  .addParameter(rootParamSpec);

方法体内容生成


for (Element element : routeElements) {
  …………
  if (types.isSubtype(tm, type_Activity)) {                 
    // Activity
    logger.info(">>> Found activity route: " + tm.toString() + " <<<");
    // Get all fields annotation by @Autowired
    Map<String, Integer> paramsType = new HashMap<>();
    Map<String, Autowired> injectConfig = new HashMap<>();
    for (Element field : element.getEnclosedElements()) {
      if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !types.isSubtype(field.asType(), iProvider)) {
        // It must be field, then it has annotation, but it not be provider.
        Autowired paramConfig = field.getAnnotation(Autowired.class);
        String injectName = StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name();
        paramsType.put(injectName, typeUtils.typeExchange(field));
        injectConfig.put(injectName, paramConfig);
      }
    }
    routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
    routeMeta.setInjectConfig(injectConfig);
  }
  …………
  categories(routeMeta);
}

这里完成了RouteMeta类的构建,还获取了Activity通过@AutoWired注解的接收参数,但是不影响我们理解跳转。然后通过categories(routeMeta)方法,对所有RouteMeta进行分组。

private void categories(RouteMeta routeMete) {
    if (routeVerify(routeMete)) {
      logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
      Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
        if (CollectionUtils.isEmpty(routeMetas)) {
          Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {
            @Override
            public int compare(RouteMeta r1, RouteMeta r2) {
              try {
                return r1.getPath().compareTo(r2.getPath());
              } catch (NullPointerException npe) {
                logger.error(npe.getMessage());
                return 0;
              }
            }
          });
          routeMetaSet.add(routeMete);
          groupMap.put(routeMete.getGroup(), routeMetaSet);
        } else {
          routeMetas.add(routeMete);
        }
    } else {
        logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");
    }
}

所有的RouteMeta会根据其group作为Key放入一个Map中。RouteMeta本身会放入一个Set,Set中所有RouteMeta的group都是相同的,作为Map的Value。

我们依据将所有的Route根据group进行了分类,并存在的Map中,结构如下。

在这里插入图片描述
有了这个Map,再进行IRouteGroup和IRouteRoot的方法体实现就方便了。对分组的Map进行遍历,根据不同分组,再对Set中的RouteMeta进行遍历,依次添加loadInto代码实现。

//遍历Map
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
    String groupName = entry.getKey();
    MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
            .addAnnotation(Override.class)
            .addModifiers(PUBLIC)
            .addParameter(groupParamSpec);
    …………
    // Build group method body
    Set<RouteMeta> groupData = entry.getValue();
    //遍历Set
    for (RouteMeta routeMeta : groupData) {
        …………
        ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());
        …………
        // Make map body for paramsType
        StringBuilder mapBodyBuilder = new StringBuilder();
        Map<String, Integer> paramsType = routeMeta.getParamsType();
        Map<String, Autowired> injectConfigs = routeMeta.getInjectConfig();
        if (MapUtils.isNotEmpty(paramsType)) {
            …………
            for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
                …………
            }
            …………
        }
        String mapBody = mapBodyBuilder.toString();
        //生成IRouteGroup 的loadInto方法体实现
        loadIntoMethodOfGroupBuilder.addStatement(
                "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                routeMeta.getPath(),
                routeMetaCn,
                routeTypeCn,
                className,
                routeMeta.getPath().toLowerCase(),
                routeMeta.getGroup().toLowerCase());
                …………
    }
    // Generate groups
    //生成ARouter$$Group$$qrcode implements IRouteGroup类和Java文件
    //NAME_OF_GROUP定义在Consts,为"Arouter$$Group$$"
    String groupFileName = NAME_OF_GROUP + groupName;
    //groupFileName在我们的案例中为"Arouter$$Group$$qrcode"
    //PACKAGE_OF_GENERATE_FILE为包名,定义在Consts,为"com.alibaba.android.arouter.routes"
    //代码文件全部生成完成
    JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
            TypeSpec.classBuilder(groupFileName)
                    .addJavadoc(WARNING_TIPS)
                    .addSuperinterface(ClassName.get(type_IRouteGroup))
                    .addModifiers(PUBLIC)
                    .addMethod(loadIntoMethodOfGroupBuilder.build())
                    .build()
    ).build().writeTo(mFiler);

    logger.info(">>> Generated group: " + groupName + "<<<");
    //分组名和文件名存入Map,以便后续生成IRouteRoot
    rootMap.put(groupName, groupFileName);
    …………
}

到这里,我们已经生成了ARouter$$Group$$qrcode的Java文件。同样的逻辑会生成ARouter$$Root$$qrcode的Java文件。

if (MapUtils.isNotEmpty(rootMap)) {
  // Generate root meta by group name, it must be generated before root, then I can find out the class of group.
  for (Map.Entry<String, String> entry : rootMap.entrySet()) {
        //生成loadInto具体实现
        loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
    }
}
……
// Write root meta into disk.
// NAME_OF_ROOT为"ARouter$$Root$$"
String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
//rootFileName在我们的案例中为ARouter$$Root$$qrcode
//代码文件全部生成完成
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
        TypeSpec.classBuilder(rootFileName)
                .addJavadoc(WARNING_TIPS)
                .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
                .addModifiers(PUBLIC)
                .addMethod(loadIntoMethodOfRootBuilder.build())
                .build()
).build().writeTo(mFiler);    

IRouteRoot生成的过程中和IRouteGroup类不同的地方是文件名最后用的是moduleName,这个变量之前分析过就是在gradle中ARouter要求配置的参数,用module名字的原因是能够避免工程庞大后命名冲突的可能。

到这里就完成了两个类的生成工作。那么ARouter是怎么去用这两个类的呢?前面提出的问题,有了IRouteGroup就能拿到路由表,为什么还需要IRouteRoot再吧IRouteGroup包一层呢?

路由跳转

ARouter.init(this)是一个初始化的地方。一路下去,会调用到LogisticsCenter的init方法。

路由表的生成有两种方式,我们看ARouter最传统的方式。如果是设置了debug模式或者App版本与记录的不同的情况下,扫描所有Dex文件查找所有com.alibaba.android.arouter.routes开头的文件,然后更新到SharePreferences。否则直接从SharePreferences读缓存,减少解析时间。


if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
    // These class was generated by arouter-compiler.
    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    if (!routerMap.isEmpty()) {
        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
    }
    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
} else {
    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>()));
}

解析完成后,进行类名遍历,将所有com.alibaba.android.arouter.routes.Arouter$$Root开头的类反射出来,实例化,并调用loadInto方法加到Warehouse.groupsIndex这个Map中,也就是拿到了所有group和对应的IRouteGroup。


if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
  //com.alibaba.android.arouter.routes.Arouter$$Root
  // This one of root elements, load root.
  ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
}

Warehouse.groupsIndex结构如下:
在这里插入图片描述

这些IRouteGroup所对应的类什么时候解析呢?我们来看会发生跳转的navigation方法,最终会走到navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback),里边会立马调用LogisticsCenter.completion(postcard)方法。

方法里会先尝试根据path去取解析过的RouteMeta,但是很明显,我们这边还没有解析过。

RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

routeMeta 为null,于是从Warehouse.groupsIndex去取对应的IRouteGroup,拿到他就可以通过他的loadInto去加载对应的Activity信息了。

Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());

可以看到,会把我们请求跳转的path所在group的内容全部加载到Warehouse.routes下,然后从Warehouse.groupsIndex移除group对应的IRouteRoot,因为他已经没有用了,该分组下的所有内容都被加载上来了。于是移除,省出内存。

可以看出,同一分组下的所有RouteMeta的注册是在其中一个第一次被请求跳转的时候完成的。Warehouse.routes的结构如下:

在这里插入图片描述
有了IRouteGroup就能拿到路由表,为什么还需要IRouteRoot再吧IRouteGroup包一层呢?

启动应用,然后立马遍历所有com.alibaba.android.arouter.routes开头的文件,分析出是Root的文件,反射实例化,调用loadInto方法,将IRouteGroup.class载入。这几步里好多是耗时的方法,如果工程很庞大,像淘宝这样,如果一上来直接加载所有的IRouteGroup中的内容,恐怕要非常长的时间,会导致启动慢,或者ANR。

这也是为什么ARouter需要IRouteRoot存在的原因,他可以延时加载所需要的分组,第一次使用的时候加载可以分摊加载时间。

路由表加载有两种方式,我们分析的是最传统的一种。那另一种呢

就是使用com.alibaba:arouter-register了,它底下使用的是arouter-auto-register这个库,它能够在编译阶段扫描IRouteRoot的实现类,注册到LogisticsCenter。用这种方式能够避免应用开启后再扫描生成路由表的开销。而且能够避免在某些加壳工具加固后无法获取到dex文件中的com.alibaba.android.arouter.routes包下的内容,导致无法生成注册表的问题。

继续看最后的跳转工作。

这里拿到完整信息的Postcard会创建Intent,进行跳转

final Intent intent = new Intent(currentContext, postcard.getDestination());
// Navigation in main looper.
runInMainThread(new Runnable() {
  @Override
  public void run() {
    startActivity(requestCode, currentContext, intent, postcard, callback);
  }
});

跳转会被安排到主线程。

参考:https://mp.weixin.qq.com/s?src=11&timestamp=1573461757&ver=1967&signature=t0yrs2jjQBJf9V-xMEdHkuT85qPnsuoaRJDjxF5Csj4PYh00rVEv3wx33ECWQn-2N5tZBSY02PpgwZbuFy6wV-p9M3yIvT9QeFnzS7hW0OHQ6iSmblE5CVekRDhxHj&new=1

【Android知识笔记】ARouter / Navigation / EventBus Android ARouter、Navigation、EventBus相关的知识笔记。 阅读详情

相关推荐

ARouter依赖问题:AGPBI: {“kind“:“error“,“text“:“Type com.alibaba.android.arouter...如何解决?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1219

条码扫描二维码扫描——ZXing android 源码简化

前言   最近公司的Android项目需要用到摄像头做条码或二维码的扫描,Google一下,发现一个以Apache License 2.0开源的ZXing项目。Zxing项目里的Android实现太过复杂多余东西太多,得对其进行简化。 前提条件   下载源代码:点击这里   编译核心库:Zxing的主页上有介绍具体步骤,大家也可以参照这篇博文:android 条码识别软件开发全解析(续...

weixin_30593261的博客 389

Service Activity的三种交互方式(详解)

下面小编就为大家带来一篇Service Activity的三种交互方式(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Android 进阶 - Activity服务启动分析

上篇文章已经wj 本文将从一个Activity客户端

szzhaom的专栏 4442

Ubuntu安装pytorch3d的最便捷方法【原创】

Ubuntu安装pytorch3d的最便捷方法

qq_54900679的博客 2924

接口调试必看:status显示canceled的3种常见原因及修复方法

本文深度解析了接口调试中status显示canceled的三种核心原因:网络层中断(如请求超时)、应用层逻辑主动取消(如请求竞态、组件卸载)以及用户交互与浏览器行为。文章提供了从诊断到修复的系统性方案,包括分析Network Timing、使用Axios CancelToken、优化请求策略与性能,帮助开发者从根本上解决这一常见问题。

bb456的博客 936

Android 菜鸟进击之路 (5)Activity 未完待续

参考学习地址: https://www.runoob.com

weixin_43892021的博客 261

(超详细)Scala环境搭建(scala-2.12.15、IDEA2021.1.3)

Scala环境搭建(scala-2.12.15、IDEA2021.1.3) JunLeon——go big or go home 目录 Scala环境搭建(scala-2.12.15、IDEA2021.1.3) 一、Scala概述 1、什么是Scala? 2、Scala发展史 3、Scala与Java的关系 二、Scala环境搭建 1、环境准备 2、Scala安装与配置环境变量 3、验证Scala 三、编写第一个Scala程序 1、使用IDEA创建maven工程 2、.

JunLeon的博客 3万+

Android activity/service 开机启动

1. 在AndroidManifest.xml中添加权限和监听的intent. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.oms.netwapswitch" android:versionCode="1" android:versionN

阿强的一亩三分地,一分耕耘一分收获 1422

YOLO26优化:特征融合 | 一种新颖的多尺度特征融合iAFF,适配小目标检测

在YOLO26中如何使用:iAFF加入Neck替代Concat;

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 269

2026年佰维存储数字IC笔试试卷带答案

建立时间是有效时钟沿到来前,输入数据必须稳定保持的最短时间。保持时间是有效时钟沿到来后,输入数据必须继续稳定保持的最短时间。

Open 103

学习笔记03.浅析service

作为Android四大组件之一,service在每一个应用程序中都扮演着非常重要的角色。它主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候 我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。 一、新建一个service 新建一个service很简单,只需要让我们自定义的MyService继承Service类并复写主要的几个方法(onCrea

PickUp_Artist的博客 796

Siemens SIMATIC软件二次开发:使用C#进行SIMATIC软件二次开发

通过使用OPC-UA通信协议和S7通信库,可以有效地在C#应用程序中与Siemens SIMATIC软件进行数据交换。OPC-UA提供了一个标准化的平台,而S7通信库则提供了直接访问PLC数据的能力。掌握这两种通信方式,将极大地增强在工业自动化领域的二次开发能力。在Siemens SIMATIC软件中,自定义功能块开发是实现二次开发的关键步骤之一。通过C#,可以创建高度定制化的功能块,以满足特定的控制需求。下面,我们将通过一个示例来展示如何使用C#开发一个自定义功能块。OPC-UA通信。

kkchenjj的博客 1414

攒了一个月的Android面试题及详细解答,年底准备起来,冲刺大厂单车变摩托!(下篇)

我这里整理了一份完整的学习思维以及Android开发知识大全PDF。当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化学习资料的朋友,可以戳这里获取一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

2401_84545364的博客 841

【论文复现】水下单光子激光雷达设计 Sequential Multimodal Underwater Single

针对对慢速小水下目标进行远距离、高分辨率重建的需求,单光子激光雷达(LiDAR)目标重建技术的研究正在进行。本文报告了一种基于时空序列融合的顺序多模态水下单光子 LiDAR 自适应目标重建算法,该算法具有强大的信息提取和噪声过滤能力,能够从复杂的回波光子计数和空间像素关系中重建目标的深度和反射强度信息。该方法包括三个步骤:数据预处理、序列优化极值推断滤波以及图像优化协同变分策略,以实现复杂水下环境下的高质量目标重建。

u012133341的博客 292

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Android菜菜进字节跳动,居然是看了这个......

谈谈我的真实感受吧~ 程序员真的是需要将终生学习贯彻到底的职业,一旦停止学习,离被淘汰,也就不远了。 金三银四、金九银十跳槽季,这是一个千年不变的话题,每到这个时候,很多人都会临阵磨枪,相信不快也光。于是,大家都会搜集一些面试题。 但其实,对于一些不跳槽的同学来说,真的也很有必要去研读一些大厂的面试题。了解一下最近的主流技术,看看大厂在意的点是什么,再在自己的工作中去完善,去总结。 于是,我们花费了几个月的时间,整理出了Android大厂题(附带详细答案)。 2017-2020字节跳动 Android面试真

Android23333的博客 3983

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

阿里巴巴开源路由框架——ARouter原理分析

转载请注明出处:https://blog.youkuaiyun.com/binbinqq86/article/details/80927885 背景 当项目的业务越来越复杂,业务线越来越多的时候,就需要按照业务线去分不同的模块去开发,这样专门的人负责专门的业务模块,最终上线由壳工程去负责进行组合打包各个module,完成业务的快速迭代。整个过程会涉及到各个模块间进行通信,比如订单模块和个人中心模块,可能会需要频繁的传递数据和页面跳转,这个时候怎么去处理呢?我们能想到的方案就是采用类名反射,来动态创建需要跳转和交互的类,

Water的博客 4764

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Android路由方案ARouter分析

一、路由方案 原生的路由方案缺点: 显式:直接的类依赖,耦合严重 隐式:规则集中式管理,协作困难 Manifest扩展性较差 跳转过程无法控制 失败无法降级 ARouter的优势: 使用注解,实现了映射关系自动注册 与 分布式路由管理 编译期间处理注解,并生成映射文件,没有使用反射,不影响运行时性能 映射关系按组分类、多级管理,按需初始化 灵活的降级策略,每次跳转都会回调跳转...

LRH0211的博客 865

ARouter学习之一——框架的使用

按照ARouter学习之三——注解解析器(arouter-compiler源码)的结尾,似乎这应该是学习之四,但我选择标题起为之一,虽说是后面才写,但毕竟学习的话都是先学会用,再去弄懂原理。而总结顺序,则是根据好奇心的顺序来整理。 本文的初衷:搞懂在项目中怎样引入ARouter并加以使用 目录 引入ARouter建立依赖 各类简单的跳转 目标界面接收参数 ARouter进阶高级用法 ...

bendan50的博客 2581

Android路由方案ARouter超级详解

一、路由方案 原生的路由方案缺点: 显式:直接的类依赖,耦合严重 隐式:规则集中式管理,协作困难 Manifest扩展性较差 跳转过程无法控制 失败无法降级 ARouter的优势: 使用注解,实现了映射关系自动注册 与 分布式路由管理 编译期间处理注解,并生成映射文件,没有使用反射,不影响运行时性能 映射关系按组分类、多级管理,按需初始化 灵活的降级策略,每...

weixin_33928137的博客 767

Android ARouter、APT、Glide相关面试题

我整理了一套Android面试题合集,除了以上面试题,还包含【Java 基础、集合、多线程、虚拟机、反射、泛型、并发编程、Android四大组件、异步任务和消息机制、UI绘制、性能调优、SDN、第三方框架、设计模式、Kotlin、计算机网络、系统启动流程、Dart、Flutter、算法和数据结构、NDK、H.264、H.265.音频编解码、FFmpeg、OpenMax、OpenCV、OpenGL ES。

datian1234的博客 503

Android ARouter 处理器模块深度剖析(三)

ARouter 的处理器模块主要在编译期发挥作用,它会扫描项目中使用 ARouter 注解(如@Route@Provider等)的类和方法,解析注解中的信息,并根据这些信息生成相应的 Java 代码文件。这些生成的代码文件包含了路由表、服务表、拦截器表等重要数据结构,在应用运行时,ARouter 可以直接使用这些数据进行路由跳转和服务调用,避免了在运行时进行大量的反射操作,从而提高了应用的性能和稳定性。ARouter 的处理器模块包含多个注解处理器类,其中最重要的是类,它负责处理@Route注解。以下是。

Android开发领域创作者 1648

揭秘ARouter路由机制,源码+原理+手写框架

导语 本部分内容是关于Android进阶的一些知识总结,涉及到的知识点比较杂,不过都是面试中几乎常问的知识点,也是加分的点。 关于这部分内容,可能需要有一些具体的项目实践。在面试的过程中,结合具体自身实践经历,才能更加深入透彻的描绘出来。 题库非常全面包括: Android基础知识: 基本涵盖Android所有知识体系,四大组件,Fragment,WebView,事件分发,View绘制… Java基础知识&高阶知识点: 基础部分不谈了,高阶部分:泛型,反射,Java虚拟机… 算法与数据结构: 链

chayel123的博客 185
上一篇: Glide——缓存分析整理
下一篇: Android——内存泄漏及OOM整理
PdKingLiu
博客等级 码龄9年 89粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值