[转]RxHttp 一条链发送请求,新一代Http请求神器(一)

 

简介

 

RxHttp是基于OkHttp的二次封装,并于RxJava做到无缝衔接,一条链就能发送一个完整的请求。主要功能如下:

 

  • 支持Get、Post、Put、Delete等任意请求方式,可自定义请求方式
  • 支持Json、DOM等任意数据解析方式,可自定义数据解析器
  • 支持文件下载/上传,及进度的监听,并且支持断点下载
  • 支持在Activity/Fragment的任意生命周期方法,自动关闭未完成的请求
  • 支持添加公共参数/头部信息,且可动态更改baseUrl
  • 支持请求串行和并行

 

gradle依赖

 

implementation 'com.rxjava.rxhttp:rxhttp:1.0.4'
//注解处理器,生成RxHttp类,即可一条链发送请求
annotationProcessor 'com.rxjava.rxhttp:rxhttp-compiler:1.0.4'
//管理RxJava及生命周期,Activity/Fragment 销毁,自动关闭未完成的请求
implementation 'com.rxjava.rxlife:rxlife:1.0.4'

 

RxHttp 源码
RxLife 源码

 

初始化

 

//设置debug模式,此模式下有日志打印
HttpSender.setDebug(boolean debug)
//非必须,只能初始化一次,第二次将抛出异常
HttpSender.init(OkHttpClient okHttpClient)
//或者,调试模式下会有日志输出
HttpSender.init(OkHttpClient okHttpClient, boolean debug)

 

此步骤是非必须的,不初始化或者传入null即代表使用默认OkHttpClient对象。

 

疑问:标题不是说好的是RxHttp,这么用HttpSender做一些初始化呢?这里先卖一个关子,后面会解答

 

添加公共参数/头部及重新设置url

 

相信大多数开发者在开发中,都遇到要为Http请求添加公共参数/请求头,甚至要为不同类型的请求添加不同的公共参数/请求头,为此,RxHttp为大家提供了一个静态接口回调,如下,每发起一次请求,此接口就会被回调一次,并且此回调在子线程进行(在请求执行线程回调)

 

HttpSender.setOnParamAssembly(new Function() {@Overridepublic Param apply(Param p) { if (p instanceof GetRequest) {//根据不同请求添加不同参数} else if (p instanceof PostRequest) {} else if (p instanceof PutRequest) {} else if (p instanceof DeleteRequest) {}//可以通过 p.getSimpleUrl() 拿到url更改后,重新设置//p.setUrl("");return p.add("versionName", "1.0.0")//添加公共参数.addHeader("deviceType", "android"); //添加公共请求头}
});

 

然后有些请求我们不希望添加公共参数/请求头,RxHttp又改如何实现呢?很简单,发起请求前,设置不添加公共参数,如下:

 

Param param = Param.get("http://...")//设置是否对Param对象修饰,即是否添加公共参数,默认为true.setAssemblyEnabled(false); //设为false,就不会回调上面的静态接口

 

到这,也许你们会有疑问,Param 是什么东东,下面就为大家讲解。

 

Param

 

首先,我们来看看如何发送一个请求

 

Param param = Param.get("http://...").add("key", "value");
Disposable disposable = HttpSender.from(param).subscribe(s -> { //这里的s为String类型,即Http请求的返回结果//成功回调}, throwable -> {//失败回调});

 

疑问:说好的一条链发送请求呢?别着急,还没到放大招的时候
到这,我可以告诉大家,Param承担的是一个请求体的一个角色,我们通过Param可以确定请求方式(如:Get、Post、Put、Delete等请求方式)、添加请求参数、添加请求头、添加File对象等;然后通过HttpSender,传入Param对象,将请求发送出去。

 

HttpSender

 

到这,有人又有疑问,前面初始化、设置公共参数都用到了HttpSender,这里发送请求又用到了HttpSender ,那么它又是承担怎么样的一个角色呢?看名字,我们可以理解为它就是一个请求发送者,通过一个from操作符,传入一个Param对象,然后返回一个RxJavaObservable对象,此时,我们就可以使用RxJava强大的操作符去处理相关的逻辑(这就是简介说的,做到了与RxJava的无缝链接),在这,我们只是使用了subscribe操作符去订阅观察者。

 

RxHttp

 

现在,我们正式放大招,标题说好的一条链发送请求,既然吹牛了,就要去实现它。拿上面的例子,看看我们如何一条链实现,上代码

 

  RxHttp.get("http://...").add("key", "value").from()  .subscribe(s -> { //这里的s为String类型,即Http请求的返回结果//成功回调}, throwable -> {//失败回调});

 

我们的主角RxHttp终于登场了,可以看到使用RxHttp类我们就实现了一条链完成请求的发送,那它又是承担一个什么角色呢?我们暂时可以理解为RxHttp=Param+HttpSender,并且还有自己特殊的使命。至于什么使用,后面会讲解。

 

我们现在来解疑惑,为什么我们的库叫RxHttp,但是初始化、设置公共参数等却用HttpSender?因为RxHttp这个类不在RxHttp库中,它是通过注解处理器生成的类。前面我们看到gradle依赖时,使用了

 

annotationProcessor 'com.rxjava.rxhttp:rxhttp-compiler:1.0.2'

 

该注解处理器的目的就是在项目中生成RxHttp类,那为何不直接把它写到库里面去呢?前面讲过,因为它有自己的使命,而这个使命,就是我们可以通过注解,在RxHttp中生成自定义的api,我们来看看如何使用注解。

 

动态设置baseUrl

 

现实开发中,大部人开发者都会将baseUrl 单独抽取出来,RxHttp也考虑到了这一点,RxHttp通过@DefaultDomain注解来配置baseUrl,看代码

 

public class Url {@DefaultDomain() //设置为默认域名public static String baseUrl = "http://ip.taobao.com/";
}

 

rebuild一下项目,此时我们发送请求就可以直接传入path路径,如下:

 

  RxHttp.get("/service/getIpInfo.php").add("key", "value").from()  .subscribe(s -> { //这里的s为String类型,即Http请求的返回结果//成功回调}, throwable -> {//失败回调});

 

RxHttp在发送请求前,会对url做判断,如果没有域名,就会自定加上默认的域名,也就是baseUrl。然后,如果我们不想使用默认的域名呢?RxHttp也考虑到来,提供了一个@Domain注解,我们再来看看用法:

 

public class Url {@Domain(name = "Update9158") //设置非默认域名,name 可不传,不传默认为变量的名称public static String update = "http://update.9158.com";@DefaultDomain() //设置为默认域名public static String baseUrl = "http://ip.taobao.com/";
}

 

此时再rebuild一下项目,就会在RxHttp类中生成一个setDomainToUpdate9158IfAbsent()方法,其中的Update9158字符就是name指定的名字,然后发请求就可以这样:

 

  RxHttp.get("/service/getIpInfo.php").setDomainToUpdate9158IfAbsent().add("key", "value").from()  .subscribe(s -> { //这里的s为String类型,即Http请求的返回结果//成功回调}, throwable -> {//失败回调});

 

此时,RxHttp检测到url已经配置了域名,就不会再去使用默认的域名。同样的,setDomainToUpdate9158IfAbsent也会检测url 有没有配置域名,如果配置了,也不会使用我们指定的域名。

 

注意:@Domain注解可以在多个地方使用,而@DefaultDomain()只能在一个地方使用,否则编译不通过,很好理解,默认域名只可能有一个。两个注解都要使用在public static修饰的String类型变量上,对final关键字没有要求,可写可不写,这就表明,baseUrl 可以动态更改,RxHttp始终会拿到的最新的baseUrl 。怎么样,是不是很nice!!
更多注解使用请查看RxHttp 扩展篇之注解处理器 Generated API(八)

 

接下来,我们来看看,如何发送Post请求、如何在Activity/Fragment销毁时,自动关闭为完成的请求、如何上传/下载文件及进度的监听、如何把Http返回的结果自动解析成我们想要的对象。

 

注:以下讲解均使用RxHttp

 

Post

 

  RxHttp.postForm("http://...").add("key", "value").from()  .subscribe(s -> { //这里的s为String类型,即Http请求的返回结果//成功回调}, throwable -> {//失败回调});

 

可以看到,跟上面的Get请求只有一点不同,Get是RxHttp.get,而Post是RxHttp.postForm,除此之外,没有任何区别,我们在看来来,RxHttp都有哪些静态方法供我们选择请求方式
在这里插入图片描述可以看到,默认提供了10个静态方法供我们选择具体的请求方式,有Get、Post、Put等,而Post等又分为postForm和postJson,这个好理解,前者是发送表单形式的post请求,后者是发送json字符串的post请求。

 

现实中,这些默认的请求方式显然不能满足我们的需求,如:我要发送加密的post请求,这个时候该怎么办呢?此时就需要我们自定义请求方式。自定义请求方式请查看RxHttp 扩展篇之Param扩展(七)

 

Activity 销毁,自动关闭未完成的请求

 

上面的案例中,在Activity/Fragment销毁时,如果请求还未完成,就会造成Activity/Fragment 无法回收,导致内存泄漏。这是非常严重的问题,那么RxHttp是如何解决的呢?此时,就要引入我自己写的另一个库RxLife,直接看看如何使用

 

  RxHttp.postForm("http://...").add("key", "value").from().as(RxLife.as(this)) //订阅观察者前,加上这句话即可.subscribe(s -> {//成功回调}, throwable -> {//失败回调});//或者RxHttp.postForm("http://...").add("key", "value").from().as(RxLife.asOnMain(this)) //asOnMain 可以在主线程回调观察者.subscribe(s -> {//成功回调}, throwable -> {//失败回调});

 

这里的thisLifecycleOwner对象,它是一个接口,这里我们传入的是Activity,因为Activity实现了LifecycleOwner接口。当Activity/Fragment销毁时,会将RxJava的管道中断,管道中断时,又会将未完成的请求自动关闭。
RxLife不了解的同学请查看Android RxLife 一款轻量级别的RxJava生命周期管理库 (一),这里不详细讲解。在下面的讲解中,我们均会使用RxLife

 

文件上传/下载及进度监听

 

使用RxHttp,可以很优雅的实现文件上传/下载及进度的监听,如何优雅?直接上代码

 

文件上传

 

  RxHttp.postForm("http://...") //发送Form表单形式的Post请求.add("key", "value").add("file1", new File("xxx/1.png")) //添加file对象.add("file2", new File("xxx/2.png")).from() //from操作符,是异步操作.as(RxLife.asOnMain(this))  //感知生命周期,并在主线程回调.subscribe(s -> { //成功回调}, throwable -> {//失败回调});

 

可以看到,文件上传跟普通的post请求其实没啥区别,无非就是在post请求的基础上,调用add方法添加要上传的文件对象。

 

文件下载

 

  //文件存储路径String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk").download(destPath) //注意这里使用download操作符,并传入本地路径.as(RxLife.asOnMain(this))  //感知生命周期,并在主线程回调.subscribe(s -> {//下载成功,回调文件下载路径}, throwable -> {//下载失败});

 

下载跟普通请求不同的是,下载使用的是download操作符,其它都一样。

 

文件下载进度监听

 

//文件存储路径String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk").downloadProgress(destPath) //注:如果需要监听下载进度,使用downloadProgress操作符.observeOn(AndroidSchedulers.mainThread()).doOnNext(progress -> {//下载进度回调,0-100,仅在进度有更新时才会回调,最多回调101次,最后一次回调文件存储路径int currentProgress = progress.getProgress(); //当前进度 0-100long currentSize = progress.getCurrentSize(); //当前已下载的字节大小long totalSize = progress.getTotalSize();     //要下载的总字节大小String filePath = progress.getResult(); //文件存储路径,最后一次回调才有内容}).filter(Progress::isCompleted)//下载完成,才继续往下走.map(Progress::getResult) //到这,说明下载完成,返回下载目标路径.as(RxLife.as(this)) //感知生命周期.subscribe(s -> {//s为String类型,这里为文件存储路径//下载完成,处理相关逻辑}, throwable -> {//下载失败,处理相关逻辑});

 

下载进度的监听我们稍微看一下 ,首先一点,下载使用download操作符,而下载进度监听使用downloadProgress操作符,随后,我们使用了doOnNext操作符处理进度回调,注意这里是仅当有进度更新时,才会回调,其中的progress变量是一个Progress类型的对象,我们贴上源码:

 

public class Progress<T> {private int  progress; //当前进度 0-100private long currentSize;//当前已完成的字节大小private long totalSize; //总字节大小private T mResult; //http返回结果,上传/下载完成时调用//省略get/set方法
}

 

由于进度回调会执行101次(上面注释有解释),而最下面观察者其实是不需要关心这么多事件的,只需要关心最后下载完成的事件,所以使用了filter操作符过滤事件,只要还未下载完成,就将事件过滤调,不让往下走。最终下载完成后,拿到本地下载路径。

 

文件上传进度监听

 

  RxHttp.postForm("http://www.......") //发送Form表单形式的Post请求.add("file1", new File("xxx/1.png")).add("file2", new File("xxx/2.png")).add("key1", "value1")//添加参数,非必须.add("key2", "value2")//添加参数,非必须.addHeader("versionCode", "100") //添加请求头,非必须.uploadProgress() //注:如果需要监听上传进度,使用uploadProgress操作符.observeOn(AndroidSchedulers.mainThread()) //主线程回调.doOnNext(progress -> {//上传进度回调,0-100,仅在进度有更新时才会回调,最多回调101次,最后一次回调Http执行结果int currentProgress = progress.getProgress(); //当前进度 0-100long currentSize = progress.getCurrentSize(); //当前已上传的字节大小long totalSize = progress.getTotalSize();     //要上传的总字节大小String result = progress.getResult(); //Http执行结果,最后一次回调才有内容}).filter(Progress::isCompleted)//过滤事件,上传完成,才继续往下走.map(Progress::getResult) //到这,说明上传完成,拿到Http返回结果并继续往下走.as(RxLife.as(this))  //感知生命周期.subscribe(s -> { //s为String类型,由SimpleParser类里面的泛型决定的//上传成功,处理相关逻辑}, throwable -> {//上传失败,处理相关逻辑});

 

上传进度监听使用downloadProgress操作符,剩下的操作跟下载进度监听的操作都一样,通过doOnNext监听上传进度,然后过滤事件,最终拿到Http的返回结果。

 

数据解析器Parser

 

在上面的案例中,观察者拿到数据类型都是String类型,然后现实开发中,我们经常需要对数据解析成我们想要的对象,RxHttp考虑到了这一点,现在我们就来看看如何的到我们想要的对象

 

我们拿淘宝获取IP的接口作为测试接口http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42
对应的数据结构如下

 

public class Response {private int     code;private Address data;//省略set、get方法class Address {//为简单起见,省略了部分字段private String country; //国家private String region; //地区private String city; //城市//省略set、get方法}
}

 

开始发送请求

 

  RxHttp.get("http://ip.taobao.com/service/getIpInfo.php") //Get请求.add("ip", "63.223.108.42")//添加参数.addHeader("accept", "*/*") //添加请求头.addHeader("connection", "Keep-Alive").addHeader("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)").fromSimpleParser(Response.class)  //这里返回Observable<Response> 对象.as(RxLife.asOnMain(this))  //感知生命周期,并在主线程回调.subscribe(response -> {//成功回调}, throwable -> {//失败回调});

 

可以看到,这里我们没有用from操作符,而是用了fromSimpleParser操作符,并且传入Response.class,最后观察者拿到的response变量就是Response类型的对象。怎么样,是不是很简单。RxHttp为我们提供了一系列的fromXXX方法,我们来看一下:
在这里插入图片描述
我们可以看到,一些基本类型的封装对象RxHttp都为我们封装好了,还有一个fromListParser方法,此方法是用来解析集合对象的,一些常见的数据结构,RxHttp都为我们考虑到了,并封装好了,然后,一些不常见的数据呢?眼尖的你也许发现了,上图中还有一个<T> Observable<T> from(Parser<T> parser)方法,它允许我们传入一个自定义的解析器,更多解析器的介绍,请查看RxHttp 之强大的数据解析功能(二)

 

最后,附上RxHttp一些常用的用法,如下:

 

  RxHttp.postForm("/service/getIpInfo.php")       //发送Form表单形式的Post请求.setDomainToUpdate9158IfAbsent()  //手动设置域名,此方法是通过@Domain注解生成的.tag("RxHttp.get")          //为单个请求设置tag.setUrl("http://...")       //重新设置url.setAssemblyEnabled(false)  //设置是否添加公共参数,默认为true.cacheControl(CacheControl.FORCE_NETWORK)  //缓存控制.setParam(Param.postForm("http://..."))    //重新设置一个Param对象.add(new HashMap<>())   //通过Map添加参数.add("int", 1)          //添加int类型参数.add("float", 1.28838F) //添加float类型参数.add("double", 1.28838) //添加double类型参数.add("key1", "value1")  //添加String类型参数.add("key2", "value2", false) //根据最后的boolean字段判断是否添加参数 .add("file1", new File("xxx/1.png"))            //添加文件对象.addHeader("headerKey1", "headerValue1")        //添加头部信息.addHeader("headerKey2", "headerValue2", false)//根据最后的boolean字段判断是否添加头部信息 .fromSimpleParser(String.class)  //这里返回Observable<T> 对象  fromXXX都是异步操作符//感知生命周期,并在主线程回调,当Activity/Fragment销毁时,自动关闭未完成的请求.as(RxLife.asOnMain(this))  .subscribe(s -> {    //订阅观察者//成功回调}, throwable -> {//失败回调});

 

小结

 

到这,RxHttp的基本用法我们就讲解完毕了,可以看到,使用RxHttp类一条链就能完成一个完整的Http请求,简单一点,就是请求三部曲:

 

  • 首先,确定请求方式并添加相关参数
  • 然后,确定解析器,指定要解析成的类型
  • 最后,订阅观察者,开始发送请求

 

以上所有的案例都离不开这3个步骤。最后,你会发现,RxHttp除了提供的一系列强大的功能外,在写法上,不管什么请求,都极其的相似,只要通过RxHttp类,就能一条链,完成所有的请求,极大的降低了学习成本。

 

注:要想在项目中生成RxHttp类,至少需要使用一次注解类,否则检测不到注解,就无法生成。

 

如果你觉得RxHttp+RxLife好用,请记得给我star
如果有好的idea,请留言或者联系我。

 

更过详情请查看RxHttp系列其它文章

 

RxHttp 之强大的数据解析功能(二)

 

RxHttp 介绍篇之Param介绍(三)

 

RxHttp 介绍篇之多请求串行与并行(四)

 

RxHttp 扩展篇之Param扩展(六)

 

RxHttp 扩展篇之注解处理器 Generated API(七)


---------------------
作者:刘敬兴
来源:CSDN
原文:https://blog.csdn.net/liujingxing93/article/details/86600477
版权声明:本文为作者原创文章,转载请附上博文链接!

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

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

相关文章

【Pix4d精品教程】Pix4d空三后处理:点云分类与过滤、DSM精编生成DEM、生成等高线案例详解

《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho) DEM结果预览: 等高线结果预览: Pix4d内业空三结束后,会生成点云,DOM和DSM等产品,一般情况下,DOM精度不达标(如房屋边缘有噪点)的话,可以直接在镶嵌图编辑器进行DOM的编辑,然而后处理的…

git分支进阶

其实git除了版本控制&#xff0c;另外一个最突出的特点就是他的分支操作。简直 丝滑~.git也是多人协作的必备武器。 通常我们正常情况下只需要master 和 develop分支就够了。 这里我们先以这两条分支作为基准&#xff0c;进行一系列的操作。 开发新功能流程 这个应该属于develo…

关于c# .net爬虫

刚开始听到爬虫这两个字眼的时候感觉挺稀奇的&#xff0c;之前并没有接触过爬虫&#xff0c;正好这会手上没事&#xff0c;于是便百度了一下。 1.网络爬虫&#xff08;又被称为网页蜘蛛&#xff0c;网络机器人&#xff0c;在FOAF社区中间&#xff0c;更经常的称为网页追逐者&am…

【Pix4d精品教程】Pix4d中央子午线细化设置(测区跨两个分带)

《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho) 航测内业中,在自由空三结束之后,需要导入像控点,进而去刺像控点。但是当测区跨两个分带的时候(如测区正好处在3度带105和108中间),像控点可能距离靶标点很远,给刺点带来了很大难度。怎样解…

Failed:(13: Permission denied)导致访问浏览器出现Nginx 500 Internal Server Error

1 、问题 我在部署nginx反向代理服务器的时候&#xff0c;nginx.conf文件都配置好了&#xff0c;但是我在浏览器里面输入域名的时候&#xff0c;提示Nginx 500 Internal Server Error 2、分析 我们需要找到nginx输出错误日志的文件&#xff0c;在nginx.conf里面我们可以看到错…

MAUI与Blazor共享一套UI,媲美Flutter,实现Windows、macOS、Android、iOS、Web通用UI

1. 前言距离上次发《MAUI初体验&#xff1a;爽》一文已经过去2个月了&#xff0c;本计划是下半年或者明年再研究MAUI的&#xff0c;现在计划提前啦&#xff0c;因为我觉得MAUI Blazor挺有意思的&#xff1a;在Android、iOS、macOS、Windows之间共享UI&#xff0c;一处UI增加或者…

dns 报文格式

最近学习了下DNS的格式&#xff0c;发现很多内容都是转载自同一个而且说的不是很清楚&#xff0c;特再整理下具体可以查看RFC1035 http://www.ietf.org/rfc/rfc1035.txt有详细的解释对于英语理解不是很好和懒得看这么长的可以看下本文首先是DNS数据帧的格式-------------------…

【Pix4d精品教程】Pix4d修编正射影像DOM的两种方法案例详解

《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho) DOM修编前: DOM修编后: 文章摘要: Pix4d内业数据处理通常会生成点云、DSM和DOM等产品,DSM经过精编可以生成精准的DEM,而DOM一般情况下,存在比如房屋边缘被拉花,或者存在噪点的情况

终于找到了,开源的Vue3+.NET6通用管理后台!

据说80%的.NET项目都是管理后台&#xff0c;然而能用上Vue3.NET6的管理后台并不多见。这里分享一套Vue3 Axios TS Vite ElementUI Plus .NET 6 WebAPI JWT SqlSugar的前后端分离架构的通用管理后台源码数据库脚本&#xff0c;还有与之配套录制的一组视频教程&#xff0c;全部打…

【Pix4d精品教程】Pix4d模型成果导出OSGB并加载OSGB到EPS进行三维测图完美案例教程

《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho) 在垂直摄影中,Pix4d也可以生成漂亮的三维模型,并导出为OSGB,加载到EPS进行三维测图。首先来看生成的三维格网纹理和EPS三维模型加载效果。 Pix4d生成的三维格网纹理: EPS加载OSGB模型效果: 文…

Android实现ListView(1)

昨天有个朋友问我Android ListView列表视图&#xff0c;遇到了点错误&#xff0c;今天我给大家演示&#xff0c;具体实现见图&#xff1a; 1&#xff1a;创建一个item布局layout/item.xml 2&#xff1a;创建一个ListViewActivity类&#xff0c;但是必须继承ListActivity&#x…

WolframAlpha 的使用

WolframAlpha 1. 求解复杂方程组 ab−4abc2ac1直接点开网站&#xff0c;在输入框中输入&#xff0c;ab-4;abc2;ac1;&#xff08;逗号分割开来&#xff09;&#xff0c; 转载于:https://www.cnblogs.com/mtcnn/p/9423087.html

聊聊 C# 中的 Composite 模式

‍写在前面 Composite组合模式属于设计模式中比较热门的一个&#xff0c;相信大家对它一定不像对访问者模式那么陌生&#xff0c;毕竟谁又没有遇到过树形结构呢。不过所谓温故而知新&#xff0c;我们还是从一个例子出发&#xff0c;起底一下这个模式吧。一个简单例子 设想我们…

140种Python标准库、第三方库和外部工具都有了

导读&#xff1a;Python数据工具箱涵盖从数据源到数据可视化的完整流程中涉及到的常用库、函数和外部工具。其中既有Python内置函数和标准库&#xff0c;又有第三方库和工具。 这些库可用于文件读写、网络抓取和解析、数据连接、数清洗转换、数据计算和统计分析、图像和视频处理…

开源项目【zheng】搭建流程

2019独角兽企业重金招聘Python工程师标准>>> 搭建过程 项目地址 https://gitee.com/shuzheng/zheng这两篇写的比较详细的搭建过程&#xff0c;结合一下就没什么问题了。 https://my.oschina.net/yzuzhang/blog/1538555http://www.jianshu.com/p/b2fb42e17b581.JDK 1…

简述HTML DOM及其节点分类

在JavaScript中&#xff0c;document这个对象大家一定很熟悉&#xff0c;哪怕是刚刚开始学习的新人&#xff0c;也会很快接触到这个对象。而document对象不仅仅是一个普通的JavaScript内置对象&#xff0c;它还是一个巨大API的核心对象&#xff0c;这个巨大的API就是DOM&#x…

【CC精品教程】任务二:导入像控点、差分POS,空三平差权重设置,提交自由空三

《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho) 【CC精品教程】任务一:CC新建工程、添加照片、相机参数设置、选择坐标系统 本任务接着上一个任务,继续完成CC项目作业,主要内容有:导入像控点、选择空间参考系统,导入差分POS,空三平差权重设…

WPF 基础控件之 TabControl样式

其他基础控件1.Window2.Button3.CheckBox4.ComboBox5.DataGrid 6.DatePicker7.Expander8.GroupBox9.ListBox10.ListView11.Menu12.PasswordBox13.TextBox14.RadioButton15.ToggleButton16.Slider 17.TreeView TabControl 实现下面的效果1&#xff09;TabControl来实现动画&…

两个数值交换位置

2019独角兽企业重金招聘Python工程师标准>>> 先说非计算机专业都能理解的。 int a 10; int b 20; 方法一&#xff1a; int c a; a b; b c; System.out.println("a"a",b"b); 方法二&#xff1a; a a b; b a - b; a a - b; System.out…

教你如何在Android 6.0上创建系统悬浮窗

郭霖大神的文章:http://mp.weixin.qq.com/s?__bizMzA5MzI3NjE2MA&mid2650235949&idx1&sn0f7eded67f834d38b02f8872768cb68a&scene0#wechat_redirect今天周二&#xff0c;又该跟大家分享由我执笔的文章了。从之前我写的deep links、通知栏微技巧这两篇文章中&a…