0%

Retrofit2学习记录

Retrofit2

学习使用Retrofit和RxJava,简单记录一下重要的api和使用方法

需啊添加的依赖如下:

1
2
3
4
5
6
7
8
// Retrofit主要类库
implementation 'com.squareup.retrofit2:retrofit:2.0.2'
// Gson
implementation 'com.squareup.retrofit2:converter-gson:2.0.2'
// okHttp
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
// 日志拦截器
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'

Retrofit主要使用注解,标记请求头,请求方式,请求参数,请求格式。主要记一下请求参数和请求格式的注解意义:

注解 使用
@Body 多用于post请求发送非表单数据,比如想要以post方式传递json格式数据
@Filed 多用于post请求中表单字段,Filed和FieldMap需要FormUrlEncoded结合使用
@FiledMap 和@Filed作用一致,用于不确定表单参数
@Part 用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况
@PartMap 用于表单字段,默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传
@Path 用于url中的占位符
@Query 用于Get中指定参数
@QueryMap 和Query使用类似
@Url 指定请求路径

注解 使用
@FormUrlEncoded 表示请求发送编码表单数据,每个键值对需要使用@Field注解
@Multipart 表示请求发送multipart数据,需要配合使用@Part
@Streaming 表示响应用字节流的形式返回.如果没使用该注解,默认会把数据全部载入到内存中.该注解在在下载大文件的特别有用

使用示例:

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
public interface ApiService {
@Headers({
"User-Agent: android",
"apikey: 28",
})

/*
设置Url的三种方式
*/
@POST()
Call<Entity> post1(@Url String url, @QueryMap Map<String, String> map);

@POST("api/en/login")
Call<Entity> post2(@QueryMap Map<String, String> map);

@POST("api/{url}/login")
Call<Entity> post3(@Path("url") String url, @QueryMap Map<String, String> map);

/**
* @param getId Query请求参数设置,将会以"?id=getId"
*/
@GET("...")
Call<Entity> get(@Header("token") String token, @Query("id") int getId);

/**
* 使用@Body注解实现传入实体,自行转化成Json
*/
@POST("api/{url}/login")
Call<Entity> login(@Path("url") String url, @Body Entity post);

/**
* 上传图片
* retrofit 2.0的上传和以前略有不同,使用@Multipart注解和@Part MultipartBody实现。
*/
@Multipart
@POST("{url}")
Call<Entity> upload(@Path("url") String url, @Part MultipartBody.Part file);

/**
* 多张图片上传
*/
@Multipart
@POST("upload")
Call<Entity> upload(@PartMap Map<String, MultipartBody.Part> map);

/**
* 上传图文
*/
@Multipart
@POST("")
Call<Entity> register(@Body Entity post, @PartMap Map<String, MultipartBody.Part> map);

/**
* 文件下载
*/
@Streaming
@GET
Call<Entity> downloadPicture(@Url String fileUrl);

/**
* 若要下载大体积的文件,如10m以上,强烈建议使用@Streaming进行注解,否则将会出现IO异常.
*/
@Streaming
@GET
Observable<Entity> downloadPicture2(@Url String fileUrl);

@POST()
@FormUrlEncoded
Observable<Entity> executePost(@FieldMap Map<String, Object> maps);
}

注:使用@Path出现url被转义问题
设置Url的方法中,如果这样使用,就会出现url被转义,即‘/’被转义为乱码
1
2
@POST("{url}")
Call<Entity> post(@Path("url") String url);

因此使用这种方式需要这样写
1
2
@POST("{url}")
Call<Entity> post(@Path(value = "url", encoded = true) String url)

使用与配置

说了这么多,终于开始具体使用。

简单使用

首先需要创建一个请求方法接口ApiService,在其中编写需要用到的请求方法接口;
在创建一个RetrofitService类用于初始化及启动retrofit。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyRetrofitService {
private MyRetrofitService(){}

static OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10000L, TimeUnit.MILLISECONDS)
.readTimeout(10000L, TimeUnit.MILLISECONDS)
.build();

public static Retrofit retrofit = new Retrofit.Builder()
.baseUrl("URL")
.client(okHttpClient)
// 设置json转换器
.addConverterFactory(GsonConverterFactory.create())
.build();
}

具体调用 时:
1
2
3
4
5
6
7
8
9
10
11
12
ApiService api = MyRetrofitService.retrofit.create(ApiService.class);
Call<ResponseBody> mData = api.get();
mData.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

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

}
});

关于OkHttpClient更多具体配置

拦截器

addNetworkInterceptor 用于添加网络拦截器Network Interfacetor,它会在request和response时分别被调用一次;
addInterceptor 用于添加应用拦截器Application Interceptor,只会在response被调用一次

1. 日志拦截器

使用addNetworkInterceptor()添加到OkHttpClient中
日志拦截器有两种创建方式:

  1. 使用HttpLoggingInterceptor
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static HttpLoggingInterceptor getHttpLoggingInterceptor() {
    HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(
    new HttpLoggingInterceptor.Logger() {
    @Override
    public void log(String message) {
    Log.e("OkHttp", "log : " + message);
    }
    });
    loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
    return loggingInterceptor;
    }
  2. 普通Interceptor
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private class LogInterceptor implements Interceptor {
    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    long t1 = System.nanoTime();
    Log.d("OkHttp", "HttpHelper1" + String.format("Sending request %s on %s%n%s",
    request.url(), chain.connection(), request.headers()));

    okhttp3.Response response = chain.proceed(request);
    long t2 = System.nanoTime();

    Log.d("OkHttp", "HttpHelper2" + String.format("Received response for %s in %.1fms%n%s",
    response.request().url(), (t2 - t1) / 1e6d, response.headers()));
    return response;
    }
    }

    2. 请求头拦截器

    使用addInterceptor()添加到OkHttpClient中

    请求头拦截器为了让服务端能更好的识别该请求,服务器那边通过请求头判断该请求是否为有效请求等…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 使用addHeader()不会覆盖之前设置的header,若使用header()则会覆盖之前的header
*/
public static Interceptor getRequestHeader() {
Interceptor headerInterceptor = new Interceptor() {

@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request.Builder builder = originalRequest.newBuilder();
builder.addHeader("version", "1");
builder.addHeader("time", System.currentTimeMillis() + "");

Request.Builder requestBuilder = builder.method(originalRequest.method(), originalRequest.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
};
return headerInterceptor;
}

3. 统一请求拦截器

使用addInterceptor()添加到OkHttpClient中
统一请求拦截器的功能跟请求头拦截器相类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 拦截器Interceptors
* 统一的请求参数
*/
private Interceptor commonParamsInterceptor() {
Interceptor commonParams = new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originRequest = chain.request();
Request request;
HttpUrl httpUrl = originRequest.url().newBuilder().
addQueryParameter("paltform", "android").
addQueryParameter("version", "1.0.0").build();
request = originRequest.newBuilder().url(httpUrl).build();
return chain.proceed(request);
}
};
return commonParams;
}

4. 时间拦截器

使用addInterceptor()方法添加到OkHttpClient中
从响应中获取服务器返回的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 通过响应拦截器实现了从响应中获取服务器返回的time
*/
public static Interceptor getResponseHeader() {
Interceptor interceptor = new Interceptor() {

@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response response = chain.proceed(chain.request());
String timestamp = response.header("time");
if (timestamp != null) {
// 获取到响应header中的time
}
return response;
}
};
return interceptor;
}

缓存

  • 使用okhttp缓存,先要创建Cache,然后在创建缓存拦截器
    1
    2
    3
    4
    5
    6
    7
    // 创建Cache
    File httpCacheDirectory = new File(MyApp.getContext().getCacheDir(), "OkHttpCache");
    Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
    httpClientBuilder.cache(cache);
    // 设置缓存
    httpClientBuilder.addNetworkInterceptor(getCacheInterceptor2());
    httpClientBuilder.addInterceptor(getCacheInterceptor2());
  • 缓存拦截器
    缓存时间自己根据情况设定
    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
    /**
    * 在无网络的情况下读取缓存,有网络的情况下根据缓存的过期时间重新请求
    */
    public Interceptor getCacheInterceptor2() {
    Interceptor commonParams = new Interceptor() {
    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    if (!NetworkUtils.isConnected()) {
    //无网络下强制使用缓存,无论缓存是否过期,此时该请求实际上不会被发送出去。
    request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE)
    .build();
    }

    okhttp3.Response response = chain.proceed(request);
    if (NetworkUtils.isConnected()) {//有网络情况下,根据请求接口的设置,配置缓存。
    //这样在下次请求时,根据缓存决定是否真正发出请求。
    String cacheControl = request.cacheControl().toString();
    //当然如果你想在有网络的情况下都直接走网络,那么只需要
    //将其超时时间这是为0即可:String cacheControl="Cache-Control:public,max-age=0"
    int maxAge = 60 * 60; // read from cache for 1 minute
    return response.newBuilder()
    .header("Cache-Control", "public, max-age=" + maxAge)
    .removeHeader("Pragma")
    .build();
    } else {
    //无网络
    int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
    return response.newBuilder()
    .header("Cache-Control", "public,only-if-cached,max-stale=" + maxStale)
    .removeHeader("Pragma")
    .build();
    }

    }
    };
    return commonParams;
    }

    自定义CookieJar

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    httpClientBuilder.cookieJar(new CookieJar() {
    final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();

    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    cookieStore.put(url, cookies);//保存cookie
    //也可以使用SP保存
    }

    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
    List<Cookie> cookies = cookieStore.get(url);//取出cookie
    return cookies != null ? cookies : new ArrayList<Cookie>();
    }
    });

    使用RxJava

    使用RxJava的目的是,RxJava可以更加简洁的实现异步操作。

    RxJava的简单理解

    Rxjava使用观察者模式实现,因此其中有几个基本概念就是Observable被观察者,Observer观察者,subscribe订阅事件。(关于观察者模式最经典的实现就是OnClickListener)Observable与Observer通过subcribe()实现订阅关系,Observable就可以在发生指定事件时通知Observer。
    不同于传统的观察者模式,Rxjava的回调方法除了onNext()(相当于onClick()),还有onCompleted()和onError()方法。由于Rxjava会将每个事件作为一个事件队列处理,并把每个事件单独处理。以此为前提既可以理解另外两个方法:
  • onCompleted() 当事件队列结束,不会有新的onNext()发出时,即会触发onCompleted()方法。
  • onError() 当事件队列出现异常是会触发onError(),并且会同时停止事件队列,不允许新的事件发出。
  • 那么可能会有疑问,异常以后没有事件发出,是否会调用onCompleted()。答案是不会,在一个正常的时间序列中,onCompleted()与onError()方法有且只有一个,二者互斥,即调用其中一个就不会调用另外一个,且都是事件队列中最后一个调用。

Rxjava的简单实现

注:一下代码的实现依据rxjava版本为,io.reactivex.rxjava:1.3.8,是一个比较旧的版本了,从这以后rxjava开始2.0版本,有了一些新的改动。但是以此版本理解RxJava的使用思想还是没有问题,而且更加简易更好理解。

  1. 创建观察者Observer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Observer<String> observer = new Observer<String>() {
    @Override
    public void onNext(String s) {
    Log.d(tag, "Item: " + s);
    }

    @Override
    public void onCompleted() {
    Log.d(tag, "Completed!");
    }

    @Override
    public void onError(Throwable e) {
    Log.d(tag, "Error!");
    }
    };

    除了Observer接口,RxJava还内置了一个实现了Observer的抽象类:Subscriber。 Subscriber是Observer的扩展,但他们的基本使用方式完全一样(同上)。
    实质上,在RxJava的subscribe过程中,Observer也总是会先被转换成一个Subscriber再使用。

    关于区别
    onStart(): 这是 Subscriber 增加的方法。它会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行), onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法,具体可以在后面的文中看到。
    unsubscribe(): 这是 Subscriber 所实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。 unsubscribe() 这个方法很重要,因为在 subscribe() 之后, Observable 会持有 Subscriber 的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(例如 onPause() onStop() 等方法中)调用 unsubscribe()来解除引用关系,以避免内存泄露的发生。

  2. 创建Observable被观察者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
    subscriber.onNext("Hello");
    subscriber.onNext("Hi");
    subscriber.onNext("Aloha");
    subscriber.onCompleted();
    }
    });

    create()方法中传入了参数OnSubscribe对象会被存储在Observable对象中,相当于一个计划表。当Observable被订阅,将回调call()方法,依次触发call中的事件序列。因此例如实际使用中,就可以在call()中进行异步操作,如网络请求,然后将请求的结果通过onNext()的参数发送出去。
    除了使用create()方法,Rxjava还提供其他方法创建事件队列

    • just(T…)
      1
      2
      // 作用等同上例
      Observable observable = Observable.just("Hello", "Hi", "Aloha");
    • from(T[]) / from(Iterable<? extends T>)
      1
      2
      3
      // 将传入的数组或可迭代对象拆分成具体对象后,依次发送出来。
      String[] words = {"Hello", "Hi", "Aloha"};
      Observable observable = Observable.from(words);
  3. 实现订阅Subscribe
    1
    observable.subscribe(observer);

    这看起来是『observalbe 订阅了 observer / subscriber』,思维逻辑上虽然有些别扭,但是这有利于流式api的设计
    Observable.subscribe()内部代码结构如下

    1
    2
    3
    4
    5
    public Subscription subscribe(Subscriber subscriber) {
    subscriber.onStart();
    onSubscribe.call(subscriber);
    return subscriber;
    }

    将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().

进阶用法

subscriber()支持不完整定义的回调,可以根据定义自动创建Subscriber。使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Action1<String> onNextAction = new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s );
}
};

// Lambda表达式写法,内容同上
Action1<Throwable> onErrorAction = (e) -> System.out.println("error" + e);

Action0 onComAction = () -> System.out.println("com");

Observable observable = Observable.just("Hello", "Aloha");

// 自动创建Subscriber
observable.subscribe(onNextAction);
observable.subscribe(onNextAction, onErrorAction);
observable.subscribe(onNextAction, onErrorAction, onComAction);

简单解释一下这段代码中出现的 Action1 和 Action0。
Action0 是 RxJava 的一个接口,它只有一个方法 call(),这个方法是无参无返回值的;由于 onCompleted() 方法也是无参无返回值的,因此 Action0 可以被当成一个包装对象,将 onCompleted() 的内容打包起来将自己作为一个参数(onComAction)传入 subscribe() 以实现不完整定义的回调。这样其实也可以看做将 onCompleted() 方法作为参数传进了 subscribe(),相当于其他某些语言中的『闭包』。
Action1 也是一个接口,它同样只有一个方法 call(T param),这个方法也无返回值,但有一个参数;与 Action0 同理,由于 onNext(T obj) 和 onError(Throwable error) 也是单参数无返回值的,因此 Action1 可以将 onNext(obj) 和 onError(error) 打包起来传入 subscribe() 以实现不完整定义的回调。事实上,虽然 Action0 和 Action1 在 API 中使用最广泛,但 RxJava 是提供了多个 ActionX 形式的接口 (例如 Action2, Action3) 的,它们可以被用以包装不同的无返回值的方法。

简单来说就是一个有参一个无参的包装类,无参的类可以用于构建onCompleted(),有参的可以用于构建onNext()或onError()。subsrcribe()重载了3个关于ActionX的方法,就是上述代码中的3个。比如第三个方法参数表为(Action1<? super T>, Action1, Action0),以此限定了三个参数的位置。

接下来用一个例子小结一下:

由 id 取得图片并显示: 由指定的一个 drawable 文件 id drawableRes 取得图片,并显示在 ImageView 中,并在出现异常的时候打印 Toast 报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
@Override
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
}).subscribe(new Observer<Drawable>() {
@Override
public void onNext(Drawable drawable) {
imageView.setImageDrawable(drawable);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
}
});

以上代码,对上面RxJava的内容进行了小结。可以看到,在Observable的call()方法中进行所要进行的异步操作,然后在subcribe的Observer(Subscriber)中通过onNext,onError,onCompleted设置回调方法。这就是Rxjava的基础使用格式。

异步-线程控制Scheduler

然而到这里,都和开头所说RxJava的主要作用异步还没有什么关系。因为Rxjava默认是在运行在同一线程。
想要实现异步机制,就需要用到RxJava的第四个概念Scheduler。

观察者模式本身的目的就是『后台处理,前台回调』的异步机制
在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。

RxJava通过Scheduler指定一个代码段需要运行在哪一个进程。

  • subscribeOn() 指定subscribe()发生的进程。即 Observable.OnSubscribe 被激活时所处的线程,事件产生的线程。
  • observeOn() 指定Subscriber所运行的线程。即事件消费进程。

RxJava中内置了几个进程,以适用一些默认场景:

  • Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler。
  • Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
  • Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
  • Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
  • Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行。

还是使用Android中图片显示的例子

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
int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
@Override
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Observer<Drawable>() {
@Override
public void onNext(Drawable drawable) {
imageView.setImageDrawable(drawable);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
}
});

即 加载图片将会发生在 IO 线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿。

Schedule的原理:变换

先具体实现一下RxJava+Retrofit

Retrofit参考链接
RxJava参考链接


未完待续…


未完 待续 (╯‵□′)╯︵┻━┻