ARouter 源码分析

2023-10-31

ARouter基本使用

在开始分析源码之前,先了解一下ARoute如何使用的,使用ARoute可以概括为以下3步:

  1. 项目中引入ARouter 及配置

  2. 初始化ARouter

  3. 开始使用

下面详细的看下每一步怎么操作

项目中引入ARouter及配置

其实这一步就是导包,将ARouter 下载到本地,在app module下的build.gradle文件中 添加以下代码

plugins {
    ...
    id 'kotlin-kapt'
}




android {
    ...


    kapt {
        arguments {
            arg("AROUTER_MODULE_NAME", project.getName())
        }
    }


...
}


dependencies {
    implementation 'com.alibaba:arouter-api:x.x.x'
    kapt 'com.alibaba:arouter-compiler:x.x.x'
    ...
}

初始化ARouter

初始化很简单,只需要在项目的application类(我的是MyApplication)中添加下面代码即可

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()


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


    }
}

开始使用

只是界面跳转,使用起来还是挺简单地,我这里写了1个Activity,Test1Activity,要实现的功能就是从MainActivity跳转到Test1Activity。这里也可以分为两步:

  1. Test1Activity添加注解。

  2. MainActivity添加跳转代码。

Test1Activity添加的注解如下

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/Test1Activity")
class Test1Activity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test1)
    }
}
复制代码

MainActivity添加的跳转代码如下

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<TextView>(R.id.tv_test).setOnClickListener {
            // 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
            ARouter.getInstance().build("/test/Test1Activity").navigation()
        }
    }
}
复制代码

做完了上面的三步,就可以实现利用ARoute进行界面跳转的功能了。ARouter 的功能还是比较多的,可以点击这里,进行详细了解。

ARouter概览

经过上面的步骤,我们已经可以在项目中使用ARoute了,下面我们来看下ARouter项目的整体架构和实现路由跳转的主要流程。

ARouter 整体架构

ARouter 项目的代码结构,如下

a1bd358d636b8b591800bfa59caef5c4.jpeg

红框内的是ARouter的核心代码,为了方便理解,我画了一个ARouter代码架构图,如下

52ea20eb216400bf57277f6c304496aa.jpeg

可以发现,ARouter项目主要是围绕着生成和加载及解析路由表来编写的,现在已经对ARouter架构有了基本的印象,下面我们再看下,ARouter是怎么通过上面的架构来实现路由跳转的,

5358205396821c5cb9b03e9a1614dfb3.jpeg

从这幅图中,可以更加清晰的理解ARouter每个模块的职责与联系,当然到这里,也仅是列出了ARouter项目的架构和模块间的联系,还没有对ARouter整体的工作流程有个基础的认识,下面会介绍一下ARouter的工作流程。

ARouter整体的工作流程

一图胜千言,这里还是先用图来展示ARouter的工作流程,如下

8c6dd5c17b2359635bb13c0f010601e9.jpeg

这里只是ARouter跳转的主要流程,好多细节方面的知识下文会讲解,在深入源码分析之前,对流程先有个印象,方便下文源码的理解,也防止在源码的大海里迷失方向。

ARouter原理解析

到这里我们就正式开始ARouter的源码分析,为了防止在源码的大海里迷失,分析源码的顺序就按上面画出的工作流程图来一步步进行,首先看下路由文件的生成原理。

路由文件的生成

路由文件的生成是通过APT技术来实现的,如果不了解APT技术可以先去了解一下,不然这部分代码可能看不懂,不过最后会画一张这部分的流程图,方便理解记忆。

生成路由文件的主要包目录如图

3b9ac9355444547021419bd90250e7b1.jpeg

APT技术就是对特定的注解来做一些逻辑处理和自动生成文件,上图标出的Route就是注解,RouteProcessor就是用来处理注解的注解处理器,现在看下RouteProcessor主要代码,从程序入口开始看

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    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;
}
复制代码

接着看下parseRoutes方法的代码,如下

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
    ... 
    // Write root meta into disk.
    String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
    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);
    ...
}
复制代码

上面的是parseRoutes方法的部分代码,此方法的主要作用就是生成路由文件,生成的路由文件的格式如下

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("pac", 10); put("ch", 5); put("obj", 11); put("fl", 6); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}
复制代码

我用的是官方的demo,这个文件的位置如下图

6073697b90ddf1ae1741146da4553266.jpeg

可以看到生成的代码,是创建了RouteMeta实例,然后放到map中,那么这个RouteMeta是什么呢?在路由跳转中又有什么作用呢?

RouteMeta是什么?

从名字上来看是路由的元数据,可以猜测此类包含了路由的元信息,那么这个类是不是这样呢?看下代码

public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class<?> destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    private String name;


    private Map<String, Autowired> injectConfig;  // Cache inject config.
复制代码

上面的代码是RouteMeta的成员变量,这里我做了一个表格来解释每个成员变量的作用,如下

成员变量 释义
type 路线类型:路线类型 RouteType 是一个枚举类,类型有这几个:Activity、Service、Provider、ContentProvider、Fragment、Broadcast、Method、Unknown
rawType 路线原始类型:路由处理器 RouteProcessor 设定
destination 终点:声明了 @Route 的跳转目标的 Class ,比如目标 Activity 和 Fragment 的 Class,由 RouteProcessor 设定的。
path 路径:比如 path = /goods/details ,那么 goods 就是 group ,details 就是路径 path
group 路线组:如果在@Route注解中没有设置,那么就从设置的path中取值,设置的话就用设置的
priority 优先级:优先级在 @Route 中无法设定,是给拦截器用的,priority 的值越小,拦截器的优先级就越高
extra 标志:路由文档 RouteDoc 的标志
paramsType 参数类型:对于我们跳转时设定的参数,ARouter 会根据不同的类型给它们一个枚举值,然后取值时,再根据不同的类型调用 Intent 的 getXXXExtra() 等方法
name 路线名称
injectConfig 注入配置

从上面的表格中,可以看出RouteMeta确实是用来保存路由的元信息的,这里大家熟悉一下每个成员变量的作用,下文源码分析的时候还会出现。路由文件的生成原理就到这里,接着看下代码在运行时是如何加载路由表以及如何根据路由表的信息来做跳转的。

路由表的加载

先从ARouter初始化开始看,就是这句代码ARouter.init(this),看下init方法做了什么,代码如下

public static void init(Application application) {
        if (!hasInit) { // 首次执行会到这里
            ...
              // 进一步调用了_ARouter的init方法
            hasInit = _ARouter.init(application);


            if (hasInit) {
                _ARouter.afterInit();
            }
  ...
        }
    }

从代码中可以看到,调用了_ARouterinit方法,接着跟下去,代码如下

protected static synchronized boolean init(Application application) {
        ...
        // 主要是这句代码
        LogisticsCenter.init(mContext, executor);
        ...
        return true;
    }

跟到这里,出现了LogisticsCenter这个类,这个类是什么呢?根据类的名称翻译过来是 “物流中心” 的意思,继续看下它的init方法做了什么

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        try {
           ...
            // 首先从插件中加载路由表
            loadRouterMap();
            if (registerByPlugin) {
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                Set<String> routerMap;
        
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                  // 调试模式或则是新的版本,会重建路由表
                    // 这里就是从dex文件中查找“com.alibaba.android.arouter.routes”包下的类,放到map中
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                      // routerMap有内容的话,就把内容存到sp中
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }
                    // 保存新的版本号
                    PackageUtils.updateVersion(context);    
                } else {
                    // 直接从sp文件中拿路由表,就是前面保存到sp文件中的路由表
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }
              // 根据包目录,来实例化不同的对象并调用loadInto方法。
                for (String className : routerMap) {
                    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)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
            }
  ...
        } 
    }

从上面的代码可以看出,这段代码就是加载路由表的核心代码,上面有注释标出了一些代码的业务逻辑,这里再挑出几个比较难理解的地方重点讲解一下,首先是这句代码loadRouterMap();注释上说的是从插件中加载路由表,什么意思呢?就是如果我们想缩短ARouter初始化的时间,可以用 ARouter 的 Gradle 插件,这个插件能自动加载路由表,这样 ARouter 初始化的时候就不需要读取类的信息,从而缩短初始化时间。

插件的工作原理就是在代码编译的时候,插件会找到LogisticsCenter类的loadRouterMap方法,然后在方法中插入路由相关的代码,这样初始化的时候就不会从dex文件中扫描路由表了。

为了证明我不是胡诌的,这里贴出LogisticsCenter代码编译后的class文件截图,如下

2637590afb1e6d837cc086550ff517ab.jpeg

可以看到通过插件编译后,路由表已经插入到源码中。从源码中可以看出,加载路由表是有两种方式的,第一种就是刚才讲到的通过插件加载,第二种就是通过dex文件加载,通过dex文件加载路由表的方式已经 在上面的源码中进行注释了,简单了解下即可。

再回忆下,看这部分的源码是为了什么,是为了了解ARouter如何加载路由文件的,上面已经通过代码了解了如何加载的路由文件,为了方便理解和记忆这里还是用图来总结一下这部分的内容,如下

1143bce157f8da25fd02d861f2678f1e.jpeg

好了,路由表的加载原理到这里就结束了,下面开始研究路由表的跳转。

路由表的跳转

还是从官方的demo开路由表的跳转

4f0aa98715a368e929188409daab90ce.jpeg

跟进navigation方法,发现调用到了下面的代码

ea0762248ffdf8a55b5b116efefa7969.jpeg

继续跟进,最终调用到的是_ARouternavigation方法,如下

f54747f731efeea6007b15c5c652f7de.jpeg

现在看下_ARouternavigation方法的代码,如下

c4f47a4b7e573f555e5dd4183eba136c.jpeg

这里我把Postcard给标记出来了,为了更够更好的理解代码的原理,这里很有必要先搞清楚Postcard是什么。

Postcard是什么

Postcard翻译过来的意思是明信片,它的作用也和明信片的作用类似,里面保存的都是路由跳转的一些信息,可以看下它的成员变量

8c2c931fc69e7eaf032129a17924ca4d.jpeg

每个成员变量的作用,如下表

成员变量 释义
uri 统一资源标识符,可以用uri作为路径的跳转
tag 用于在 NavigationCallback 的 interrupt() 方法中获取异常信息
mBundle 调用 withString() 等方法设置要传递给跳转目标的数据时,这个数据就是放在 mBundle 中的
flags 调用 withFlag() 设定 Activity 的启动标志时,这个标志就会赋值给 flags 字段
timeout 拦截器链处理跳转事件是放在 CountDownLatch 中执行的,超时时间默认为 300 秒
provider 当我们实现了自定义服务时,参数注解处理器 AutowiredProcessor 会为各个路径创建一个实现注射器 ISyringe 接口的类,在这个类的 inject() 方法中,调用了 ARouter.getInstance().navigation(XXXService.class) ,当 LogisticsCenter 发现这是一个 Provider 时,就会通过反射创建一个 Provider 实例,然后设置给 Postcard ,再进行跳转。
greenChannel 所谓绿色通道,就是不会被拦截器链处理的通道,自定义服务 IProvider 和 Fragment 就是走的绿色通道
serializationService 当我们调用 withObject() 方法时,ARouter 就会获取我们自己自定义的序列化服务 SerializationService,然后调用该服务的 object2Json() 方法,再把数据转化为 String 放入 bundle 中
optionsCompat 转场动画
enterAnim/exitAnim 进入与退出动画

理解了Postcard的作用后,再看navigation方法的代码,就比较容易理解了。

接着看navigation方法的代码,如下

885c078ac4ad783bafe024599e0bfff0.jpeg

重点需要看的地方,已经标出来了,先看标注1的代码做了什么。

标注1的作用

主要代码如下

46528035171aea1384b112880de7acd3.jpeg这里标出了3处,还是一点点的解释

  • 标注1:从routeMeta中取值,设置到postcard属性中,还记的routeMeta是什么吗?就是在路由文件生成的时候生成的路由元数据,忘记的话,可以到前文再看下。

  • 标注2:解析Uri中的参数,设置到Bundle里。

  • 标注3:主要看绿色框的部分,当类型是PROVIDER和FRAGMENT的的时候,设置postcard的greenChannel。

这个方法的作用,总结起来就是完善postcard对象的属性。

标注2的原理

标注2其实比较简单的,就是判断是否是greenChannel,不是greenChannel的话,就进入拦截器中调用onInterrupt方法,是greenChannel的话就继续进_navigation(postcard, requestCode, callback)方法,这个方法的代码如下,

private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();


        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());


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


                // Non activity, need FLAG_ACTIVITY_NEW_TASK
                if (!(currentContext instanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }


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


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


                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case 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;
    }

这部分代码也很好理解,拿到类型之后,分别对对应的类型做处理是ACTIVITY是的话最后在主线程中调用startActivity跳转,是FRAGMENT的话就利用反射创建出Fragment的实例并返回。

总结

本篇文章首先介绍了ARouter的基本使用,然后整体的看了一下Arouter代码的框架,最后对ARouter的路由跳转功能进行原理分析,文章的主要内容也是对ARouter的跳转进行分析,ARouter的功能还是比较多的,感兴趣的话可以自己阅读源码,详细的了解下ARouter的原理。

作者:wizardev
链接:https://juejin.cn/post/7200041752470749243

关注我获取更多知识或者投稿

748ba36c8b0bf500bad00b8c4470cccd.jpeg

2b3e5374f45b1f734b59f2511fe473c0.jpeg

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ARouter 源码分析 的相关文章

随机推荐

  • Android开发之逐帧动画优化

    Android上如果使用逐帧动画的话 可以很方便地使用AnimationDrawable 无论是先声明xml还是直接代码里设置 都是几分钟的事 但使用AnimationDrawable有一个致命的弱点 那就是需要一次性加载所有图片到内存 万
  • go语言基础-----17-----channel创建、读写、安全关闭、多路复用select

    1 通道channel介绍 1 channel 可译为通道 是go语言协程goroutine之间的通信方式 2 channel通信可以想象成从管道的一头塞进数据 从另一头读取数据 通道作为容器是有限定大小的 满了就写不进去 空了就读不出来
  • 高防CDN对于网站、平台有着至关重要作用?

    1 减轻服务器的占用率和网站服务器的带宽资源 通过使用CDN服务 用户可以在CDN节点上分发对主要频道 包括页面和图片 的访问 这样可以减少源设备上的负载压力和带宽资源 并将资源保存到相同的带宽消耗服务中 如邮件 论坛和服务器资源 以保证网
  • 热区的使用方法

    1 如图所示 热区的位置是在元件库中 这样的一个标识 2 热区的使用经常会搭配一些比较小的文字或者图片等区域 只要是在热区中 随便点击哪一个地方都是属于这个区域 3 我们做了几个页面 样式不同 4 如图 我们创建一个热区在02的选区中 5
  • python + selenium实现巨潮资讯网指定范围年报下载

    大家好 第一次写文章 紧张滴捏 这段时间在做课设 课设里需要下载沪市600000到601000号的年报原文做数字化关键词的词频分析 想着用程序帮我批量下载一下 但是找了一下貌似没有类似的代码 就写了一个应用selenium库来做模拟下载的p
  • 各类打印机驱动官网下载安装

    前言概述 找驱动很简单 但是网上有时候找起来有点费劲呢 不安全 目前市面上打印机驱动搜索软件好用的基本都要付费 或者不全 比如下图这个就是付费的 常用的打印机品牌 惠普 HP 佳能 Canon 爱普生 Epson 京瓷 Kyocera 三星
  • 二流计算机学校,学校可以是二流的,但你不是

    我每天都会看大家在微博里给我的留言 时常看到深夜 私信的 每一条都看 问的最多的一种问题 是这么开头的 我的学校不好 或者 我是一个来自二 三 本学校的学生 我该怎么办 我不知道怎么回答 因为我不觉得来自一个二流的学校就应该过着二流的生活
  • C语言实现队列(链表实现)

    队列 Queue 也是运算受限的线性表 是一种先进先出 First In First Out 简称 FIFO 的线性表 只允许在表的一端进行插入 而在另一端进行删除 队首 front 允许进行删除的一端称为队首 队尾 rear 允许进行插入
  • 浅析Linux内核中的链表

    1 内核中的链表 linux内核链表与众不同 他不是把将数据结构塞入链表 而是将链表节点塞入数据 在2 1内核中引入了官方链表 从此内核中所有的链表使用都采用此链表 千万不要在重复造车轮子了 链表实现定义在
  • 第五届蓝桥杯Java A组决赛试题

    1 标题 海盗分金币 有5个海盗 相约进行一次帆船比赛 比赛中天气发生突变 他们被冲散了 恰巧 他们都先后经过途中的一个无名的荒岛 并且每个人都信心满满 觉得自己是第一个经过该岛的人 第一个人在沙滩上发现了一堆金币 他把金币分成5等份 发现
  • STM32CubeMX驱动MPU6050模块

    文章目录 1 MPU6050模块简介 2 MPU6050重要寄存器介绍 2 1 数字低通滤波器配置寄存器CONFIG 2 2 采样率分频寄存器SMPRT DIV 2 3 加速度计配置寄存器ACCEL CONFIG 2 4 角速度计配置寄存器
  • 区别:OrderedDict vs dict

    OrderedDict 和 dict 是两种字典类型 都用于存储键值对 key value pairs dict 是Python的内置字典类型 它是无序的 即它不会按照元素插入的顺序来保存键值对 当你通过键来访问 dict 中的值时 Pyt
  • sqoop query时单双引号选用以及$CONDITION使用的探究

    这段时间碰见了一个奇怪的sqoop导入问题 我的业务目标是想将postgresql库里的某张表内的数据导入到hive里 而且在导入的时候需要做一步查询 但在导入的时候 围绕着 CONDITION 这个参数 会有不同的运行结果 有的报错 有的
  • MySQL基础架构与日志详解

    一 MySQL基础架构 MySQL可以分为Server层和存储引擎层两部分 Server层包括连接器 查询缓存 分析器 优化器 执行器等 涵盖MySQL的大多数核心服务功能 以及所有的内置函数 如日期 时间 数学和加密函数等 所有跨存储引擎
  • 软件需求之DFD图

    DFD图是一种以数据和数据的封闭性为基础 从问题空间到某种表示的映射方法 是一种结构化分析方法 DFD图在软件的需求分析中发挥着不可替代的作用 DFD图在软考中是必考的内容 在软件工程中也是一个非常中要的图 下面是我结合网上的资料和自己的所
  • Linux系统里压缩PDF文件大小

    sudo apt get install ghostscript gs sDEVICE pdfwrite dCompatibilityLevel 1 4 dPDFSETTINGS screen dNOPAUSE dQUIET dBATCH
  • JavaFX 控件 ImageView

    ImageView 支持格式 BMP GIF JPEG PNG 加载图片 如果设置了 requestedXXX 尺寸 ImageView中 设置 FitXXX 尺寸是基于requestedXXX 尺寸缩放 Image image new I
  • Linux & Docker常用命令

    目录 一 Docker服务相关命令 二 镜像相关命令 查看镜像 查看本地所有的镜像 搜索镜像 从网络中查找需要的镜像 拉取镜像 删除镜像 三 容器相关命令 查看容器 创建容器 进入容器 启动容器 停止容器 重启应用 删除容器 查看容器信息
  • OpenCV教程——加载、修改、保存图像

    1 颜色空间 颜色空间 也称彩色模型 又称彩色空间或彩色系统 本质上 彩色模型是坐标系统和子空间的阐述 位于系统的每种颜色都有单个点表示 RGB 红绿蓝 是依据人眼识别的颜色定义出的空间 可表示大部分颜色 但在科学研究中一般不采用RGB颜色
  • ARouter 源码分析

    ARouter基本使用 在开始分析源码之前 先了解一下ARoute如何使用的 使用ARoute可以概括为以下3步 项目中引入ARouter 及配置 初始化ARouter 开始使用 下面详细的看下每一步怎么操作 项目中引入ARouter及配置