Retrofit2+Rxjava学习到封装

一. 为什么写这篇文章

RxJava和Retrofit也火了一段时间了,网上有很多RxJava与Retrofit的文章介绍,拜读一番之后,仍感觉这两个框架不接底气,对于初学者仍是丈二的和尚,不知所云,且知识点比较零碎,故写下这篇文章。

目的一: 把知识点通过一篇文章涵盖的尽量全面,减少学习成本
目的二:结合实际开发自己封装一个比较通用网络请求
目的三:从理解的层面出发,把踩过的坑明确说明,减少使用疑惑

二. 这篇文章都讲了什么

  1. 大白话叙述什么是Retrofit2

  2. Retrofit2的基本知识和用法

  3. Retrofit2自定义拦截器,配置全局请求头

  4. Retrofit2自定义解析器,数据传递加解密

  5. Rxjava是干什么的,替代了那些好理解的代码

  6. Rxjava基本知识和用法

  7. Rxjava结合Retrofit2的使用

  8. Rxjava结合Retrofit2实际开发中的封装和使用

三.说明

因本人水平有限,这篇文章只是一个入门级的,如有错误,欢迎骚扰 qq:807142217

四.进入正题:

一.什么是Retrofit2?

一句话:包装OKhttp联网框架的一个框架。其真正去网络请求的还是okHttp,所以大家也都说它是一个联网框架。okhttp设置请求方式,BaseUrl,设置请求头,设置请求参数等想必大家都已经了解,而Retrofit2做的只是通过 注解、interface和动态代理等技术手段改变了设置请求的方式。换句话来说,我们就是在学习Retrofit定义设置的方式,也即是所谓的注解所代表的含义和普通设置OKHttp设置网络请求画等号的过程。

那么问题就来了:只是改变了设置请求的方式,为什么会有这么多人使用,而且会这么火?正是因为他包装的网络请求返回结果支持响应式编程,也就是Rxjava,所以它火,就我个人理解而言,如果不是因为他支持Rxjava,我想大家也不会闲着没事去封装一个这样无聊的框架。既然它是因为Rxjava,那么什么又是Rxjava呢?

二. 什么是Rxjava?

也就是响应式编程,那么什么又是响应式编程呢? 就是:异步。就是替代Android原生的Handler来实现线程切换和数据传递的杂乱处境。众所周知,网络请求要在子线程中进行,而更新UI要在Main线程中,因此Google发明了Handler,试想一个Activity进行多次线程之间的切换,都要使用Handler,代码是多零散,即使你用Handler实现了需求,待你再过几个月去维护这些代码的时候,我想你自己都不知道写的是什么,而响应式编程,正是一行代码解决线程切换和线程之间等待的问题,所以我们为什么要学习Retrofit2和Rxjava。

三. Retrofit2的基本使用(也就是:注解所代表的含义和普通设置OKHttp设置网络请求画等号的过程。)

    看到这里,如果你还不知道OKHttp怎么使用,那就去看一下[鸿洋大神](http://blog.csdn.net/lmj623565791/article/details/47911083) ,再回过头来看这篇文章:
  • 初识框架
    需求:

    使用get请求需要传递四个参数口:
    https://api.douban.com/v2/  book/search  ?q=%E5%B0%8F%E7%8E%8B%E5%AD%90&tag=&start=0&count=3
    
    1. 创建业务请求接口,具体代码如下
    public interface BlueService {

        @GET("book/search")
        Call<BookSearchResponse> getSearchBooks(@Query("q") String name, 
                @Query("tag") String tag, @Query("start") int start, 
                @Query("count") int count);//BookSearchResponse是一个返回的Bean对象,实际类要根据接口返回对象去添加设置
    }

说明:

@GET注解就表示get请求,"()"里边的字符串表示变动的url部分

@Query表示请求参数,"()"里边的字符串表示请求的key部分内容

@Query只能用于Get请求中

通过以上步骤发现只传递过去部分url和key, 剩余的BaseUrl和value怎么传递过去呢?这就要靠第二步和第四步

2.在Activity页面创建一个Retrofit的实例,传递BseeUrl还有其他配置,下文会讲到,如:GsonConverterFactory.create()表示调用自带解析器来解析json返回值; .addCallAdapterFactory(RxJavaCallAdapterFactory.create())表示结合Rxjava使用的Adapter,如不结合,不必添加

 Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.douban.com/v2/")//要以/结束否者会报错
    .addConverterFactory(GsonConverterFactory.create())//解析的对象
    .build();

经过以上两步Retrofit框架会自动拼接一个相对完整的url,这个时候你会发现只差value没有设置了,这一步等你看到第四步自然会明白

3:通过Retrofit的实例的create()方法,传入接口生成对应的服务类

 BlueService service = retrofit.create(BlueService.class);

4:调用请求方法,并得到Call实例

    Call<BookSearchResponse> call = mBlueService.getSearchBooks("小王子", "", 0, 3); //注意这里传递的value,且第二个参数这里填写的是空字符串

这个时候有的同学就会纳闷了,明明自己就写了一个接口,怎么就可以去调用了呢?这里就不得不提一下动态代理这个词了

其实内部是通过反射和解析,去生成一个实现类,而不用我们去考虑具体是怎么实现了,这就是框架的好处

5:使用Call实例完成同步或异步请求

5.1 同步请求(注意不能在主线程调用)
 BookSearchResponse response = call.execute().body();
5.2 异步请求(回调在主线程中)
 call.enqueue(new Callback<BookSearchResponse>() {
    @Override
    public void onResponse(Call<BookSearchResponse> call, Response<BookSearchResponse> response) {

    //这里是Main线程,即使解析失败,还是会回调这个方法,详细后边在做解释

    }

    @Override
    public void onFailure(Call<BookSearchResponse> call, Throwable t) {

    }
    });
  • 如何使用

    1. 首先需要在build.gradle文件中引入需要的第三包,配置如下:
 compile 'com.squareup.retrofit2:retrofit:2.1.0'//这是联网框架的依赖
 compile 'com.squareup.retrofit2:converter-gson:2.1.0'//这是解析的依赖

注意:

    如果要结合Rxjava使用需要添加下边依赖,且rxjava的依赖版本号必须和retrofit的版本号一样,否者会报错
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

接下来我们将在围绕注解来介绍使用规则,毕竟请求方式以及参数都是通过接口传递过去的,实体方法还是框架自动生成的

Get请求方式有关的注解,以及数据传递规则和拼接url的方式

首先要十分明确:Get方法请求参数都会以key=value的方式拼接在url后面

get请求参数传递方式:Retrofit提供了两种方式设置请求参数


1:就是像上文提到的直接在interface中添加@Query注解,不再做叙述

2:通过Interceptor(拦截器)实现请求参数的添加,一般用在设置全局的参数

2.1 实现 Interceptor接口

2.2.addQueryParameter就是添加请求参数的具体代码

  public class CustomInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            //2.获取请求对象
            Request request = chain.request();

            //3.获取封装请求参数的对象,去添加参数
            HttpUrl build = request.url().newBuilder().addQueryParameter("token", "tokenValue").build();

            //4.把封装过请求数据的对象添加在请求对象里边
            Request build1 = request.newBuilder().url(build).build();

            return chain.proceed(build1);
        }
}

2.3创建完成自定义的Interceptor后,还需要在Retrofit创建client处完成添加

     OkHttpClient.Builder client = new OkHttpClient.Builder()
                .addInterceptor(new CustomInterceptor())//添加请求拦截
                .retryOnConnectionFailure(true);

2.4 添加自己设置过拦截器的OKhttpClient对象,上文已经说过,是包装的OKhttp

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.douban.com/v2/")          //要以结束否者会报错
    .addConverterFactory(GsonConverterFactory.create())//解析的对象
    .client(client.build())//添加自己设置过拦截器的OKhttpClient对象
    .build();

因为注解比较多,接下来我将把常用到的注解给大家快速归类一下,如果想整整理解还需要自己尝试,在这里推荐一篇文章Retrofit注解

@QueryMap

如果Query参数比较多,那么可以通过@QueryMap方式将所有的参数集成在一个Map统一传递,还以上文中的get请求方法为例

public interface BlueService {
    @GET("book/search")
    Call<BookSearchResponse> getSearchBooks(@QueryMap Map<String, String> options);
}

调用的时候将所有的参数集合在统一的map中即可

Map<String, String> options = new HashMap<>();
map.put("q", "小王子");
map.put("tag", null);
map.put("start", "0");
map.put("count", "3");
Call<BookSearchResponse> call = mBlueService.getSearchBooks(options);

Query非必填

如果请求参数为非必填,也就是说即使不传该参数,服务端也可以正常解析,那么如何实现呢?其实也很简单,请求方法定义处还是需要完整的Query注解,某次请求如果不需要传该参数的话,只需填充null即可。

针对文章开头提到的get的请求,加入按以下方式调用

Call<BookSearchResponse> call = mBlueService.getSearchBooks("小王子", null, 0, 3);

那么得到的url地址为

https://api.douban.com/v2/book/search?q=%E5%B0%8F%E7%8E%8B%E5%AD%90&start=0&count=3

@Path

如果请求的相对地址也是需要调用方传递,那么可以使用@Path注解,示例代码如下:

@GET("book/{id}")
Call<BookResponse> getBook(@Path("id") String id);

业务方想要在地址后面拼接书籍id,那么通过Path注解可以在具体的调用场景中动态传递,具体的调用方式如下:

Call<BookResponse> call = mBlueService.getBook("1003078");

此时的url地址为

https://api.douban.com/v2/book/1003078

Post请求
@field

Post请求需要把请求参数放置在请求体中,而非拼接在url后面,先来看一个简单的例子

@FormUrlEncoded //在post请求中这个注解必须填写,否者会报错
@POST("book/reviews")
Call<String> addReviews(@Field("book") String bookId, @Field("title") String title,
@Field("content") String content, @Field("rating") String rating);//必须有@Field注解,也就比必须有参数,否者会报错

注意:

@FormUrlEncoded将会自动将请求参数的类型调整为application/x-www-form-urlencoded,假如content传递的参数为Good Luck,那么最后得到的请求体就是

content=Good+Luck

FormUrlEncoded不能用于Get请求

@Field注解将每一个请求参数都存放至请求体中,还可以添加encoded参数,该参数为boolean型,具体的用法为

@Field(value = "book", encoded = true) String book

encoded参数为true的话,key-value-pair将会被编码,即将中文和特殊字符进行编码转换

@FieldMap

上述Post请求有4个请求参数,假如说有更多的请求参数,那么通过一个一个的参数传递就显得很麻烦而且容易出错,这个时候就可以用FieldMap

@FormUrlEncoded
@POST("book/reviews")
Call<String> addReviews(@FieldMap Map<String, String> fields);

@Body

如果Post请求参数有多个,那么统一封装到类中应该会更好,这样维护起来会非常方便

@FormUrlEncoded
@POST("book/reviews")
Call<String> addReviews(@Body Reviews reviews);
public class Reviews {
    public String book;
    public String title;
    public String content;
    public String rating;
}

这时,内部会转化成json字符串,然后提交给服务器

关于注解的更多知识,请查看上文给出的链接地址

  • 不得不说的知识

    1. 网络请求日志

          调试网络请求的时候经常需要关注一下请求参数和返回值,请求头和响应头,以便判断和定位问题出在哪里,但又不会抓包工具,Retrofit官方提供了一个很方便查看日志的Interceptor,方便开发者查看
      

首先需要在build.gradle文件中引入logging-interceptor

compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'

其次,想上文添加自定义拦截器的方式一样把log拦截器添加到请求中

 .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))      

添加请求头,或者配置全局的请求头

1.添加请求头

静态方法

public interface BlueService {
    @Headers("Cache-Control: max-age=640000")
    @GET("book/search")
    Call<BookSearchResponse> getSearchBooks(@Query("q") String name, 
            @Query("tag") String tag, @Query("start") int start, 
            @Query("count") int count);
}

当然你想添加多个header参数也是可以的,写法也很简单

public interface BlueService {
    @Headers({
        "Accept: application/vnd.yourapi.v1.full+json",
        "User-Agent: Your-App-Name"
    })
    @GET("book/search")
    Call<BookSearchResponse> getSearchBooks(@Query("q") String name, 
            @Query("tag") String tag, @Query("start") int start, 
            @Query("count") int count);
}

动态方法

public interface BlueService {
    @GET("book/search")
    Call<BookSearchResponse> getSearchBooks(
    @Header("Content-Range") String contentRange, 
    @Query("q") String name, @Query("tag") String tag, 
    @Query("start") int start, @Query("count") int count);
}

配置全局的请求头

public class RequestInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        Request request = original.newBuilder()
            .header("User-Agent", "Your-App-Name")
            .addHeader("key","value")
            .header("Accept", "application/vnd.yourapi.v1.full+json")
            .method(original.method(), original.body())
            .build();
        return chain.proceed(request);
    }
}

注意:

添加header参数Request提供了两个方法,一个是 header(key, value) ,另一个是.addHeader(key, value) ,两者的区别是,header()如果有重名的将会覆盖,而addHeader()允许相同key值的header存在

然后在OkHttp创建Client实例时,添加RequestInterceptor即可

private static OkHttpClient getNewClient(){
  return new OkHttpClient.Builder()
    .addInterceptor(new RequestInterceptor())//设置拦截器
    .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
    .build();
}
那么问题来了,通过注解也可以设置请求头,通过拦截器也可以设置请求头,那么谁会覆盖谁呢? 通过打印请求头得到结果:拦截器有可能会覆盖注解设置的请求头,关键 看拦截器调用的是header(key, value)还是addHeader(key, value) ,如果是header就会覆盖,

通过以上效果可以根据需求来动态设置请求头

  • 自定义解析器

但是如果您的需求要求数据传递都需要加密解密的情况下,那么默认的解析器显然不能瞒住要求
没有什么好说的,直接上代码

public final class DecodeConverterFactory extends Converter.Factory {

    public static DecodeConverterFactory create() {
        return create(new Gson());
    }

    public static DecodeConverterFactory create(Gson gson) {
        return new DecodeConverterFactory(gson);
    }

    private final Gson gson;

    private DecodeConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {

        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));

        return new DecodeResponseBodyConverter<>(gson, adapter);  //响应
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {

        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new DecodeRequestBodyConverter<>(gson, adapter); //请求


       }

}

请求

public class DecodeRequestBodyConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private final Gson gson;
    private final TypeAdapter<T> adapter;

    /**
     * 构造器
     */

    public DecodeRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }


    @Override
    public RequestBody convert(T value) throws IOException {
        //没有做加密处理,直接传递,如果要做,就在这里边做
        return RequestBody.create(MEDIA_TYPE, value.toString());

    }
} 

响应解密

public class DecodeResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson mGson;//gson对象
    private final TypeAdapter<T> adapter;
    private AES256JNCryptor cryptor;

    /**
     * 构造器
     */
    public DecodeResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.mGson = gson;
        this.adapter = adapter;
    }

    /**
     * 转换
     *
     * @param responseBody
     * @return
     * @throws IOException
     */
    @Override
    public T convert(ResponseBody responseBody) throws IOException {

        String string = responseBody.string();//获取返回的json,是加密过了

        if (cryptor == null) {
            cryptor = new AES256JNCryptor();//解密对象
        }

        //解密数据,然后解析成Bean返回
        try {
            string = new String(cryptor.decryptData(Base64.decode(string, Base64.NO_WRAP), Pwd.pwd.toCharArray()));//解密方法
        } catch (CryptorException e) {
            e.printStackTrace();
        }


        return string==null?null:adapter.fromJson(string);//解析返回


    }
}

当理解以上的每个环节,那么我们就可以封装一个单例模式的请求类

public class JkApiRequest {

    private static final long DEFAULT_TIMEOUT = 5;//超时时间

    private static JkApiRequest instance;//单例对象

    private Retrofit retrofit;//Retrofit 对象

    /**
     *
     * @param
     * @return
     * @author 私有构造方法,方法里边创建retrofit 对象
     * 
     * @createTime 2017/4/25
     * @lastModify 2017/4/25
     */
    private JkApiRequest() {

        retrofit = new Retrofit.Builder()
                .baseUrl(Constant.BaseUrl)
                .addConverterFactory(GsonConverterFactory.create())//可以添加自定义解析器和默认的解析器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加响应式编程的适配器
                .client(genericClient())//添加自定义的OKHttpClient对象
                .build();
    }

    /**
     * sington
     *
     * @param
     * @return
     * @author单例模式
     * @createTime 2017/4/25
     * @lastModify 2017/4/25
     */
   public synchronized static JkApiRequest getInstance() {
        if (instance == null) {
            instance = new JkApiRequest();
        }
        return instance;
    }





    //返回OkHttpClient对象
    private static OkHttpClient genericClient() {

        OkHttpClient httpClient = new OkHttpClient.Builder()
        //添加打印级别的拦截器
                    .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))
                .addInterceptor(new Interceptor() {//添加自定义拦截器,设置公共请求头
                    @Override
                    public Response intercept(Chain chain) throws IOException {

                        Request request = chain.request()
                                .newBuilder()
//                                .header("key","全局的头")//会覆盖
//                                .addHeader("key","全局的头")//不会覆盖
                                .build();
                        return chain.proceed(request);
                    }
                })
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)//设置超时时间
                .build();

        return httpClient;

    }


    /**
     * create api instance
     *
     * @param service api class
     * @return
     * @author 
     * @createTime 2017/4/25
     * @lastModify 2017/4/25
     */
    public <T> T create(Class<T> service) {
        return retrofit.create(service);
    }
}

Rxjava学习
内容是基于对扔物线rxjava详解的理解来编写的,目的在于自己以后用的时候更加方便快速的回忆起用法

1.Observable(被观察者)   可以通过creat创建对象,也可以通过just和from创建对象  他们是等价的

2.Subscribe (订阅)      

3.Observer(观察者)       可以创建Action1 不完整的  也可以创建Observer  或者创建Subscriber。


被观察者通过订阅持有观察者的对象来调用观察者的onNext(),onCompleted() 和 onError()方法

二. 观察者模式解释:

public Button btn;

btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                }
            });

通过 setOnClickListener() 方法,Button 持有 OnClickListener 的引用;当用户点击时,Button 自动调用 OnClickListener 的 onClick() 方法

(Button -> 被观察者、OnClickListener -> 观察者、setOnClickListener() -> 订阅,onClick() -> 事件),就由专用的观察者模式转变成了通用的观察者模式

三. RxJava ,使用的就是通用形式的观察者模式

不同于一般的观察者模式,rxjava会把每个事件单独处理,调用N次onNext方法 

当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。

onError(): 在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。

onCompleted() 和 onError() 二者也是互斥的,即在队列中调用了其中一个,就不应该再调用另一个。

四. Observer 和 Subscriber 两个观察者的区别

1.在 RxJava 的 subscribe 过程中,Observer 也总是会先被转换成一个 Subscriber 再使用。

2.onStart(): 这是 Subscriber 增加的方法。可以用于做一些准备工作,如果对准备工作的线程有要求,他就不适合了,他总是在被订阅的线程被调用

3.unsubscribe() :用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件。 废除被观察者中持有的观察者的引用,防止内存泄漏

六. RxJava线程控制 —— Scheduler

理解重点1:

在哪个线程调用 subscribe(),就在哪个线程生产事件;

在哪个线程生产事件,就在哪个线程消费事件。

如果需要切换线程,就需要用到 Scheduler (调度器)。

七. 线程调度器

Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler。


Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。


Schedulers.io(): I/O 读写文件 行为模式和 newThread() 差不多,区别在于可以重用空闲的线程,比 newThread() 更有效率。可以避免创建不必要的线程。


Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,


Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行。

八 . 线程切换

subscribeOn(): 指定 被观察者创建对象和事件所发生的线程-----也叫事件的生产   包括(map针对对象的直接变换时候的线程,filter过滤时候的线程,flatMap针对整个事件队列变换时候的线程)

observeOn(): 指定 观察者对象onNext()方法被调用的线程------也叫事件的消费

九 . 多次切换线程

Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .map(mapOperator) // 新线程,由 observeOn() 指定
    .observeOn(Schedulers.io())
    .map(mapOperator2) // IO 线程,由 observeOn() 指定
    .observeOn(AndroidSchedulers.mainThread) 
    .subscribe(subscriber);  // Android 主线程,由 observeOn() 指定

高能知识点:

1.当使用了多个 subscribeOn() 的时候,只有第一个 subscribeOn() 起作用

2.当使用多个observeOn()的时候,他管理下一订阅的线程,如上边的代码所展现的

结合以上封装,使用代码

    /**
     * 点击事件二
     *
     * @param view
     */
    public void Demo2(View view) {

        Observable.just(5) IO 线程,由 subscribeOn() 指定
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())//让下边的转换在io线程
                .flatMap(new Func1<Integer, Observable<Bean1>>() {
                    @Override
                    public Observable<Bean1> call(Integer integer) {

                        return JkApiRequest.getInstance().create(RequestServices.class)
                                .getTop250Observable(0, integer);//联网获取相应对象

                    }
                })
                .observeOn(AndroidSchedulers.mainThread())//让下边执行在主线程
                .subscribe(new Action1<Bean1>() {
                    @Override
                    public void call(Bean1 bean1) {
                        tv.setText(bean1.title);

                    }
                });


    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值