搜索
您的当前位置:首页正文

2步完成多文件上传,简化封装Retrofit

来源:二三娱乐

思路:Retrofit 2.0 将请求方法抽象,并且采用注解参数和请求体配置,其实原本它所做的封装已经非常简单了,但是对于实际项目开发中还是需写大量的请求接口和注解的代码,我们在这里除了对Client的初始化封装,还需要对接口定义进行定制,确切到请求类型上。

  • 外部使用Retrofit执行一个请求的流程
    1.OkHttpClient作为参数设置到Retrofit上
    2.Retrofit创建APIService
    3.传入参数使用APIService发送请求得到回掉
    我们想要定制接口,别且将参数构建简化,所以重点工作是拆分第2步
    下面我们一步一步实现封装

------------------------------分.割.线----------------------------------------------
</br>


一、构建OkHttpClient

创建一个用来初始化和配置OkHttpClient的类TBOkHttpClientFactory
使用内部类创建得方式来构建单例和初始化

  public  void build() {   
         if (null == okHttpClient) {   
             synchronized (TBOkHttpClientFactory.class) {            
        if (null == okHttpClient) {               
         OkHttpClient.Builder builder = new OkHttpClient.Builder();               
         builder.connectTimeout(this.TIMEOUT_CONNECTION, TimeUnit.SECONDS);  
                      builder.readTimeout(this.TIMEOUT_READ, TimeUnit.SECONDS);         
               builder.writeTimeout(this.TIMEOUT_WRITE, TimeUnit.SECONDS);                      
  if (null != context) {         
                   ClearableCookieJar cookieJar =      
                              new PersistentCookieJar(new SetCookieCache(),     
                                 new SharedPrefsCookiePersistor(context));              
              builder.cookieJar(cookieJar);                
        }                       
    LogInterceptor logInterceptor = new LogInterceptor();              
              logInterceptor.setLevel(LogInterceptor.Level.BODY);       
                     builder.addInterceptor(logInterceptor);    
                        TbLog.setDeBug(isDebug);              
          okHttpClient = builder.build();               

                     }        
              }      
          }    
        }
    }

可以看到上面我们得日志拦截器使用的不是Retrofit为我们提供的拦截器而是LogInteceptor,这里为了控制日志开关我们直接拷贝原来得日志拦截器代码,对里面的日志打印方法进行替换。

/**
*这里得log方法原来使用得是System.out.print();我将它替换为我们自己的TbLog类来打印
*/
DEFAULT = new Logger() { 
     @Override public void log(String message) {
                TbLog.printD(TAG,message);  
              }
};

提供一个外部静态获取Client的方法

public static final OkHttpClient getOkHttpClient() {
        if (null == okHttpClient) {  
          throw new NullPointerException("uh~. When you initializing  TBOkHttpClientFactory,you didn't build okHttpClient");   
     }else{  
          return okHttpClient;  
      }    
}}

上面对OKHttpClient的构建就基本结束了
</br>


二、对Retrofit的封装,构建TBRetrofitFactory

私有构造,提供单例保证所有请求的一致性

private TBRetrofitFactory(String baseUrl){  
  Retrofit.Builder builder = new Retrofit.Builder();    
 builder.client(TBOkHttpClientFactory.getOkHttpClient());  
  builder.baseUrl(baseUrl);    
 builder.addConverterFactory(StringConverterFactory.create());    
 builder.addCallAdapterFactory(RxJavaCallAdapterFactory.create());  
  retrofit = builder.build();  
  Log.d("TBRetrofitFactory","Have created TBRetrofitFactory");
}

上面我们看到,在AddConverterFactory的时候我们添加的StringConverterFactory。为什么不用官方给的GsonConverterFactory呢,你想不是每个后台他都在每次请求返回时刚好给你一个Bean,但是除开获取流以外,会返回String是必然的,如果一定要去做转为Bean的功能,我们可以放到CallBack回调处去做,这样适应性更好(提示:在使用GsonConverterFactory时,如果是Bean的转换错误,也不会进入onResponse,会直接进入onFailure方法,这样会混淆网络错误和转换错误,这也是非常蛋疼的)。

将retrofit包这么严实,当然需要将他的本职工作体现出来了,- - !提供一个方法共外部创建APIService。

public  <T> T createService(Class<T> clz){   
             return retrofit.create(clz);
}

对于Client的封装到此就OK了,下面才是真正的重(gan)点(liang)


三、创建TBRetrofitService

先来定义好基础的APIService 其他的Factory都是围绕这些接口来实现的
就叫TBRetrofitService

/**  
 RESTFUL 模式
 @RequestMapping(/users/{name}/{id})
 @param url * 
 @return
 */
 @GET
Call<String> get(@Url String url);
···
/** 
普通模式
 @RequestMapping(/users/?name=xx&id=xx)
 @param url 
@param map 
@return 
*/
@GET
Call<String> get(@Url String url, @QueryMap Map<String, Object> map);
···
@POST@FormUrlEncoded//单纯表单 
Call<String> postForm(@Url String url, @FieldMap Map<String, Object> map);
···
@POST
@Headers("Content-Type:application/json")
Call<String> postJson(@Url String url, @Body String body);
···
//RequestBody 可以单独设置 content-type
@POST
Call<String> post(@Url String url, @Body RequestBody body);
···
//携带文件和表单
@POSTCall<String> postFormDataFiles(@Url String url, @Body MultipartBody body);

上面的接口定义,几乎满足所有的常用请求
后面需要对请求参数进行封装,并不会直接调用上面的接口,所以我们还需要定义一个外部接口与上面的方法进行对应。
既然是外部直接调用的接口那就叫TBRequestService

  void get(String url, Callback<String> callBack);
void get(String url, String[] values, Callback<String> callBack);
void get(String url, Map<String, Object> map, Callback<String> callBack);
void postJson(String url, JSONObject json, Callback<String> callBack);
void postRequestBody(String url, RequestBody body, Callback<String> callBack);
void postFormData(String url, Map<String, Object> map, Callback<String> callBack);
void postFormDataFiles(String url, Map<String, Object> map, List<File> files, MediaType contentType, Callback<String> callBack);

上面的接口中明确指定了参数类型和CallBack<String>返回,下面我们需要一个Factory来具体实现这些方法,并且作为中转最终提交给TBRetrofitService
</br>


四、定义一个请求中转工厂 TBRequestFactory

这里TBRequestFactory 作为参数的中转站,除了实现外部接口方法,还需要将参数封装好递交给TBRetrofitService,需要在提供他本身实例的同时来初始化TBRetrofitService

private TBTBRequestFactory(TBRetrofitFactory rm) { 
   tBRetrofitService = rm.createService(TBRetrofitService.class);
}

下面是对各个类型接口的参数封装和提交

//RESTFULE 参数拼接
 @Override
public void get(String url, String[] values, Callback<String> callBack) { 
   if(values == null || values.length== 0){
        get(url,callBack);
    }else{
        String va = ""; 
           for (String value : values) {
                va += "/" + value; 
           }   
         url = url +va; 
         get(url,callBack);
    }
}
···
@Override
public void get(String url, Map<String, Object> map, Callback<String> callBack) {    
tBRetrofitService.get(url, map).enqueue(callBack);
}
···
 @Override
public void postJson(String url, JSONObject json, Callback<String> callBack){   
  tBRetrofitService.postJson(url,json.toString()).enqueue(callBack);
 }
···
@Override
public void postRequestBody(String url, RequestBody body, Callback<String> callBack){ 
   tBRetrofitService.post(url,body).enqueue(callBack);
}
···
@Override
public void postFormData(String url, Map<String, Object> map, Callback<String> callBack) { 
   tBRetrofitService.postForm(url, map).enqueue(callBack);
}
···
@Override
public void postFormDataFiles(String url, @Nullable Map<String,Object> map, @NonNull  List<File> files, MediaType contentType, Callback<String> callBack) {    
if(null == files ){
      throw  new NullPointerException("files is null!"); 
   } 
   if(files.size() == 0){ 
      throw new IllegalArgumentException("files size  equal 0,You must  include at least one file!"); 
   }
    MultipartBody.Builder builder = new MultipartBody.Builder(); 
   for(File file : files){
        RequestBody body = RequestBody.create(contentType,file);     
   builder.addFormDataPart("files",file.getName(),body);  
  }  
  if(null != map && !map.isEmpty()){ 
       Iterator<Map.Entry<String,Object>> keys = map.entrySet().iterator();
        while (keys.hasNext()){ 
               Map.Entry<String,Object> entry = keys.next();    
            builder.addFormDataPart(entry.getKey(),String.valueOf(entry.getValue()));
        } 
   }  
  MultipartBody multipartBody =  builder.build();    
tBRetrofitService.postFormDataFiles(url,multipartBody).enqueue(callBack);
}

这样已经可以使用了,但是...想让它更优(tou)雅(lan)一点
于是就有下面这个类TBRequest,这里就不解释了只是再一次包装,只为了在传参的时候更顺手而已。

  • 部分代码
    private TBRequest(){ 
   params = new HashMap<>(); 
   request = TBTBRequestFactory.getInstance();
}
···
public TBRequest put(String key, Object value){ 
   params.put(key,value); 
   return  this;
}
public TBRequest setParams(Map<String,Object> params){
    this.params = params;
    return  this;
}
···
private JSONObject map2JSONObject(){    return new JSONObject(params);}

五、测试

好了下面我们来看一下具体使用是不是满足我们的初衷

void GETByRestful() { 
   TBRequest.create()
            .get(API.GITHUB_RESTFUL, new String[]{"Harkben"}, new TBCallBack() {   
@Override               
 public void onSuccess(int code, String body) {
                    showResult(code + "--" + body);               
 }
                @Override                
public void onFailed(String errorMsg) {   
                 showResult(errorMsg);   
             }       
     });
}
···
void GETByNormal() { 
   TBRequest.create() 
           .put("user", "HarkBen") 
           .get(API.GITHUB_NORMAL, new TBCallBack() { 
               @Override
                public void onSuccess(int code, String body) {
                    showResult(code + "--" + body);   
             }                @Override
                public void onFailed(String errorMsg) { 
                   showResult(errorMsg);           
     }       
     });
}

创建请求,传入Url 参数,回掉,还是很清晰简单的。
有人会问了,说好的2行代码完成文件上传呢
下面我们来看看实际代码

  void uploadFile(){ 
   File file = new File(parentPath+"291733413425432.png");  
  TBRequest.create()  
        .postFormDataFile(API.UPLOADFILE, file, new TBCallBack() {            
    @Override
                public void onSuccess(int code, String body) { 
                   showResult(code+"--"+body); 
               } 
               @Override 
               public void onFailed(String errorMsg) {
                    showResult("onFailed--"+errorMsg); 
               }
            });
}
  • 1.将文件作为参数提交
  • 2.得到请求结果
    搞定!
    你要跟我死磕,说创建文件也算一步,那么我只能.......拉你出去 Biu!Biu!Biu!

  • PS:这里我们设置参数时都是用Map作为参数封装的(GET-RESTFULE例外),GET、POST { JSON、 纯文本表单 、表单-文件+参数 }
    除了调用put(key,value)方法以外还可以使用
   TBRequest.create() .setParams(map);

我之前有遇到过这样得问题,前端传文件总是传不上去,后台接受请求后总是转型失败,所以这次专门看了很多后台开发的代码,- -!比较混乱,他们也解释不清楚需要在请求时具体携带怎么样的头部信息。所以这自个儿按最基础的写法来写接口,都测一测。这里要谢谢我的后台小明(真名哦)同学教我搭了一个SpringBoot微框架,非常简单好用。

下面是接收文件的后端接口代码,没有使用注解转换,这样方便测试

  @RequestMapping("/upload")
  @ResponseBody
  public String handleFileUpload(MultipartHttpServletRequest request){
        printLn(request.getParameter("name"));
        printLn(request.getParameter("arg"));
        printLn(request.getParameter("gender"));
        List<MultipartFile> files = request.getFiles("files");
        System.out.println("files.seize()=="+files.size());
        for(int i=0;i<files.size();i++){
            MultipartFile multipartFile =files.get(i);
                 try {
                     File file = new File("E:/image/" +multipartFile.getOriginalFilename());
                     BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
                     out.write(multipartFile.getBytes());
                     out.flush();
                     out.close();
              }catch(FileNotFoundException e) {
                     e.printStackTrace();
                     return "上传失败,"+e.getMessage();
              }catch (IOException e) {
                     e.printStackTrace();
                     return "上传失败,"+e.getMessage();
              }
            }
        return files.size()+"个文件上传成功";
    }
}

FINISH



一个Android开发的小司机

Top