Retrofit多文件上传

后面的文件上传会涉及到链式结构,小伙伴可以先去看一下下面的文章。

相关文章:Android链式结构封装


说到网络框架,从入门级别的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","失败");  
    }  
});  

抓包可以得到效果图:


这种方式你可以尽情的添加参数,HttpParameterBuilder自行转换类型进行包装成Map格式上传
到这里retrofit的基本用法也就完成了,总之折腾了这么久也是值得的这个成功之后,再结合RxAndroid也就轻而易举了!

四、配置拦截器

抓包是我以往的习惯,如果你感觉抓包麻烦,你可以配置一个Log日志拦截器,对于okhttp的拦截器,大家可自行了解

Log拦截器代码:
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);

打印效果如下:




注意事项:
如果你在使用的过程中出现无响应的情况,并且没有抓到任何的包,可能是以下情况导致的:
1、没有加入SD卡的读写权限
2、手机抓包代理有问题,一般的第三方会出现抓不到信息的情况是因为网络请求而框架是直连WIFI的,设置代理是没用的,你可以用你的电脑开一个WIFI热点,通过网卡进行抓取就ok的。
3、后台的接口的写法有出入


猜你喜欢

转载自blog.csdn.net/u014752325/article/details/53149973