Android 网络框架之Retrofit2使用详解及从源码中解析原理
来源:互联网 发布:文件档案管理系统软件 编辑:程序博客网 时间:2024/06/16 06:30
版权声明:本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
就目前来说Retrofit2使用的已相当的广泛,那么我们先来了解下两个问题:
1 . 什么是Retrofit?
Retrofit是针对于Android/Java的、基于okHttp的、一种轻量级且安全的、并使用注解方式的网络请求框架。
2 . 我们为什么要使用Retrofit,它有哪些优势?
首先,Retrofit使用注解方式,大大简化了我们的URL拼写形式,而且注解含义一目了然,简单易懂;
其次,Retrofit使用简单,结构层次分明,每一步都能清晰的表达出之所以要使用的寓意;
再者,Retrofit支持同步和异步执行,使得请求变得异常简单,只要调用enqueue/execute即可完成;
最后,Retrofit更大自由度的支持我们自定义的业务逻辑,如自定义Converters。
好,知道了Retrofit是什么,有了哪些优势,现在我们来学习下怎么使用。
一 Retrofit2使用详解:
在使用之前,你必须先导入必要的jar包,以androidStudio为例:
添加依赖:
- 1
- 2
- 1
- 2
因为Retrofit2是依赖okHttp请求的,而且请查看它的META-INF->META-INF\maven\com.squareup.retrofit2\retrofit->pom.xml文件,
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
由此可见,它确实是依赖okHttp,okHttp有会依赖okio所以它会制动的把这两个包也导入进来。
添加权限:
既然要请求网络,在我们android手机上是必须要有访问网络的权限的,下面把权限添加进来
- 1
- 1
好了,下面开始介绍怎么使用Retrofit,既然它是使用注解的请求方式来完成请求URL的拼接,那么我们就按注解的不同来分别学习:
首先,我们需要创建一个java接口,用于存放请求方法的:
- 1
- 2
- 1
- 2
然后逐步在该方法中添加我们所需要的方法(按照请求方式):
1 :Get : 是我们最常见的请求方法,它是用来获取数据请求的。
①:直接通过URL获取网络内容:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
在这里我们定义了一个listRepos()的方法,通过@GET注解标识为get请求,请求的URL为“users/octocat/repos”。
然后看看Retrofit是怎么调用的,代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
代码解释:首先获取Retrofit对象,然后通过动态代理获取到所定义的接口,通过调用接口里面的方法获取到Call类型返回值,最后进行网络请求操作(这里不详细说明Retrofit 实现原理,后面会对它进行源码解析),这里必须要说的是请求URL的拼接:在构建Retrofit对象时调用baseUrl所传入一个String类型的地址,这个地址在调用service.listRepos()时会把@GET(“users/octocat/repos”)的URL拼接在尾部。
ok,这样就完成了,我们这次的请求,但是我们不能每次请求都要创建一个方法呀?这时我们就会想起动态的构建URL了
②:动态获取URL地址:@Path
我们再上面的基础上进行修改,如下:
- 1
- 2
- 3
- 1
- 2
- 3
这里在Get注解中包含{user},它所对应的是@Path注解中的“user”,它所标示的正是String user,而我们再使用Retrofit对象动态代理的获取到GitHubService,当调用listRepos时,我们就必须传入一个String类型的User,如:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
如上代码,其他的代码都是不变的,而我们只需要使用@Path注解就完全的实现了动态的URL地址了,是不是很方便呢,这还不算什么,通常情况下,我们去获取一些网络信息,因为信息量太大,我们会分类去获取,也就是携带一些必要的元素进行过滤,那我们该怎么实现呢?其实也很简单,因为Retrofit已经为我们封装好了注解,请看下面(官网实例):
③:动态指定条件获取信息:@Query
- 1
- 2
- 1
- 2
我们只需要使用@Query注解即可完成我们的需求,在@Query(“sort”)中,short就好比是URL请求地址中的键,而它说对应的String sort中的sort则是它的值。
但是我们想,在网络请求中一般为了更精确的查找到我们所需要的数据,过滤更多不需要的无关的东西,我们往往需要携带多个请求参数,当然可以使用@Query注解,但是太麻烦,很长,容易遗漏和出错,那有没有更简单的方法呢,有,当然后,我们可以直接放到一个map键值对中:
④:动态指定条件组获取信息:@QueryMap
- 1
- 2
- 1
- 2
使用@QueryMap注解可以分别地从Map集合中获取到元素,然后进行逐个的拼接在一起。
ok,到这里,我们使用@Get注解已经可以完成绝大部分的查询任务了,下面我们再来看看另一种常用的请求方式–post
2 POST : 一种用于携带传输数据的请求方式
稍微了解点Http的同学们,可能都会知道:相对于get请求方式把数据存放在uri地址栏中,post请求传输的数据时存放在请求体中,所以post才能做到对数据的大小无限制。而在Retrofit中,它又是怎么使用的呢?请看下面:
①:携带数据类型为对象时:@Body
- 1
- 2
- 1
- 2
当我们的请求数据为某对象时Retrofit是这么处理使用的:
首先,Retrofit用@POST注解,标明这个是post的请求方式,里面是请求的url;
其次,Retrofit仿照http直接提供了@Body注解,也就类似于直接把我们要传输的数据放在了body请求体中,这样应用可以更好的方便我们理解。
来看下应用:
- 1
- 1
这样我们直接把一个新的User对象利用注解@Body存放在body请求体,并随着请求的执行传输过去了。
但是有同学在这该有疑问了,Retrofit就只能传输的数据为对象吗?当然不是,下面请看
②:携带数据类型为表单键值对时:@Field
- 1
- 2
- 3
- 1
- 2
- 3
当我们要携带的请求数据为表单时,通常会以键值对的方式呈现,那么Retrofit也为我们考虑了这种情况,它首先用到@FormUrlEncoded注解来标明这是一个表单请求,然后在我们的请求方法中使用@Field注解来标示所对应的String类型数据的键,从而组成一组键值对进行传递。
那你是不是有该有疑问了,假如我是要上传一个文件呢?
③:单文件上传时:@Part
- 1
- 2
- 3
- 1
- 2
- 3
此时在上传文件时,我们需要用@Multipart注解注明,它表示允许多个@Part,@Part则对应的一个RequestBody 对象,RequestBody 则是一个多类型的,当然也是包括文件的。下面看看使用
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
这里我们创建了两个RequestBody 对象,然后调用我们定义的updateUser方法,并把RequestBody传递进入,这样就实现了文件的上传。是不是很简单呢?
相比单文件上传,Retrofit还进一步提供了多文件上传的方式:
④:多文件上传时:@PartMap
- 1
- 2
- 3
- 1
- 2
- 3
这里其实和单文件上传是差不多的,只是使用一个集合类型的Map封装了文件,并用@PartMap注解来标示起来。其他的都一样,这里就不多讲了。
3 Header : 一种用于携带消息头的请求方式
Http请求中,为了防止攻击或是过滤掉不安全的访问或是为添加特殊加密的访问等等以减轻服务器的压力和保证请求的安全,通常都会在消息头中携带一些特殊的消息头处理。Retrofit也为我们提供了该请求方式:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
以上两种是静态的为Http请求添加消息头,只需要使用@Headers注解,以键值对的方式存放即可,如果需要添加多个消息头,则使用{}包含起来,如上所示。但要注意,即使有相同名字得消息头也不会被覆盖,并共同的存放在消息头中。
当然有静态添加那相对的也就有动态的添加消息头了,方法如下:
- 1
- 2
- 1
- 2
使用@Header注解可以为一个请求动态的添加消息头,假如@Header对应的消息头为空的话,则会被忽略,否则会以它的.toString()方式输出。
ok,到这里已基本讲解完Retrofit的使用,还有两个重要但简单的方法也必须在这里提一下:
1 call.cancel();它可以终止正在进行的请求,程序只要一旦调用到它,不管请求是否在终止都会被停止掉。
2 call.clone();当你想要多次请求一个接口的时候,直接用 clone 的方法来生产一个新的,否则将会报错,因为当你得到一个call实例,我们调用它的 execute 方法,但是这个方法只能调用一次。多次调用则发生异常。
好了,关于Retrofit的使用我们就讲这么多,接下来我们从源码的角度简单的解析下它的实现原理。
二 Retrofit2 从源码解析实现原理
首先先看一下Retrofit2标准示例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
由上面我们基本可以看出,Retrofit是通过构造者模式创建出来的,那么我们就来看看Builder这个构造器的源码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
源码讲解:
1:当我们使用new Retrofit.Builder()来创建时,在Builder构造器中,首先就获得当前的设备平台信息,并且把内置的转换器工厂(BuiltInConverters)加添到工厂集合中,它的主要作用就是当使用多种Converters的时候能够正确的引导并找到可以消耗该类型的转化器。
2:从我们的基本示例中看到有调用到.baseUrl(BASE_URL)这个方法,实际上没当使用Retrofit时,该方法都是必须传入的,并且还不能为空,从源码中可以看出,当baseUrl方法传进的参数来看,如果为空的话将会抛出NullPointerException空指针异常。
3:addConverterFactory该方法是传入一个转换器工厂,它主要是对数据转化用的,请网络请求获取的数据,将会在这里被转化成我们所需要的数据类型,比如通过Gson将json数据转化成对象类型。
4 : 从源码中,我们看到还有一个client方法,这个是可选的,如果没有传入则就默认为OkHttpClient,在这里可以对OkHttpClient做一些操作,比如添加拦截器打印log等
5:callbackExecutor该方法从名字上看可以得知应该是回调执行者,也就是Call对象从网络服务获取数据之后转换到UI主线程中。
6:addCallAdapterFactory该方法主要是针对Call转换了,比如对Rxjava的支持,从返回的call对象转化为Observable对象。
7:最后调用build()方法,通过new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);构造方法把所需要的对象传递到Retrofit对象中。
ok,当我们通过Builder构造器构造出Retrofit对象时,然后通过Retrofit.create()方法是怎么把我们所定义的接口转化成接口实例的呢?来看下create()源码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
当看到Proxy时,是不是多少有点明悟了呢?没错就是动态代理,动态代理其实已经封装的很简单了,主要使用newProxyInstance()方法来返回一个类的代理实例,其中它内部需要传递一个类的加载器,类本身以及一个InvocationHandler处理器,主要的动作都是在InvocationHandler中进行的,它里面只有一个方法invoke()方法,每当我们调用代理类里面的方法时invoke()都会被执行,并且我们可以从该方法的参数中获取到所需要的一切信息,比如从method中获取到方法名,从args中获取到方法名中的参数信息等。
而Retrofit在这里使用到动态代理也不会例外:
首先,通过method把它转换成ServiceMethod ;
然后,通过serviceMethod, args获取到okHttpCall 对象;
最后,再把okHttpCall进一步封装并返回Call对象。
下面来逐步详解。
1:将method把它转换成ServiceMethod
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
loadServiceMethod源码方法中非常的好理解,主要就是通过ServiceMethod.Builder()方法来构建ServiceMethod,并把它给缓存取来,以便下次可以直接回去ServiceMethod。那下面我们再来看看它是怎么构建ServiceMethod方法的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
首先在Builder()中初始化一些参数,然后在build()中返回一个new ServiceMethod<>(this)对象。
下面来详细的解释下build()方法,完全理解了该方法则便于理解下面的所有执行流程。
①:构建CallAdapter对象,该对象将会在第三步中起着至关重要的作用。
现在我们先看看它是怎么构建CallAdapter对象的:createCallAdapter()方法源码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
在createCallAdapter方法中主要做的是事情就是获取到method的类型和注解,然后调用retrofit.callAdapter(returnType, annotations);方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
辗转到Retrofit中nextCallAdapter()中,在for 循环中分别从adapterFactories中来获取CallAdapter对象,但是adapterFactories中有哪些CallAdapter对象呢,这就需要返回到构建Retrofit对象中的Builder 构造器中查看了
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
从上面的代码中可以看到,不管有没有通过addCallAdapterFactory添加CallAdapter,adapterFactories集合至少都会有一个ExecutorCallAdapterFactory对象。当我们从adapterFactories集合中回去CallAdapter对象时,那我们都会获得ExecutorCallAdapterFactory这个对象。而这个对象将在第三步中和后面执行同步或异步请求时起着至关重要的作用。
②:构建responseConverter转换器对象,它的作用是寻找适合的数据类型转化
该对象的构建和构建CallAdapter对象的流程基本是一致的,这里就不在赘述。同学们可自行查看源码。
2:通过serviceMethod, args获取到okHttpCall 对象
第二步相对比较简单,就是对象传递:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
3:把okHttpCall进一步封装并返回Call对象
这一步也是一句话 return serviceMethod.callAdapter.adapt(okHttpCall);但是想理解清楚必须先把第一步理解透彻,通过第一步我们找得到serviceMethod.callAdapter就是ExecutorCallAdapterFactory对象,那么调用.adapt(okHttpCall)把okHttpCall怎么进行封装呢?看看源码:
- 1
- 1
一看,吓死宝宝了,就这么一句,这是嘛呀,但是经过第一步的分析,我们已知道serviceMethod.callAdapter就是ExecutorCallAdapterFactory,那么我们可以看看在ExecutorCallAdapterFactory类中有没有发现CallAdapter的另类应用呢,一看,果不其然在重写父类的get()方法中我们找到了答案:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
当看到return new CallAdapter中的adapt(Call call)我们就完全知其所以然了,至于ExecutorCallbackCall怎么应用的我们在发起网络请求的时候讲解。
ok,当我们得到接口的代理实例之后,通过代理接口调用里面的方法,就会触发InvocationHandler对象中的invoke方法,从而完成上面的三个步骤并且返回一个Call对象,通过Call对象就可以去完成我们的请求了,Retrofit为我们提供两种请求方式,一种是同步,一种是异步。我们这里就以异步方式来讲解:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
从上面我们可以看到enqueue方法中有一个回调函数,回调函数里面重写了两个方法分别代表请求成功和失败的方法,但是我们想知道它是怎么实现的原理呢?那么请往下面看:
在上面获取接口的代理实例时,通过代理接口调用里面的方法获取一个Call对象,我们上面也分析了其实这个Call对象就是ExecutorCallbackCall,那么我们来看看它里面是怎么实现的?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
在ExecutorCallbackCall类中,封装了两个对象一个是callbackExecutor,它主要是把现在运行的线程切换到主线程中去,一个是delegate对象,这个对象就是真真正正的执行网络操作的对象,那么它的真身到底是什么呢?还记得我们在获取代理接口第三步执行的serviceMethod.callAdapter.adapt(okHttpCall)的分析吧,经过辗转几步终于把okHttpCall传递到了new ExecutorCallbackCall<>(callbackExecutor, call);中,然后看看ExecutorCallbackCall的构造方法:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
由此可以明白delegate 就是okHttpCall对象,那么我们在看看okHttpCall是怎么执行异步网络请求的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
从上面代码中,我们很容易就看出,其实它就是这里面封装了一个okhttp3.Call,直接利用okhttp进行网络的异步操作,至于okhttp是怎么进行网络请求的我们就不再这里讲解了,感兴趣的朋友可以自己去查看源码。
好了,到这里整个Retrofit的实现原理基本已解析完毕,相信大家学习过都能够很好的掌握了。ok,今天就讲到这里吧,本来还打算再写个实例放上来的,看看篇幅也就放弃了,实战部分会在下一篇和Rxjava一起放出来,Rxjava我自己学习的都心花怒放了。看完记住关注微信平台。
更多资讯请关注微信平台,有博客更新会及时通知。爱学习爱技术。
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
- Android 源码解析 Retrofit2 原理
- Android中网络框架Retrofit2.0简单使用
- Android网络之Retrofit2.0使用和解析
- Retrofit2.0的使用及原理解析
- Retrofit2使用案例及源码解析
- Retrofit2 源码解析 理解原理能帮助我们更好的使用框架
- 使用retrofit2和rxjava封装的网络框架RNet:(二)RNet的源码解析
- 网络框架之Retrofit2
- Android 网络访问框架retrofit2,okhttp3之简单封装,kotlin源码
- RxJava2+Retrofit2网络请求框架封装及使用
- 如何使用retrofit2网络框架
- Retrofit2网络框架的使用
- Android-AndFix 热修复框架原理及源码解析
- Android 网络请求框架 Retrofit2.0实践使用总结
- Android 网络框架 Retrofit2.0介绍、使用和封装
- 升级VS后项目加载失败的情况
- 几种常见代码管理工具比较(2009)
- 大型项目使用Automake/Autoconf完成编译配置
- Retrofit2的再封装实战—同步与异步请求
- 人事管理系统实现(二)
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
- 初谈一Java Annotation
- 5步搭建GO环境
- 判断用户是否在线及实现一个账号一个人登陆
- hdu 2048 错排公式 神、上帝以及老天爷
- MFC:超链接文本(重绘CStatic)
- 场效应管
- Java并发容器大合集
- 【java基础 15】java代码中“==”和equals的区别