首页 > 代码库 > 【Android实战】----基于Retrofit实现多图片/文件、图文上传

【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定义。

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实战】----基于Retrofit实现多图片/文件、图文上传