【Android实战】----基于Retrofit实现多图片/文件、图文上传

本文代码详见:https://github.com/honghailiang/RetrofitUpLoadImage

原文链接:http://blog.csdn.net/honghailiang888/article/details/62884231


一、再次膜拜下Retrofit

Retrofit无论从性能还是使用方便性上都很屌!!!,本文不去介绍其运作原理(虽然很想搞明白),后面会出专题文章解析Retrofit的内部原理;本文只是从使用上解析Retrofit实现多图片/文件、图文上传的功能。文件上传相关可参考Multipart/form-data文件上传简介 Apache FileUpload文件上传功能 


二、概念介绍

1)注解@Multipart

从字面上理解就是与多媒体文件相关的,没错,图片、文件等的上传都要用到该注解,其中每个部分需要使用@Part来注解。。看其注释

[html]  view plain  copy
  1. /**  
  2.  * Denotes that the request body is multi-part. Parts should be declared as parameters and  
  3.  * annotated with {@link Part @Part}.  
  4.  */  

2)注解@PartMap

当然可以理解为使用@PartMap注释,传递多个Part,以实现多文件上传。注释

[html]  view plain  copy
  1. /**  
  2.  * Denotes name and value parts of a multi-part request.  
  3.  * <p>  
  4.  * Values of the map on which this annotation exists will be processed in one of two ways:  
  5.  * <ul>  
  6.  * <li>If the type is {@link okhttp3.RequestBody RequestBody} the value will be used  
  7.  * directly with its content type.</li>  
  8.  * <li>Other object types will be converted to an appropriate representation by using  
  9.  * {@linkplain Converter a converter}.</li>  
  10.  * </ul>  
  11.  * <p>  
  12.  * <pre><code>  
  13.  * @Multipart  
  14.  * @POST("/upload")  
  15.  * Call<ResponseBody> upload(  
  16.  *     @Part("file") RequestBody file,  
  17.  *     @PartMap Map<String, RequestBody> params);  
  18.  * </code></pre>  
  19.  * <p>  
  20.  * A {@code null} value for the map, as a key, or as a value is not allowed.  
  21.  *  
  22.  * @see Multipart  
  23.  * @see Part  
  24.  */  

3)RequestBody

从上面注释中就可以看到参数类型是RequestBody,其就是请求体。文件上传就需要参数为RequestBody。官方使用说明如下http://square.github.io/retrofit/

[html]  view plain  copy
  1. Multipart parts use one of Retrofit's converters or they can implement RequestBody to handle their own serialization.  


四、基本实现

了解了以上概念,下面就一一实现

1)接口定义

[html]  view plain  copy
  1. public interface IHttpService {  
  2. @Multipart  
  3.     @POST("file/upLoad.do")  
  4.     Call<BaseBean> upLoadAgree(@PartMap Map<String, RequestBody>params);  
  5. }  

BaseBean是根据服务端返回数据进行定义的,这个使用时可以根据自有Server定义。

2)Retrofit实现

[java]  view plain  copy
  1. /** 
  2.  * Created by DELL on 2017/3/16. 
  3.  * 上传文件用(包含图片) 
  4.  */  
  5.   
  6. public class RetrofitHttpUpLoad {  
  7.     /** 
  8.      * 超时时间60s 
  9.      */  
  10.     private static final long DEFAULT_TIMEOUT = 60;  
  11.     private volatile static RetrofitHttpUpLoad mInstance;  
  12.     public Retrofit mRetrofit;  
  13.     public IHttpService mHttpService;  
  14.     private static Map<String, RequestBody> params;  
  15.   
  16.     private RetrofitHttpUpLoad() {  
  17.         mRetrofit = new Retrofit.Builder()  
  18.                 .baseUrl(UrlConfig.ROOT_URL)  
  19.                 .client(genericClient())  
  20.                 .addConverterFactory(GsonConverterFactory.create())  
  21.                 .build();  
  22.         mHttpService = mRetrofit.create(IHttpService.class);  
  23.     }  
  24.   
  25.     public static RetrofitHttpUpLoad getInstance() {  
  26.         if (mInstance == null) {  
  27.             synchronized (RetrofitHttpUpLoad.class) {  
  28.                 if (mInstance == null)  
  29.                     mInstance = new RetrofitHttpUpLoad();  
  30.                 params = new HashMap<String, RequestBody>();  
  31.             }  
  32.         }  
  33.         return mInstance;  
  34.     }  
  35.   
  36.     /** 
  37.      * 添加统一超时时间,http日志打印 
  38.      * 
  39.      * @return 
  40.      */  
  41.     private OkHttpClient genericClient() {  
  42.         HttpLoggingInterceptor logging = new HttpLoggingInterceptor();  
  43.         logging.setLevel(HttpLoggingInterceptor.Level.BODY);  
  44.         OkHttpClient httpClient = new OkHttpClient.Builder()  
  45.                 .addInterceptor(logging)  
  46.                 .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)  
  47.                 .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)  
  48.                 .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)  
  49.                 .build();  
  50.         return httpClient;  
  51.     }  
  52.   
  53.   
  54.     /** 
  55.      * 将call加入队列并实现回调 
  56.      * 
  57.      * @param call             调入的call 
  58.      * @param retrofitCallBack 回调 
  59.      * @param method           调用方法标志,回调用 
  60.      * @param <T>              泛型参数 
  61.      */  
  62.     public <T> void addToEnqueue(Call<T> call, final RetrofitCallBack retrofitCallBack, final int method) {  
  63.         final Context context = MyApplication.getContext();  
  64.         call.enqueue(new Callback<T>() {  
  65.             @Override  
  66.             public void onResponse(Call<T> call, Response<T> response) {  
  67.                 LogUtil.d("retrofit back code ====" + response.code());  
  68.                 if (null != response.body()) {  
  69.                     if (response.code() == 200) {  
  70.                         LogUtil.d("retrofit back body ====" + new Gson().toJson(response.body()));  
  71.                         retrofitCallBack.onResponse(response, method);  
  72.                     } else {  
  73.                         LogUtil.d("toEnqueue, onResponse Fail:" + response.code());  
  74.                         ToastUtil.makeShortText(context, "网络连接错误" + response.code());  
  75.                         retrofitCallBack.onFailure(response, method);  
  76.                     }  
  77.                 } else {  
  78.                     LogUtil.d("toEnqueue, onResponse Fail m:" + response.message());  
  79.                     ToastUtil.makeShortText(context, "网络连接错误" + response.message());  
  80.                     retrofitCallBack.onFailure(response, method);  
  81.                 }  
  82.             }  
  83.   
  84.             @Override  
  85.             public void onFailure(Call<T> call, Throwable t) {  
  86.                 LogUtil.d("toEnqueue, onResponse Fail unKnown:" + t.getMessage());  
  87.                 t.printStackTrace();  
  88.                 ToastUtil.makeShortText(context, "网络连接错误" + t.getMessage());  
  89.                 retrofitCallBack.onFailure(null, method);  
  90.             }  
  91.         });  
  92.     }  
  93.   
  94.     /** 
  95.      * 添加参数 
  96.      * 根据传进来的Object对象来判断是String还是File类型的参数 
  97.      */  
  98.     public RetrofitHttpUpLoad addParameter(String key, Object o) {  
  99.   
  100.         if (o instanceof String) {  
  101.             RequestBody body = RequestBody.create(MediaType.parse("text/plain;charset=UTF-8"), (String) o);  
  102.             params.put(key, body);  
  103.         } else if (o instanceof File) {  
  104.             RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);  
  105.             params.put(key + "\"; filename=\"" + ((File) o).getName() + "", body);  
  106.         }  
  107.         return this;  
  108.     }  
  109.   
  110.     /** 
  111.      * 构建RequestBody 
  112.      */  
  113.     public Map<String, RequestBody> bulider() {  
  114.   
  115.         return params;  
  116.     }  
  117.   
  118.     public void clear(){  
  119.         params.clear();  
  120.     }  
  121. }  


其中定义了Retrofit实例、还用拦截器定义了统一的超时时间和日志打印;将call加入队列并实现回调。最重要的就是添加参数:

[java]  view plain  copy
  1. /** 
  2.     * 添加参数 
  3.     * 根据传进来的Object对象来判断是String还是File类型的参数 
  4.     */  
  5.    public RetrofitHttpUpLoad addParameter(String key, Object o) {  
  6.   
  7.        if (o instanceof String) {  
  8.            RequestBody body = RequestBody.create(MediaType.parse("text/plain;charset=UTF-8"), (String) o);  
  9.            params.put(key, body);  
  10.        } else if (o instanceof File) {  
  11.            RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);  
  12.            params.put(key + "\"; filename=\"" + ((File) o).getName() + "", body);  
  13.        }  
  14.        return this;  
  15.    }  


这里就是根据传入的参数,返回不同的RequestBody, 注意文件的key值。


3)使用

[java]  view plain  copy
  1. private void upLoadAgree() {  
  2.         showWaitDialog();  
  3.         RetrofitHttpUpLoad retrofitHttpUpLoad = RetrofitHttpUpLoad.getInstance();  
  4.         retrofitHttpUpLoad.clear();  
  5.         if (!StringUtil.isEmpty(pathImage[0])){  
  6.             retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("pic1",new File(pathImage[0]));  
  7.         }  
  8.         if (!StringUtil.isEmpty(pathImage[1])){  
  9.             retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("pic2"new File(pathImage[1]));  
  10.         }  
  11.         if (!StringUtil.isEmpty(pathImage[2])){  
  12.             retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("zip"new File(pathImage[2]));  
  13.         }  
  14.   
  15.         Map<String, RequestBody> params = retrofitHttpUpLoad  
  16.                 .addParameter("status""4")  
  17.                 .addParameter("pickupId", tv_orderquality_pid.getText().toString())  
  18.                 .addParameter("cause", reason)  
  19.                 .addParameter("connectname", et_orderquality_lxrname.getText().toString())  
  20.                 .addParameter("connectphone", et_orderquality_lxrphone.getText().toString())  
  21.                 .addParameter("details", et_orderquality_xqms.getText().toString())  
  22.                 .bulider();  
  23.         retrofitHttpUpLoad.addToEnqueue(retrofitHttpUpLoad.mHttpService.upLoadAgree(params),  
  24.                 this, HttpStaticApi.HTTP_UPLOADAGREE);  
  25.     }  



需要注意的是要对图片及文件路径进行判空操作,否则会报异常W/System.err: Java.io.FileNotFoundException: /: open failed: EISDIR (Is a directory)


五、报文日志

其中图片报文有省略

[html]  view plain  copy
  1. D/OkHttp: --> POST http://192.168.xxx.xxx:8880/xxx/nocheck/file/agree.do http/1.1  
  2. D/OkHttp: Content-Type: multipart/form-data; boundary=f3e7369a-ead9-46e2-9ddd-448442fd5108  
  3. D/OkHttp: Content-Length: 300580  
  4. D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108  
  5. D/OkHttp: Content-Disposition: form-data; name="pic2"filename="90079.jpg"  
  6. D/OkHttp: Content-Transfer-Encoding: binary  
  7. D/OkHttp: Content-Type: multipart/form-data;charset=UTF-8  
  8. D/OkHttp: Content-Length: 149456  
  9. D/OkHttp: ??????JFIF????H??H????????C??      
  10. D/OkHttp:    "##!  %*5-%'2(  .?/279<<<$-BFA:F5;<9????C  
  11. D/OkHttp:  9& &99999999999999999999999999999999999999999999999999?????8"?????????????????????????????????????????????????????????? ??????????|???/n?s??y?]8ug?7?R??????h?tΘ a?T?T<?U???z???+3C?w??tdf??=<??????fN??s??x??hhzd??X~?X??i?{~/?<^??~=zX??\??4?U?ɡ)I???????????????????$??@??? iM?"J?R   2?f?M K5x#w?????r?I?3?Y??l???V?Bj? ?>{??t?u????]????g>?o??o???dM?U??????J?R??<?+?;??????????OG>????=?5?L?9?&???_/\?yu??~|*,???My?r????????='?d?=?t*??*?Y??(????????????????   YB i?Jlv??d?"l????Y??4??X?7???;??sY?\κ+?N??;?L??&? (?MJ???@w~~???a?qs??m7??y???Ns?\?C?g??>???N%??N??gs?Q??c????Z?t???x??{??^_}s?s??gO?????N?|}?;??y?y?ǎ|?v??N?l???????????*k5?(?????????1$?B??j?+,?l???hN??U?<sgb?g?x?S??;;c?,??7?0Z?J?I?r??X?9?t?'\?1W+  
  12. D/OkHttp: [?? ????=/X???n??T*4?u??<?????s?q??????c???\?6?YV?p????oB%??|?s??????????{??g??k?}?t??d{]^W???v?WB?x???|z9?>V?{ǒ>?o??Y????xk?k7???{w????b?!JQjRQ%RH?%8?H??Q??Ys?{???u??(?`?b\??k?cC:u#???d?C??&??W.CXd?e??N??n????.?%v?,.zW?>??&??+??r??S'?.n?[    V?    ?q??oGL?,:?S??????/?o<???,?B???;??????^[?#Lm??7.Q?6sONz??fwN?  
  13. D/OkHttp: ???,?\????  
  14. D/OkHttp: ???U<???1?Z???=??pn?~q??[-?P??=?j?va?R?   ??4X*???nv?H??j?j?p??`h#-???qiA?U?????x?&v? b?R??o?.??H[M5??Y??5?>%Y?j????x?2.m??=??GG???  
  15. D/OkHttp: \?D??(?JK9<J???JE?jl??pW  
  16. D/OkHttp: ??}??i?6??R??:!J??FT?!e???  
  17. D/OkHttp: ????:??5????%??`? |???;z?G??[?P ???N=???T??X?-?okNe?? ?Y??f8?`:?P???x?1?I?g    ?0?)?fe*P?qU?~?jSY%??gɡR?(?$A?|y?}??s?2?<?/??4?s??@-?,??AZ?az?,??bl?.??WD??????q?X?u}?+G?z?h8=?w[`?j??g&q?c? ???????<|??|? 1????q^???  
  18. D/OkHttp: 5??)x???:*dK??|?KPz5v?4?+?>eO?4??i?P2F?&\9???? -V?esf~&F?Q??S?\???8{??*B?1qVO????-S??!?????????*6??   
  19. D/OkHttp: 3?5W?r?x??+?\?r???6??C ??Ms,H?AE??=q??????(??f?=,????Z??+????L??<??v_i-?m|????6??L???=?4?Y?{?W??9=??\AW???~??{@!?^ Z6??,?k>|??C  
  20. D/OkHttp: aZ??-?ы??R?K???1?6`'??F%/4wZ?Cn????[?]?el??U&[???1db-4????????~er!?4??>ji?]??AV?[v??e??`θo???帏!(??Pk?XCI??Glk-p??p ?B?b???ae???d?]-|"??*??`??l??Tmn`???  
  21. D/OkHttp: R?G? ?h?DETp???i???^????u?E??1?wW%?<????????3e??V????   **m??9V??O?R??f?b?  
  22. D/OkHttp: ??j%)^?$??g?\?Qm^`??  
  23. D/OkHttp: ? ?[*?\?@/T@,?|@\$F?????v_??uA???:?9>%B????1 =?D]{?"??*^??q1??i??B?bs?L_???? e??W?2??pDR?Q??M?? ?{????7 S?????Dzcb\??????0?????u? h???e??7X??)?s{??DIqf???QdM??V?????r??MTk?=??+?>b0?b???i?\?lI??H ?P??,? Pn[]??.?`.X?=A?I?P?@?<~??Px??.???9?(??:=??5E?n?!l??? 5???ee_??'[ ????p?d??1?)g?s<????kop?вd?19m?ft??ab????????j?5/pT?M?xBb??8???z??????wX??V??|~x????????c?Rsx???D???ixH??ud?50??MΘ7???<?^I???i?`?????f?A?????????;?U?H????a~?W臸?@O?' u\-???????CN,????-???@?+"n?:y???G   |S??C?F5?????Ix????? )?????b 22???jRj????j?,K?K"¥</?G?w/  *?W????s n??L????n?n????? k????"?*??~?9??<4?,c? d>?EG??iB??0+? ?i?Y??D?? ?p???????S|.?2???# &??"v?Y??P? ?O?#EK???,J?6U?>a???;?-rM??@???^b??@??K?????PI??4?qM|??V??h[Ld??R????or?U?M??)_?J?^S?41n }?@n|??  
  24. D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108  
  25. D/OkHttp: Content-Disposition: form-data; name="cause"  
  26. D/OkHttp: Content-Transfer-Encoding: binary  
  27. D/OkHttp: Content-Type: text/plain;charset=UTF-8  
  28. D/OkHttp: Content-Length: 33  
  29. D/OkHttp: 对货物数量、质量有异议  
  30. D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108  
  31. D/OkHttp: Content-Disposition: form-data; name="details"  
  32. D/OkHttp: Content-Transfer-Encoding: binary  
  33. D/OkHttp: Content-Type: text/plain;charset=UTF-8  
  34. D/OkHttp: Content-Length: 9  
  35. D/OkHttp: 哈哈哈  
  36. D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108  
  37. D/OkHttp: Content-Disposition: form-data; name="status"  
  38. D/OkHttp: Content-Transfer-Encoding: binary  
  39. D/OkHttp: Content-Type: text/plain;charset=UTF-8  
  40. D/OkHttp: Content-Length: 1  
  41. D/OkHttp: 4  
  42. D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108  
  43. D/OkHttp: Content-Disposition: form-data; name="pickupId"  
  44. D/OkHttp: Content-Transfer-Encoding: binary  
  45. D/OkHttp: Content-Type: text/plain;charset=UTF-8  
  46. D/OkHttp: Content-Length: 6  
  47. D/OkHttp: 105329  
  48. D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108  
  49. D/OkHttp: Content-Disposition: form-data; name="connectphone"  
  50. D/OkHttp: Content-Transfer-Encoding: binary  
  51. D/OkHttp: Content-Type: text/plain;charset=UTF-8  
  52. D/OkHttp: Content-Length: 11  
  53. D/OkHttp: 13xxxxxxxxx  
  54. D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108  
  55. D/OkHttp: Content-Disposition: form-data; name="connectname"  
  56. D/OkHttp: Content-Transfer-Encoding: binary  
  57. D/OkHttp: Content-Type: text/plain;charset=UTF-8  
  58. D/OkHttp: Content-Length: 3  
  59. D/OkHttp: 111  
  60. D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108--  
  61. D/OkHttp: --> END POST (300580-byte body)  


六、代码托管

https://github.com/honghailiang/RetrofitUpLoadImage


七、效果图:


八、服务端代码

[java]  view plain  copy
  1. FileItemFactory factory = new DiskFileItemFactory();  
  2.         ServletFileUpload upload = new ServletFileUpload(factory);  
  3.         File directory = null;  
  4.         List<FileItem> items = new ArrayList<FileItem>();  
  5.   
  6.         InputStream is = null;  
  7.   
  8.         FileOutputStream fos = null;  
  9.           
  10.   
  11.         try {  
  12.             items = upload.parseRequest(request);  
  13.             // 得到所有的文件  
  14.             Iterator<FileItem> it = items.iterator();  
  15.             while (it.hasNext()) {  
  16.                 FileItem fItem = (FileItem) it.next();  
  17.                 String fName = "";  
  18.                 Object fValue = null;  
  19.                 if (fItem.isFormField()) { // 普通文本框的值  
  20.                     fName = fItem.getFieldName();  
  21.                     fValue = fItem.getString("UTF-8");  
  22.                       
  23.                     if("pickupId".equals(fName)){  
  24.                         pickupAppeal.setPickupId(fValue.toString());  
  25.                     }else if("cause".equals(fName)){  
  26.                         pickupAppeal.setCause(fValue.toString());  
  27.                     }else if("connectname".equals(fName)){  
  28.                         pickupAppeal.setConnectname(fValue.toString());  
  29.                     }else if("connectphone".equals(fName)){  
  30.                         pickupAppeal.setConnectphone(fValue.toString());  
  31.                     }else if("details".equals(fName)){  
  32.                         pickupAppeal.setDetails(fValue.toString());  
  33.                     }else if("status".equals(fName)){  
  34.                         String status = fValue.toString();  
  35.                         BigDecimal big = new BigDecimal(status);  
  36.                         pickupAppeal.setStatus(big);  
  37.                     }  
  38.                       
  39.                     //map.put(fName, fValue);  
  40.                 } else { // 获取上传文件的值  
  41.                     fName = fItem.getFieldName();  
  42.                     fValue = fItem.getInputStream();  
  43.                     String name = fItem.getName();  
  44.                     if (name != null && !("".equals(name))) {  
  45.                         name = name.substring(name.lastIndexOf(File.separator) + 1);  
  46.                         int lenN =name.indexOf(".");  
  47.                         String suf = name.substring(lenN, name.length());  
  48.                           
  49.                         String day = DateUtil.format(Calendar.getInstance().getTime(),   
  50.                                 "yyyy-MM-dd");  
  51.                         String path = PATH+File.separator+day;  
  52.                         directory = new File(path);  
  53.                         if (!directory.exists()) {  
  54.                             directory.mkdirs();  
  55.                         }  
  56.   
  57.                         String preFile =UUID.randomUUID().toString().replace("-""");  
  58.                           
  59.                         String filePath = path + File.separator+preFile+suf;  
  60.                         String serverPath = day+ File.separator+preFile+suf;  
  61.                         map.put(fName,  serverPath);  
  62.                         map.put(fName+"m", name);  
  63.                         is = fItem.getInputStream();  
  64.                         fos = new FileOutputStream(filePath);  
  65.                         byte[] buffer = new byte[1024];  
  66.                         int len = 0;  
  67.                         while ((len = is.read(buffer, 01024)) != -1) {  
  68.                             fos.write(buffer, 0, len);  
  69.                         }  
  70.                         fos.flush();  
  71.   
  72.                     }  
  73.                 }  
  74.             }  
  75.         } catch (Exception e) {  
  76.             pLog.error("接收参数错误:" + e.getMessage());  
  77.             resultInfo.setStatus(ComStatus.RESULTINFO_STATUS_FAILE);  
  78.             resultInfo.setMessage(ComStatus.RESULTINFO_MESSAGE_FAILE);  
  79.               
  80.             resultInfo.setData(map);  
  81.             resReslt.setResData(resultInfo);  
  82.         } finally {  
  83.             try {  
  84.                 if (null != fos) {  
  85.                     fos.close();  
  86.                 }  
  87.   
  88.                 if (null != is) {  
  89.                     is.close();  
  90.                 }  
  91.             } catch (IOException e) {  
  92.                 e.printStackTrace();  
  93.             }  
  94.         }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值