首先介绍Retrofit: Retrofit是Square 公司开发的一款正对Android 网络请求的框架。底层基于OkHttp 实现。版本要求至少需要java7或者Android2.3。
github地址
使用
在项目的build.gradle下dependencies下添加:
compile 'com.squareup.retrofit2:retrofit:2.1.0'
创建retrofit 实例
String Basrurl = "Https://api.douban.com/v2/movie/";
//Basrurl 代表服务器根地址
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Basrurl)
.addConverterFactory(GsonConverterFactory.create())
.build();
Retrofit2的baseurl必须要以斜线结束,不然会抛出一个IllegalArgumentException。
添加一个factory 来响应返回数据进行序列化解析。这里添加Gson解析。
在你项目的build.gradle上的dependencies下加入:
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
还有其他converter如下:
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
定义接口.
在retrofit2中,端口是定义在一个interface里面的,通过注解来映射参数以及请求类型或者请求头等细节。另外返回值都是一个被参数化得Call< T >对象。
public interface HttpInterface {
/**
* @param start 开始位置
* @param count 数量
* @return
*/
@GET("top250")
Call<Object> getMovie(@Query("start") int start,
@Query("count") int count);
}
各种注解含义(主要解释GET和POST两种):
@Query
用于GET请求参数传递
@GET("login/users")
Call<List<User>> checkLogin(@Query("id") int useId);
等同于:url后面接?加参数的K-V值
@GET("loger/users?id=useId")
@QueryMap
用于GET多参数集成一个Map统一上传
@GET("movie/search")
Call<MovieSearchEntity> getSearchMovies(@Query("type") String type,
@QueryMap Map<String, String> params);
调用时候将多个参数存入一个map里面一起调用,如下:
Map<String, String> params= new HashMap<>();
map.put("name", "星际");
map.put("time", null);
map.put("start", "0");
map.put("count", "5");
Call<MovieSearchEntity> call = httpService.getSearchMovies("科幻",params);
等同于:
@GET("movie/search?type=科幻"&name=星际&start=0&count=5)
@Path
用于url上的占位符,可以用双大括号包裹,参数中替换,可用于任何请求方式中。如下:
@GET("movie/{id}")
Call<MovieEntity> getMovie(@Path("id") String id);
根据不同的id得到的数据也不同。可以动态传递,比如调用:
Call<BookResponse> call = mBlueService.httpService("123456");
相对与请求以下@GET
@GET("movie/123456")
@field
用于post请求,post请求需要把参数放置在请求体中,并非是拼接在url后面:
@POST("user/login")
@FormUrlEncoded
Call<Object> login(@Field("usename") String usename,
@Field("password") String password);
@FormUrlEncoded是会自动将请求参数的类型调整为表单类型application/x-www-form-urlencoded,不能用于GET请求,如果不用传递参数可以不用加。
@Field注解是将每个请求参数都存在请求体中,还可以添加encoded参数,该参数为boolean型,比如:
@Field(value = "usename",encoded = ture) String usename
encoded参数为true的话,key-value-pair将会被编码,即将中文和特殊字符进行编码转换。
@Body
用于POST请求,可以将实例对象转换为对应的字符串传递参数,如果在Retrofit初始化时候添加了GsonConverterFactory,则是将body转换为gson字符进行串传递的,其他Converter大同小异。如下:
@POST("user/login")
@FormUrlEncoded
Call<String> loginTestBody(@Body LoginEntity loginEntity);
public class LoginEntity {
public String usename;
public String password;
public String vip;
public String money;
}
@Part
用于文件的上传,这里先不介绍
@Headers
Retrofit提供了两种定义Http请求头的参数:静态和动态。静态方法在头部信息初始化的时候已经固定写死了,而动态方法则必须为每个请求单独设置。
静态设置:
@Headers("User-Agent: android")
@POST("login")
@FormUrlEncoded
Call<Object> login1(@Field("usename") String usename,
@Field("password") String password);
添加多个header参数也可以,类似数组:
@Headers({"User-Agent: android"
,"Cache-Control: max-age=640000"})
@POST("login")
@FormUrlEncoded
Call<Object> login1(@Field("usename") String usename,
@Field("password") String password);
静态也可以设置okhttp的拦截器去添加头布局:
okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.addHeader("userDeviceID", MyApplication.DEVICETOKEN)
.header("User-Agent", "android-27")
.build();
return chain.proceed(request);
}
})
.cookieJar(new CookiesManager())
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)//设置写的超时时间
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.build();
需要注意的是,addHeader是可以存在相同的key值,而header则是覆盖重复的。
设置好OkHttp后在Retrofit中client加入okHttpClient
retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseUrl)
.build();
动态设置:
@POST("login")
@FormUrlEncoded
Call<Object> login2(@Header("User-Agent") String userAgent,
@HeaderMap Map<String,String> headers,
@Field("usename") String usename,
@Field("password") String password);
@Url
需求总是变化的,如果需要请求的地址不是以baseUrl开头的话,就需要使用这个注解,直接请求完整的url,忽视baseurl:
@POST
@FormUrlEncoded
Call<Object> login3(@Url String url,
@Field("usename") String usename,
@Field("password") String password);
@Streaming
大文件下载的时候,同个该注解,防止直接写入内存中。比如app更新时候,下载music的时候。
@Streaming
@GET
Observable<ResponseBody> download(@Header("RANGE") String start, @Url String url);
前面Retrofit的各种参数讲的差不多了,都不知道刚开始讲的是什么(手动滑稽)
上面讲了创建Retrofit的实例,创建了请求接口。通过retrofit动态代理拿到接口对象的实例:
String Basrurl = "Https://api.douban.com/v2/movie/";
//Basrurl 代表服务器根地址
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Basrurl)
.addConverterFactory(GsonConverterFactory.create())
.build();
httpInterface = retrofit.create(HttpInterface.class);
拿到getMovie返回的Call对象:
Call<Object> movie = httpInterface.getMovie(0, 10);
执行enqueue(可以链式连写):
movie.enqueue(new Callback<Object>() {
@Override
public void onResponse(Call<Object> call, Response<Object> response) {
Object body = response.body();
body.toString();
Log.e("TAG",body.toString());
try {
Gson gson = new Gson();
String str = gson.toJson(body);
JSONObject jsonObject = new JSONObject(str);
JSONArray subjectsArray = jsonObject.optJSONArray("subjects");
for(int i = 0;i < subjectsArray.length(); i++){
JSONObject jsonChild = subjectsArray.getJSONObject(i);
String title = jsonChild.optString("title");
Log.e("TAG","TOP250电影第"+(i+1)+"的名字: "+title);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<Object> call, Throwable t) {
}
});
这里简单的请求就完成了,这里是打印出来的,没有界面,比较懒。
Call对象还可以执行取消操作,当网络请求还没有完成的时候取消:
call.cancel();
是不是觉得很麻烦,还要一层层的解析。是的,既然是泛型,为何不加入实体类就行了嘛。加入泛型改动如下:
实体类Movie250Modle实现Serializable接口,完成序列化操作。
接口添加泛型:
@GET("top250")
Call<Movie250Modle> getMovie(@Query("start") int start,
@Query("count") int count);
请求时回调会自动加入泛型,如下:
String Basrurl = "Https://api.douban.com/v2/movie/";
//Basrurl 代表服务器根地址
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Basrurl)
.addConverterFactory(GsonConverterFactory.create())
.build();
httpInterface = retrofit.create(HttpInterface.class);
Call<Movie250Modle> movie = httpInterface.getMovie(0, 10);
movie.enqueue(new Callback<Movie250Modle>() {
@Override
public void onResponse(Call<Movie250Modle> call, Response<Movie250Modle> response) {
Movie250Modle movie250Modle = response.body();
List<Movie250Modle.SubjectsBean> movie250ModleSubjects = movie250Modle.getSubjects();
for(int i = 0; i < movie250ModleSubjects.size(); i++){
Log.e("TAG","实体类TOP250电影第"+(i+1)+"的名字: "+movie250ModleSubjects.get(i).getTitle());
}
}
@Override
public void onFailure(Call<Movie250Modle> call, Throwable t) {
}
});
看看结果也是一样的:
添加OkHttp参数
你们所知道的,retrofit2内部网络请求使用的就是okhttp,光是用上面的retrofit2往往是不够全面的,还需要添加okhttp参数:
/**
* addInterceptor 设置拦截器
* cookieJar 设置cook管理类
* readTimeout 设置读取超时时间
* writeTimeout 设置写的超时时间
* connectTimeout 设置链接超时时间
* retryOnConnectionFailure 设置是否重试链接
*/
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new MyInterceptor())
.cookieJar(new CookiesManager())
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10,TimeUnit.SECONDS)
.connectTimeout(10,TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
前面讲了
cookieJar 是cook持久化管理,用于免登陆使用
addInterceptor是前面设置header用过的,可以添加一些参数:
class MyInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl httpUrl = request.url()
.newBuilder()
// add common parameter
.addQueryParameter("token", "123")
.addQueryParameter("username", "tt")
.build();
Request build = request.newBuilder()
// add common header
.addHeader("contentType", "text/json")
.url(httpUrl)
.build();
Response response = chain.proceed(build);
return response;
}
}
在retrofit创建的时候加入client:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Basrurl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
这样就设置好了okhttp的参数
网络日志
okhttp支持网络请求的信息打印,这样更方便查看网络请求信息。在项目中build.gradle文件中引入logging-interceptor:
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
在初始化okhttp时候加入:
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
/**
* addInterceptor 设置拦截器
* addInterceptor 设置logg拦截器
* cookieJar 设置cook管理类
* readTimeout 设置读取超时时间
* writeTimeout 设置写的超时时间
* connectTimeout 设置链接超时时间
* retryOnConnectionFailure 设置是否重试链接
*/
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new MyInterceptor())
.addInterceptor(loggingInterceptor)
.cookieJar(new CookiesManager())
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10,TimeUnit.SECONDS)
.connectTimeout(10,TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
没错HttpLoggingInterceptor也是implements Interceptor接口;loggingInterceptor其中包含了4个打印等级:在logcat用okhttp过滤信息
NONE
No logs,无信息
BASIC
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
打印请求类型,URL,请求体大小,返回值状态以及返回值的大小
具体例子:
HEADERS
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
打印返回请求和返回值的头部信息,请求类型,URL以及返回值状态码
BODY
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }</pre>
*/
打印返回请求和返回值的头部信息,请求类型,URL以及返回值状态码以及Body信息(数据信息)
加入Rxjava
如果rxjava没学过的,可以看看这个大牛写的博客:
初学者Rxjava2教程
先引入rxjava全家桶,在项目build.gradle下加入:
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.0.1'
// 此处一定要注意使用RxJava2的版本
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
然后在Retrofit实例化加入addCallAdapterFactory
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Basrurl)
.client(okHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
httpInterface = retrofit.create(HttpInterface.class);
接口类中的修改,不能返回Call对象了,要返回被观察者,,观察者观察网络请求,将网络请求当做被观察者,完成一个异步操作。
/**
* @param start 开始位置
* @param count 数量
* @return
*/
@GET("top250")
Observable<Movie250Modle> getMovie(@Query("start") int start,
@Query("count") int count);
那么网络请求拿到的接口对象如下:
Flowable<Movie250Modle> movieObservable = httpInterface.getMovie(0, 10);
执行异步网络操作:
movieObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Movie250Modle>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Movie250Modle value) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
等待,我们这里用的是rxjava2,所以对应被观察者(上游)Observable变成Flowable,下游Observer就变成了Subscriber,所以就变成如下:
movieObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Movie250Modle>() {
@Override
public void onSubscribe(Subscription s) {
}
@Override
public void onNext(Movie250Modle movie250Modle) {
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
subscribeOn(Schedulers.io())指定被观察者事件发生io线程,耗时操作
observeOn(AndroidSchedulers.mainThread())指定观察者事件发生在主线程,更新UI必须在主线程中。
subscribe(new Subscriber…..) 被观察者订阅观察者。
基本使用已经完成了,后续写下请求封装。