后面的文件上传会涉及到链式结构,小伙伴可以先去看一下下面的文章。
说到网络框架,从入门级别的android-async-http->Volley->Okhttp->Retrofit+RxJava,之前我比较钟情于android-async-http,使用简单暴力,后来Google在API中移除掉了HttpClient的相关的支持,我又大力使用Okhttp。
第一眼看Retrofit的感觉是:哼,啥玩意啊,如果变换一个参数,起码改两个地方,看起来不灵活,难用,但是这只是我的个人看法,既然这么多大牛支持的网络框架应该不会这么low吧,于是运用了一下。说实话,第二次用我就有点喜欢他了,后来一直再用,也摸透了Retrofit的套路。
重点:Retrofit+RxJava的那种优雅是没有用过这个组合框架的人永远想象不出来的,反正我用了之后承认自己是个low比了,哈哈!
一、实现步骤:
(1)引用Gradle :
compile 'com.squareup.retrofit2:retrofit:2.0.2' //Retrofit支持库
compile 'com.squareup.retrofit2:converter-gson:2.0.2'//Gson解析转换器
创建一个API的类,在API中实现下面的两个方法(参数接口+接口实例)
(2)创建参数接口:
public interface RetrofitAPI {
//用户登陆
@GET("user/UserAccountLoginApi.ajax")
Call<String> getUserInfo(@Query("loginAccount") String username,
@Query("loginPassword") String password,
@Query("operateDevice") String operateDevice);
}
(3)创建接口实例:(这是通用的方法,目的就是创建出接口的实例,可重复调用)
public static RetrofitAPI Retrofit() {
if (retrofitAPI == null) {
retrofitAPI = new Retrofit.Builder()
.baseUrl("http://114.55.73.60:8044//interface//app/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RetrofitAPI.class);
}
return retrofitAPI;
}
Retrofit定义了一个baseUrl,网络请求的整个url是:baseUrl+@GET后面的url,比如上面的这个网络请求的url就是"http://114.55.73.60:8044//interface//app/user/UserAccountLoginApi.ajax",因为一个项目中网络请求的url之前的部分是一样的,所以我们没有必要都重新写一遍,只需要搞懂url后面的部分就可以了。
(4)调用:
API.Retrofit().getUserInfo("18058810112", "123456", "ANDROID").enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
Log.v("TAG",response.body().toString());
//解析Json
}
@Override
public void onFailure(Call<String> call, Throwable t) {
}
});
这样一个Retrofit的网络请求就完成了,不过这样每次请求下来的数据都得手动解析一下,会有冗余代码,Retrofit提供了自动解析的Gson的支持,在上面的请求中有这么一句:addConverterFactory(GsonConverterFactory.create()),这句话的意思就是添加Gson转换器,这样请求下来的数据就会自动被转换成一个实体类,上代码
创建一个UserBean实体类:
public class UserBean {
/**
* token : YWG6L4IBTB1W7GO1TQVUIV1EMTHAX40R
*/
private DataBean data;
/**
* data : {"token":"YWG6L4IBTB1W7GO1TQVUIV1EMTHAX40R"}
* status : 200
* statusMessage : 成功
*/
private int status;
private String statusMessage;
public DataBean getData() {
return data;
}
public void setData(DataBean data) {
this.data = data;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getStatusMessage() {
return statusMessage;
}
public void setStatusMessage(String statusMessage) {
this.statusMessage = statusMessage;
}
public static class DataBean {
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
}
修改部分代码:
public interface RetrofitAPI {
//用户登陆
@GET("user/UserAccountLoginApi.ajax")
//把String改成UserBean
Call<UserBean> getUserInfo(@Query("loginAccount") String username,
@Query("loginPassword") String password,
@Query("operateDevice") String operateDevice);
在调用的时候Response后面的就会变成相应的实体类,如下:
API.Retrofit().getUserInfo("18058810112", "123456", "ANDROID").enqueue(new Callback<UserBean>() {
@Override
public void onResponse(Call<UserBean> call, Response<UserBean> response) {
textview.setText(response.body().getData().getToken());
}
@Override
public void onFailure(Call<UserBean> call, Throwable t) {
}
});
token已经被显示出来了
二、相应的API使用详解
Get方法
(1)@Query
这种方式请求参数都会以key=value的方式拼接在url后面
(2)@QueryMap
如果Query参数比较多,那么可以通过@QueryMap方式将所有的参数集成在一个Map统一传递。
调用的时候将所有的参数集合在统一的map中即可:
Map<String, String> map = new HashMap<>();
map.put("loginAccount", "123456");
map.put("loginPassword", "123456");
map.put("operateDevice", "ANDROID");
API.Retrofit().getUserInfo(map).enqueue();
(3)Query集合
假如你需要添加相同Key值,但是value却有多个的情况,一种方式是添加多个@Query参数,还有一种简便的方式是将所有的value放置在列表中,然后在同一个@Query下完成添加,实例代码如下:
public interface BlueService {
@GET("book/search")
Call<UserBean> getUserInfo (@Query("loginAccount") List<String> name);
}
(4)如果其中有参数不需要传就直接写成null
API.Retrofit().getUserInfo("18058810112", "123456", null).enqueue()
(5)@Path
这个不怎么常用,大家可以自行查询用法,也是很简单的
Post方法
(1)@Body
如果Post请求参数有多个,那么统一封装到类中应该会更好,这样维护起来会非常方便
@FormUrlEncoded
@POST("user/UserAccountLoginApi.ajax")
Call<String> login(@Body User user);
public class User {
public String loginAccount;
public String loginPassword;
public String operateDevice;
}
(2)@field
Post请求需要把请求参数放置在请求体中,而非拼接在url后面
@FormUrlEncoded
@POST("user/UserAccountLoginApi.ajax")
Call<String> addReviews(@Field("loginAccount") String loginAccount, @Field("loginPassword") String loginPassword,
@Field("operateDevice") String operateDevice);
(3)@FieldMap
假如说有更多的请求参数,那么通过一个一个的参数传递就显得很麻烦而且容易出错,这个时候就可以用FieldMap
@FormUrlEncoded
@POST("user/UserAccountLoginApi.ajax")
Call<String> addReviews(@FieldMap Map<String, String> fields);
三、文件上传详解:
对于文件上出传,真正想弄懂他的原理有点麻烦,csdn的“一叶扁舟“ 大神也是费了很大一番周折。
文件上传这一块与后台交互的时候情况比较复杂,牵扯到接口的问题,很容易出问题,经过大量的测试,最后找到了一个方式可以完成各种情况的文件+参数上传
前文提到,普通的表单格式可以直接用GET请求就可以的,对于json格式请求,可以直接用户POST中的@Body实体传递,对于文件上传,我们必须用到一个 @Multipart,
通过设置相应的请求头文件来让后台服务器识别谁是参数谁是File文件。
上传文件的接口我们可以写成
@Multipart
@POST("you methd url upload/")
Call<ResponseBody> uploadFile(@Part("avatar\\\\"; filename=\\\\"avatar.jpg") RequestBody file);
然后构造RequstBody
RequestBody body = RequestBody.create(MediaType.parse("image/*"), file);
但是多文件上传我们需要不断的添加很多参数,太麻烦,有没有一个比较完善的方式,可以将参数和图片写在一起,一起提交给后台呢?
目前我测试的是可以的,怎么写呢?
1、声明接口
//图片上传
@Multipart
@POST("upload")
Call<String> updateImage(@PartMap Map<String,RequestBody> params);
2、创建封装参数的工具类HttpParameterBuilder:
public class RetrofitParameterBuilder {
private static RetrofitParameterBuilder mParameterBuilder;
private static Map<String, RequestBody> params;
/**
* 构建私有方法
*/
private RetrofitParameterBuilder() {
}
/**
* 初始化对象
*/
public static RetrofitParameterBuilder newBuilder(){
if (mParameterBuilder==null){
mParameterBuilder = new RetrofitParameterBuilder();
if (params==null){
params = new HashMap<>();
}
}
return mParameterBuilder;
}
/**
* 添加参数
* 根据传进来的Object对象来判断是String还是File类型的参数
*/
public RetrofitParameterBuilder addParameter(String key, Object o) {
if (o instanceof String) {
RequestBody body = RequestBody.create(MediaType.parse("text/plain"), (String) o);
params.put(key, body);
} else if (o instanceof File) {
RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);
params.put(key + "\"; filename=\"" + ((File) o).getName() + "", body);
}
return this;
}
/**
* 初始化图片的Uri来构建参数
* 一般不常用
* 主要用在拍照和图库中获取图片路径的时候
*/
public RetrofitParameterBuilder addFilesByUri(String key, List<Uri> uris) {
for (int i = 0; i < uris.size(); i++) {
File file = new File(uris.get(i).getPath());
RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), file);
params.put(key + i + "\"; filename=\"" + file.getName() + "", body);
}
return this;
}
/**
* 网络请求完成之后,别忘了在回调函数中调一下这个方法
* 如果你用的是RxJava,可以再onCompleted和onError中调一下
* 如果不清空,可能会出现一个问题,就是下一次的网络请求回带有上次网络请求的参数
* 因为我这里创建的构造参数的方法是类似于单例,实例不会再次创建
* 这里是需要注意的地方
*/
public void cleanParams(){
if (params!=null){
params.clear();
}
}
/**
* 构建RequestBody
* 可以添加公共参数
*/
public Map<String, RequestBody> bulider() {
//添加公共的参数,如Token验证
RequestBody body = RequestBody.create(MediaType.parse("text/plain"), (String) token);
params.put(“token”, body);
return params;
}
}
3、java调用
Map<String,RequestBody> params = HttpParameterBuilder.newBuilder()
.addParameter("username","lisi")
.addParameter("password","123456789")
.addParameter("img1",file1)
.addParameter("img2",file2)
.bulider();
API.Retrofit().updateImage(params).enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
Log.v("TAG","成功");
}
@Override
public void onFailure(Call<String> call, Throwable t) {
Log.v("TAG","失败");
}
});
抓包可以得到效果图:
到这里retrofit的基本用法也就完成了,总之折腾了这么久也是值得的这个成功之后,再结合RxAndroid也就轻而易举了!
class LogInterceptor implements Interceptor {
private String TAG = "okhttp";
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Log.v(TAG, "request:" + request.toString());
long t1 = System.nanoTime();
okhttp3.Response response = chain.proceed(chain.request());
long t2 = System.nanoTime();
Log.v(TAG, String.format(Locale.getDefault(), "Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
okhttp3.MediaType mediaType = response.body().contentType();
String content = response.body().string();
Log.i(TAG, "response body:" + content);
return response.newBuilder()
.body(okhttp3.ResponseBody.create(mediaType, content))
.build();
}
}
在Retrofit中添加即可:
mHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)//设置超时时间
.readTimeout(10, TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(10, TimeUnit.SECONDS)//设置写入超时时间
.addInterceptor(new LogInterceptor())//拦截器
.build();
retrofitAPI = new Retrofit.Builder()
.baseUrl("http://114.80.80.60:8080//interface//app/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(mHttpClient)
.build()
.create(RetrofitAPI.class);
打印效果如下: