从来好事天生俭,自古瓜儿苦后甜。这篇文章主要讲述Android实战----基于Retrofit实现多图片/文件图文上传相关的知识,希望能为你提供帮助。
一、再次膜拜下Retrofit
Retrofit无论从性能还是使用方便性上都很屌!!!,本文不去介绍其运作原理(虽然很想搞明白),后面会出专题文章解析Retrofit的内部原理;本文只是从使用上解析Retrofit实现多图片/文件、图文上传的功能。
二、概念介绍
1)注解@Multipart
从字面上理解就是与多媒体文件相关的,没错,图片、文件等的上传都要用到该注解,其中每个部分需要使用@Part来注解。。看其注释
/** * Denotes that the request body is multi-part. Parts should be declared as parameters and * annotated with {@link Part @Part}. */
2)注解@PartMap
当然可以理解为使用@PartMap注释,传递多个Part,以实现多文件上传。注释
/** * Denotes name and value parts of a multi-part request. * < p> * Values of the map on which this annotation exists will be processed in one of two ways: * < ul> * < li> If the type is {@link okhttp3.RequestBody RequestBody} the value will be used * directly with its content type.< /li> * < li> Other object types will be converted to an appropriate representation by using * {@linkplain Converter a converter}.< /li> * < /ul> * < p> * < pre> < code> * @Multipart * @POST(" /upload" ) * Call< ResponseBody> upload( *@Part(" file" ) RequestBody file, *@PartMap Map< String, RequestBody> params); * < /code> < /pre> * < p> * A {@code null} value for the map, as a key, or as a value is not allowed. * * @see Multipart * @see Part */
3)RequestBody
从上面注释中就可以看到参数类型是RequestBody,其就是请求体。文件上传就需要参数为RequestBody。官方使用说明如下http://square.github.io/retrofit/
Multipart parts use one of Retrofit‘s converters or they can implement RequestBody to handle their own serialization.
四、基本实现
了解了以上概念,下面就一一实现
1)接口定义
public interface IHttpService { @Multipart @POST(" nocheck/file/agree.do" ) Call< BaseBean> upLoadAgree(@PartMap Map< String, RequestBody> params); }
BaseBean是根据服务端返回数据进行定义的,这个使用时可以根据自有Server定义。
【Android实战----基于Retrofit实现多图片/文件图文上传】2)Retrofit实现
/** * Created by DELL on 2017/3/16. * 上传文件用(包含图片) */public class RetrofitHttpUpLoad { /** * 超时时间60s */ private static final long DEFAULT_TIMEOUT = 60; private volatile static RetrofitHttpUpLoad mInstance; public Retrofit mRetrofit; public IHttpService mHttpService; private Map< String, RequestBody> params = new HashMap< String, RequestBody> (); private RetrofitHttpUpLoad() { mRetrofit = new Retrofit.Builder() .baseUrl(UrlConfig.ROOT_URL) .client(genericClient()) .addConverterFactory(GsonConverterFactory.create()) .build(); mHttpService = mRetrofit.create(IHttpService.class); }public static RetrofitHttpUpLoad getInstance() { if (mInstance == null) { synchronized (RetrofitHttpUpLoad.class) { if (mInstance == null) mInstance = new RetrofitHttpUpLoad(); } } return mInstance; }/** * 添加统一超时时间,http日志打印 * * @return */ public static OkHttpClient genericClient() { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient httpClient = new OkHttpClient.Builder() .addInterceptor(logging) .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .build(); return httpClient; }/** * 将call加入队列并实现回调 * * @param call调入的call * @param retrofitCallBack 回调 * @param method调用方法标志,回调用 * @param < T> 泛型参数 */ public static < T> void addToEnqueue(Call< T> call, final RetrofitCallBack retrofitCallBack, final int method) { final Context context = MyApplication.getContext(); call.enqueue(new Callback< T> () { @Override public void onResponse(Call< T> call, Response< T> response) { LogUtil.d(" retrofit back code ====" + response.code()); if (null != response.body()) { if (response.code() == 200) { LogUtil.d(" retrofit back body ====" + new Gson().toJson(response.body())); retrofitCallBack.onResponse(response, method); } else { LogUtil.d(" toEnqueue, onResponse Fail:" + response.code()); ToastUtil.makeShortText(context, " 网络连接错误" + response.code()); retrofitCallBack.onFailure(response, method); } } else { LogUtil.d(" toEnqueue, onResponse Fail m:" + response.message()); ToastUtil.makeShortText(context, " 网络连接错误" + response.message()); retrofitCallBack.onFailure(response, method); } }@Override public void onFailure(Call< T> call, Throwable t) { LogUtil.d(" toEnqueue, onResponse Fail unKnown:" + t.getMessage()); t.printStackTrace(); ToastUtil.makeShortText(context, " 网络连接错误" + t.getMessage()); retrofitCallBack.onFailure(null, method); } }); }/** * 添加参数 * 根据传进来的Object对象来判断是String还是File类型的参数 */ public RetrofitHttpUpLoad addParameter(String key, Object o) {if (o instanceof String) { RequestBody body = RequestBody.create(MediaType.parse(" text/plain; charset=UTF-8" ), (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; }/** * 构建RequestBody */ public Map< String, RequestBody> bulider() {return params; } }
其中定义了Retrofit实例、还用拦截器定义了统一的超时时间和日志打印;将call加入队列并实现回调。最重要的就是添加参数:
/** * 添加参数 * 根据传进来的Object对象来判断是String还是File类型的参数 */ public RetrofitHttpUpLoad addParameter(String key, Object o) {if (o instanceof String) { RequestBody body = RequestBody.create(MediaType.parse(" text/plain; charset=UTF-8" ), (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; }
这里就是根据传入的参数,返回不同的RequestBody。
3)使用
private void upLoadAgree() { showWaitDialog(); RetrofitHttpUpLoad retrofitHttpUpLoad = RetrofitHttpUpLoad.getInstance(); if (!StringUtil.isEmpty(pathImage[0])){ retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter(" pic1" ,new File(pathImage[0])); } if (!StringUtil.isEmpty(pathImage[1])){ retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter(" pic2" , new File(pathImage[1])); } if (!StringUtil.isEmpty(pathImage[2])){ retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter(" zip" , new File(pathImage[2])); }Map< String, RequestBody> params = retrofitHttpUpLoad .addParameter(" status" , " 4" ) .addParameter(" pickupId" , tv_orderquality_pid.getText().toString()) .addParameter(" cause" , reason) .addParameter(" connectname" , et_orderquality_lxrname.getText().toString()) .addParameter(" connectphone" , et_orderquality_lxrphone.getText().toString()) .addParameter(" details" , et_orderquality_xqms.getText().toString()) .bulider(); RetrofitHttpUpLoad.addToEnqueue(RetrofitHttpUpLoad.getInstance().mHttpService.upLoadAgree(params), this, HttpStaticApi.HTTP_UPLOADAGREE); }
需要注意的是要对图片及文件路径进行判空操作,负责会报异常W/System.err: java.io.FileNotFoundException: /: open failed: EISDIR (Is a directory)
??
推荐阅读
- Android---页面跳转
- 源码分析篇 - Android绘制流程measurelayoutdraw流程
- Android自定义标签列表控件LabelsView解析
- android中Path的使用
- Android四大组件——ContentProvider
- Android学习总结 ———— Handler 的使用
- Android键盘事件
- Android测试(从零开始2——local单元测试)
- 安卓开源项目周报0315