Retrofit 实现单文件上传 包括进度回调, 得到文件md5, 和获取文件MimeType.

本文介绍如何使用Retrofit实现文件上传功能,包括配置Retrofit支持JSON、构建上传实例、定义上传接口、创建文件上传工具类及进度监听等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.gradle配置 Retrofit 很多时候时候让 Retrofit  支持 Json :

implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'


2.构建NetWork来获取Retrofit 实例(单实例模式)

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;



public class NetWork {

    private Retrofit retrofit;

    private static class NetWorkI {
        private static NetWork instance = new NetWork();
        static {
            OkHttpClient client = new OkHttpClient.Builder()
                    .retryOnConnectionFailure(true)
                    .connectTimeout(12, TimeUnit.SECONDS)
                    .build();

            instance.retrofit = new Retrofit.Builder()
                    .client(client)
                    .baseUrl(Common.UPLOAD_API_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
    }

    public static Retrofit getRetrofit() {
        return NetWorkI.instance.retrofit;
    }

}

3.构建一个简单的返回pojo的类ResponseModel<T>: 

可以不实现它,具体看你项目需求!!!!
@SuppressWarnings("WeakerAccess")
public class ResponseModel<T> {
    //成功
    public static final int SUCCEED = 1;
    //其他错误
    public static final int ERROR_OTHER = 0;
    //错误的请求
    public static final int BAD_REQUEST = 4041;
    //验证错误
    public static final int ERROR_NO_PERMISSION = 2010;
    //服务器错误
    public static final int SERVICE_ERROR = 2010;

    private int code;//状态码
    private String message;//简单的字符串信息
    private long time;//服务器返回时间
    private T result;//返回内容

    public boolean success() {
        return code == SUCCEED;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }


    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getResult() {
        return result;
    }

    public void setResult(T result) {
        this.result = result;
    }
}

4.在interface RemoteNet中注解配置uploadFile文件上传抽像方法:

public interface RemoteNet {
    @Multipart 
    @POST("/upload") //url路由
    Call<ResponseModel<String>> uploadFile(
            @Part MultipartBody.Part file);
}


5.构建文件上传工具类FileUploader 和工具方法fileUpload:

附:文件传输进度回调接口:
public interface ProgressListener {
        void progress(float progress, long length, long fileLength);
}

import android.annotation.SuppressLint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.jiuli.local_share.network.NetWork;
import com.jiuli.local_share.network.RemoteNet;
import com.jiuli.local_share.network.uploader.model.ResponseModel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okio.BufferedSink;
import retrofit2.Callback;
import retrofit2.Retrofit;

/**
 * //Created by r1907 on 2018/4/19.
 */

public class FileUploader {

    @SuppressLint("SimpleDateFormat")
    private static final SimpleDateFormat bartDateFormat = new SimpleDateFormat("yyyyMM");//时间格式化

    private static final String KEY = "你的上传秘钥";

    public static final String REPOSITORY_IMAGE = "image";//图片仓库

    public static final String REPOSITORY_FILE = "file";//文件仓库

    public static final String REPOSITORY_PORTRAIT = "portrait";//头像仓库

    private static final int CHUNK_SIZE = 1024 * 128;//字节块大小


    /**
     * 文件上传
     */
    public static final void fileUpload(@NonNull  final File file,
                                        @NonNull  final String repository,
                                        @NonNull  final Callback<ResponseModel<String>> callback,
                                        @Nullable final ProgressListener progressListener) throws Exception {

        if (!file.exists()) {
            throw new FileNotFoundException();
        }

        if (!file.canRead()) {
            throw new Exception("文件不可用!");
        }


        //获取retrofit
        Retrofit retrofit = NetWork.getRetrofit();
        RemoteNet remoteNet = retrofit.create(RemoteNet.class);

        // RequestBody requestBody = RequestBody.create(MediaType.parse(getMimeType(file.getPath())), file);

        MultipartBody.Part filePart = MultipartBody
                .Part
                .createFormData(
                        KEY,
                        getFileObjKey(file, repository),
                        createRequestBody(file, progressListener));
        remoteNet.uploadFile(filePart)
                .enqueue(callback);
    }


    /**
     * Returns a new request body that transmits the content of {@code file}.
     * 让requestBody支持块和进度
     */

    public static RequestBody createRequestBody(final File file, final ProgressListener progressListener) {
        if (file == null) throw new NullPointerException("file == null");

        return new RequestBody() {
            private long fileLen = 0;

            @Nullable
            @Override
            public MediaType contentType() {
                return MediaType.parse(getMimeType(file.getPath()));
            }

            @Override
            public long contentLength() throws IOException {
                return fileLen == 0 ? fileLen = file.length() : fileLen;
            }

            @Override
            public void writeTo(@NonNull BufferedSink sink) throws IOException {
                long contentLength = contentLength();
                long length = 0;
                try (FileInputStream fis = new FileInputStream(file)) {
                    byte[] bytes = new byte[CHUNK_SIZE];
                    int len;
                    while ((len = fis.read(bytes)) != -1) {
                        sink.write(bytes, 0, len);
                        length += len;
                        if (progressListener != null) {
                            progressListener.progress(length / (contentLength + 0f), length, contentLength);
                        }
                    }
                } finally {
                    sink.flush();
                }
            }
        };
    }

    /**
     * 将当前时间转化为字符串 格式为yyyyMM
     *
     * @return
     */
    private static String getDateString() {
        return bartDateFormat.format(new Date());
    }


    /**
     * 获取文件在服务器下的相对路径
     *
     * @param file
     * @param repository
     * @return
     */

    private static String getFileObjKey(File file, String repository) {
        String fileMd5 = getMD5String(file);
        String name = file.getName();
        return String.format("/%s/%s/%s%s",
                repository,
                getDateString(),
                fileMd5,
                name.substring(name.lastIndexOf(".")));
    }

    /**
     * 获取 MD5
     *
     * @param file
     * @return
     */
    public static String getMD5String(File file) {
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
        InputStream is = null;
        byte[] bytes = new byte[10240];
        try {
            is = new FileInputStream(file);
            for (int readLen; (readLen = is.read(bytes)) > 0; ) {
                md5.update(bytes, 0, readLen);
            }
            return convertToHexString(md5.digest());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 将字节数组转化为16进制String
     *
     * @param bytes
     * @return
     */
    private static String convertToHexString(byte[] bytes) {
        char[] HEX_DIGITS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        StringBuffer sb = new StringBuffer(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(HEX_DIGITS[(b & 0xF0) >>> 4]);
            sb.append(HEX_DIGITS[b & 0x0F]);
        }
        return sb.toString();
    }

    /**
     * 获取文件的mimeType
     *
     * @param fileUrl
     * @return
     */
    public static String getMimeType(String fileUrl) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String type = fileNameMap.getContentTypeFor(fileUrl);
        return type;
    }
}

其中:

获取文件MD5方法
   /**
     * 获取 MD5
     *
     * @param file
     * @return
     */
    public static String getMD5String(File file) {
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
        InputStream is = null;
        byte[] bytes = new byte[10240];
        try {
            is = new FileInputStream(file);
            for (int readLen; (readLen = is.read(bytes)) > 0; ) {
                md5.update(bytes, 0, readLen);
            }
            return convertToHexString(md5.digest());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * 将字节数组转化为16进制String
     *
     * @param bytes
     * @return
     */
    private static String convertToHexString(byte[] bytes) {
        char[] HEX_DIGITS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        StringBuffer sb = new StringBuffer(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(HEX_DIGITS[(b & 0xF0) >>> 4]);
            sb.append(HEX_DIGITS[b & 0x0F]);
        }
        return sb.toString();
    }
  /** Returns a new request body that transmits the content of {@code file}. */
  public static RequestBody create(final @Nullable MediaType contentType, final File file) {
    if (file == null) throw new NullPointerException("content == null");

    return new RequestBody() {
      @Override public @Nullable MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return file.length();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        Source source = null;
        try {
          source = Okio.source(file);
          sink.writeAll(source);
        } finally {
          Util.closeQuietly(source);
        }
      }
    };
  }
}


获取MimeType方法
    public static String getMimeType(String fileUrl) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String type = fileNameMap.getContentTypeFor(fileUrl);
        return type;
    }


附 abstract 类RequestBody 的create 方法:
  /** Returns a new request body that transmits the content of {@code file}. */
  public static RequestBody create(final @Nullable MediaType contentType, final File file) {
    if (file == null) throw new NullPointerException("content == null");

    return new RequestBody() {
      @Override public @Nullable MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return file.length();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        Source source = null;
        try {
          source = Okio.source(file);
          sink.writeAll(source);
        } finally {
          Util.closeQuietly(source);
        }
      }
    };
  }
}
让它支持进度在UploadFile类中改写为:
    public static RequestBody createRequestBody(final File file, final ProgressListener progressListener) {
        if (file == null) throw new NullPointerException("file == null");

        return new RequestBody() {
            private long fileLen = 0;

            @Nullable
            @Override
            public MediaType contentType() {
                return MediaType.parse(getMimeType(file.getPath()));
            }

            @Override
            public long contentLength() throws IOException {
                return fileLen == 0 ? fileLen = file.length() : fileLen;
            }

            @Override
            public void writeTo(@NonNull BufferedSink sink) throws IOException {
                long contentLength = contentLength();
                long length = 0;
                try (FileInputStream fis = new FileInputStream(file)) {
                    byte[] bytes = new byte[CHUNK_SIZE];
                    int len;
                    while ((len = fis.read(bytes)) != -1) {
                        sink.write(bytes, 0, len);
                        length += len;
                        if (progressListener != null) {
                            progressListener.progress(length / (contentLength + 0f), length, contentLength);
                        }
                    }
                } finally {
                    sink.flush();
                }
            }
        };
    }
本人技术有限 希望各位 同道指正其中的不足之处.
如果觉得有用请不吝惜的给我一个宝贵的赞!!!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值