前言

 之前项目需要上传大文件的功能,上传大文件经常遇到上传一半由于网络或者其他一些原因上传失败。然后又得重新上传(很麻烦),所以就想能不能做个断点上传的功能。于是网上搜索,发现市面上很少有断点上传的案例,有找到一个案例也是采用SOCKET作为上传方式(大文件上传,不适合使用POST,GET形式)。由于大文件夹不适合http上传的方式,所以就想能不能把大文件切割成n块小文件,然后上传这些小文件,所有小文件全部上传成功后再在服务器上进行拼接。这样不就可以实现断点上传,又解决了http不适合上传大文件的难题了吗!!!

原理分析

*******Android客户端********

首先,android端调用服务器接口1,参数为filename(服务器标识判断是否上传过)

如果存在filename,说明之前上传过,则续传;如果没有,则从零开始上传。

然后,android端调用服务器接口2,传入参数name,chunck(传到第几块),chuncks(总共多少块)


android实现大文件断点上传-LMLPHPandroid实现大文件断点上传-LMLPHP

*******服务器端********

接口一:根据上传文件名称filename 判断是否之前上传过,没有则返回客户端chunck=1,有则读取记录chunck并返回

接口二:上传文件,如果上传块数chunck=chuncks,遍历所有块文件拼接成一个完整文件。


接口1





  
  1. @WebServlet(urlPatterns = { "/ckeckFileServlet" })
  2. public class CkeckFileServlet extends HttpServlet {
  3. private FileUploadStatusServiceI statusService;
  4. String repositoryPath;
  5. String uploadPath;
  6. @Override
  7. public void init(ServletConfig config) throws ServletException {
  8. ServletContext servletContext = config.getServletContext();
  9. WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
  10. statusService = (FileUploadStatusServiceI) context.getBean( "fileUploadStatusServiceImpl");
  11. repositoryPath = FileUtils.getTempDirectoryPath();
  12. uploadPath = config.getServletContext().getRealPath( "datas/uploader");
  13. File up = new File(uploadPath);
  14. if (!up.exists()) {
  15. up.mkdir();
  16. }
  17. }
  18. @Override
  19. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  20. // TODO Auto-generated method stub
  21. String fileName = new String(req.getParameter( "filename"));
  22. //String chunk = req.getParameter("chunk");
  23. //System.out.println(chunk);
  24. System.out.println(fileName);
  25. resp.setContentType( "text/json; charset=utf-8");
  26. TfileUploadStatus file = statusService.get(fileName);
  27. try {
  28. if (file != null) {
  29. int schunk = file.getChunk();
  30. deleteFile(uploadPath + schunk + "_" + fileName);
  31. //long off = schunk * Long.parseLong(chunkSize);
  32. resp.getWriter().write( "{\"off\":" + schunk + "}");
  33. } else {
  34. resp.getWriter().write( "{\"off\":1}");
  35. }
  36. } catch (Exception e) {
  37. // TODO Auto-generated catch block
  38. e.printStackTrace();
  39. }
  40. }
  41. }

接口2





  
  1. @WebServlet(urlPatterns = { "/uploaderWithContinuinglyTransferring" })
  2. public class UploaderServletWithContinuinglyTransferring extends HttpServlet {
  3. private static final long serialVersionUID = 1L;
  4. private FileUploadStatusServiceI statusService;
  5. String repositoryPath;
  6. String uploadPath;
  7. @Override
  8. public void init(ServletConfig config) throws ServletException {
  9. ServletContext servletContext = config.getServletContext();
  10. WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
  11. statusService = (FileUploadStatusServiceI) context.getBean( "fileUploadStatusServiceImpl");
  12. repositoryPath = FileUtils.getTempDirectoryPath();
  13. System.out.println( "临时目录:" + repositoryPath);
  14. uploadPath = config.getServletContext().getRealPath( "datas/uploader");
  15. System.out.println( "目录:" + uploadPath);
  16. File up = new File(uploadPath);
  17. if (!up.exists()) {
  18. up.mkdir();
  19. }
  20. }
  21. @SuppressWarnings( "unchecked")
  22. public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  23. response.setCharacterEncoding( "UTF-8");
  24. Integer schunk = null; // 分割块数
  25. Integer schunks = null; // 总分割数
  26. String name = null; // 文件名
  27. BufferedOutputStream outputStream = null;
  28. if (ServletFileUpload.isMultipartContent(request)) {
  29. try {
  30. DiskFileItemFactory factory = new DiskFileItemFactory();
  31. factory.setSizeThreshold( 1024);
  32. factory.setRepository( new File(repositoryPath)); // 设置临时目录
  33. ServletFileUpload upload = new ServletFileUpload(factory);
  34. upload.setHeaderEncoding( "UTF-8");
  35. upload.setSizeMax( 5 * 1024 * 1024 * 1024); // 设置附近大小
  36. List<FileItem> items = upload.parseRequest(request);
  37. // 生成新文件名
  38. String newFileName = null;
  39. for (FileItem item : items) {
  40. if (!item.isFormField()) { // 如果是文件类型
  41. name = newFileName; // 获得文件名
  42. if (name != null) {
  43. String nFname = newFileName;
  44. if (schunk != null) {
  45. nFname = schunk + "_" + name;
  46. }
  47. File savedFile = new File(uploadPath, nFname);
  48. item.write(savedFile);
  49. }
  50. } else {
  51. // 判断是否带分割信息
  52. if (item.getFieldName().equals( "chunk")) {
  53. schunk = Integer.parseInt(item.getString());
  54. //System.out.println(schunk);
  55. }
  56. if (item.getFieldName().equals( "chunks")) {
  57. schunks = Integer.parseInt(item.getString());
  58. }
  59. if (item.getFieldName().equals( "name")) {
  60. newFileName = new String(item.getString());
  61. }
  62. }
  63. }
  64. //System.out.println(schunk + "/" + schunks);
  65. if (schunk != null && schunk == 1) {
  66. TfileUploadStatus file = statusService.get(newFileName);
  67. if (file != null) {
  68. statusService.updateChunk(newFileName, schunk);
  69. } else {
  70. statusService.add(newFileName, schunk, schunks);
  71. }
  72. } else {
  73. TfileUploadStatus file = statusService.get(newFileName);
  74. if (file != null) {
  75. statusService.updateChunk(newFileName, schunk);
  76. }
  77. }
  78. if (schunk != null && schunk.intValue() == schunks.intValue()) {
  79. outputStream = new BufferedOutputStream( new FileOutputStream( new File(uploadPath, newFileName)));
  80. // 遍历文件合并
  81. for ( int i = 1; i <= schunks; i++) {
  82. //System.out.println("文件合并:" + i + "/" + schunks);
  83. File tempFile = new File(uploadPath, i + "_" + name);
  84. byte[] bytes = FileUtils.readFileToByteArray(tempFile);
  85. outputStream.write(bytes);
  86. outputStream.flush();
  87. tempFile.delete();
  88. }
  89. outputStream.flush();
  90. }
  91. response.getWriter().write( "{\"status\":true,\"newName\":\"" + newFileName + "\"}");
  92. } catch (FileUploadException e) {
  93. e.printStackTrace();
  94. response.getWriter().write( "{\"status\":false}");
  95. } catch (Exception e) {
  96. e.printStackTrace();
  97. response.getWriter().write( "{\"status\":false}");
  98. } finally {
  99. try {
  100. if (outputStream != null)
  101. outputStream.close();
  102. } catch (IOException e) {
  103. e.printStackTrace();
  104. }
  105. }
  106. }
  107. }
  108. }


android端
UploadTask 上传线程类




  
  1. package com.mainaer.wjoklib.okhttp.upload;
  2. import android.database.sqlite.SQLiteDatabase;
  3. import android.os.Environment;
  4. import android.os.Handler;
  5. import android.os.Looper;
  6. import android.os.Message;
  7. import android.text.TextUtils;
  8. import java.io.Closeable;
  9. import java.io.File;
  10. import java.io.IOException;
  11. import java.text.DecimalFormat;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. import okhttp3.Headers;
  15. import okhttp3.MediaType;
  16. import okhttp3.MultipartBody;
  17. import okhttp3.OkHttpClient;
  18. import okhttp3.Request;
  19. import okhttp3.RequestBody;
  20. import okhttp3.Response;
  21. /**
  22. * 上传线程
  23. *
  24. * @author hst
  25. * @date 2016/9/6 .
  26. */
  27. public class UploadTask implements Runnable {
  28. private static String FILE_MODE = "rwd";
  29. private OkHttpClient mClient;
  30. private SQLiteDatabase db;
  31. private UploadTaskListener mListener;
  32. private Builder mBuilder;
  33. private String id; // task id
  34. private String url; // file url
  35. private String fileName; // File name when saving
  36. private int uploadStatus;
  37. private int chunck, chuncks; //流块
  38. private int position;
  39. private int errorCode;
  40. static String BOUNDARY = "----------" + System.currentTimeMillis();
  41. public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse( "multipart/form-data;boundary=" + BOUNDARY);
  42. private UploadTask(Builder builder) {
  43. mBuilder = builder;
  44. mClient = new OkHttpClient();
  45. this.id = mBuilder.id;
  46. this.url = mBuilder.url;
  47. this.fileName = mBuilder.fileName;
  48. this.uploadStatus = mBuilder.uploadStatus;
  49. this.chunck = mBuilder.chunck;
  50. this.setmListener(mBuilder.listener);
  51. // 以kb为计算单位
  52. }
  53. @Override
  54. public void run() {
  55. try {
  56. int blockLength = 1024 * 1024;
  57. File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator +fileName);
  58. if (file.length() % blockLength == 0) {
  59. chuncks = ( int) file.length() / blockLength;
  60. } else {
  61. chuncks = ( int) file.length() / blockLength + 1;
  62. }
  63. while (chunck <= chuncks&&uploadStatus!= UploadStatus.UPLOAD_STATUS_PAUSE&&uploadStatus!= UploadStatus.UPLOAD_STATUS_ERROR)
  64. {
  65. uploadStatus = UploadStatus.UPLOAD_STATUS_UPLOADING;
  66. Map<String, String> params = new HashMap<String, String>();
  67. params.put( "name", fileName);
  68. params.put( "chunks", chuncks + "");
  69. params.put( "chunk", chunck + "");
  70. final byte[] mBlock = FileUtils.getBlock((chunck - 1) * blockLength, file, blockLength);
  71. MultipartBody.Builder builder = new MultipartBody.Builder()
  72. .setType(MultipartBody.FORM);
  73. addParams(builder, params);
  74. RequestBody requestBody = RequestBody.create(MEDIA_TYPE_MARKDOWN, mBlock);
  75. builder.addFormDataPart( "mFile", fileName, requestBody);
  76. Request request = new Request.Builder()
  77. .url(url+ "uploaderWithContinuinglyTransferring")
  78. .post(builder.build())
  79. .build();
  80. Response response = null;
  81. response = mClient.newCall(request).execute();
  82. if (response.isSuccessful()) {
  83. onCallBack();
  84. chunck++;
  85. /* if (chunck <= chuncks) {
  86. run();
  87. }*/
  88. }
  89. else
  90. {
  91. uploadStatus = UploadStatus.UPLOAD_STATUS_ERROR;
  92. onCallBack();
  93. }
  94. }
  95. } catch (IOException e) {
  96. uploadStatus = UploadStatus.UPLOAD_STATUS_ERROR;
  97. onCallBack();
  98. e.printStackTrace();
  99. }
  100. }
  101. /* */ /**
  102. * 删除数据库文件和已经上传的文件
  103. */ /*
  104. public void cancel() {
  105. if (mListener != null)
  106. mListener.onCancel(UploadTask.this);
  107. }*/
  108. /**
  109. * 分发回调事件到ui层
  110. */
  111. private void onCallBack() {
  112. mHandler.sendEmptyMessage(uploadStatus);
  113. // 同步manager中的task信息
  114. //UploadManager.getInstance().updateUploadTask(this);
  115. }
  116. Handler mHandler = new Handler(Looper.getMainLooper()) {
  117. @Override
  118. public void handleMessage(Message msg) {
  119. int code = msg.what;
  120. switch (code) {
  121. // 上传失败
  122. case UploadStatus.UPLOAD_STATUS_ERROR:
  123. mListener.onError(UploadTask. this, errorCode,position);
  124. break;
  125. // 正在上传
  126. case UploadStatus.UPLOAD_STATUS_UPLOADING:
  127. mListener.onUploading(UploadTask. this, getDownLoadPercent(), position);
  128. // 暂停上传
  129. break;
  130. case UploadStatus.UPLOAD_STATUS_PAUSE:
  131. mListener.onPause(UploadTask. this);
  132. break;
  133. }
  134. }
  135. };
  136. private String getDownLoadPercent() {
  137. String baifenbi = "0"; // 接受百分比的值
  138. if (chunck >= chuncks) {
  139. return "100";
  140. }
  141. double baiy = chunck * 1.0;
  142. double baiz = chuncks * 1.0;
  143. // 防止分母为0出现NoN
  144. if (baiz > 0) {
  145. double fen = (baiy / baiz) * 100;
  146. //NumberFormat nf = NumberFormat.getPercentInstance();
  147. //nf.setMinimumFractionDigits(2); //保留到小数点后几位
  148. // 百分比格式,后面不足2位的用0补齐
  149. //baifenbi = nf.format(fen);
  150. //注释掉的也是一种方法
  151. DecimalFormat df1 = new DecimalFormat( "0"); //0.00
  152. baifenbi = df1.format(fen);
  153. }
  154. return baifenbi;
  155. }
  156. private String getFileNameFromUrl(String url) {
  157. if (!TextUtils.isEmpty(url)) {
  158. return url.substring(url.lastIndexOf( "/") + 1);
  159. }
  160. return System.currentTimeMillis() + "";
  161. }
  162. private void close(Closeable closeable) {
  163. try {
  164. closeable.close();
  165. } catch (IOException e) {
  166. e.printStackTrace();
  167. }
  168. }
  169. public void setClient(OkHttpClient mClient) {
  170. this.mClient = mClient;
  171. }
  172. public Builder getBuilder() {
  173. return mBuilder;
  174. }
  175. public void setBuilder(Builder builder) {
  176. this.mBuilder = builder;
  177. }
  178. public String getId() {
  179. if (!TextUtils.isEmpty(id)) {
  180. } else {
  181. id = url;
  182. }
  183. return id;
  184. }
  185. public String getUrl() {
  186. return url;
  187. }
  188. public String getFileName() {
  189. return fileName;
  190. }
  191. public void setUploadStatus(int uploadStatus) {
  192. this.uploadStatus = uploadStatus;
  193. }
  194. public int getUploadStatus() {
  195. return uploadStatus;
  196. }
  197. public void setmListener(UploadTaskListener mListener) {
  198. this.mListener = mListener;
  199. }
  200. public static class Builder {
  201. private String id; // task id
  202. private String url; // file url
  203. private String fileName; // File name when saving
  204. private int uploadStatus = UploadStatus.UPLOAD_STATUS_INIT;
  205. private int chunck; //第几块
  206. private UploadTaskListener listener;
  207. /**
  208. * 作为上传task开始、删除、停止的key值,如果为空则默认是url
  209. *
  210. * @param id
  211. * @return
  212. */
  213. public Builder setId(String id) {
  214. this.id = id;
  215. return this;
  216. }
  217. /**
  218. * 上传url(not null)
  219. *
  220. * @param url
  221. * @return
  222. */
  223. public Builder setUrl(String url) {
  224. this.url = url;
  225. return this;
  226. }
  227. /**
  228. * 设置上传状态
  229. *
  230. * @param uploadStatus
  231. * @return
  232. */
  233. public Builder setUploadStatus(int uploadStatus) {
  234. this.uploadStatus = uploadStatus;
  235. return this;
  236. }
  237. /**
  238. * 第几块
  239. *
  240. * @param chunck
  241. * @return
  242. */
  243. public Builder setChunck(int chunck) {
  244. this.chunck = chunck;
  245. return this;
  246. }
  247. /**
  248. * 设置文件名
  249. *
  250. * @param fileName
  251. * @return
  252. */
  253. public Builder setFileName(String fileName) {
  254. this.fileName = fileName;
  255. return this;
  256. }
  257. /**
  258. * 设置上传回调
  259. *
  260. * @param listener
  261. * @return
  262. */
  263. public Builder setListener(UploadTaskListener listener) {
  264. this.listener = listener;
  265. return this;
  266. }
  267. public UploadTask build() {
  268. return new UploadTask( this);
  269. }
  270. }
  271. private void addParams(MultipartBody.Builder builder, Map<String, String> params) {
  272. if (params != null && !params.isEmpty()) {
  273. for (String key : params.keySet()) {
  274. builder.addPart(Headers.of( "Content-Disposition", "form-data; name=\"" + key + "\""),
  275. RequestBody.create( null, params.get(key)));
  276. }
  277. }
  278. }
  279. }

UploadManager上传管理类




  
  1. package com.mainaer.wjoklib.okhttp.upload;
  2. import android.content.Context;
  3. import android.database.sqlite.SQLiteDatabase;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. import java.util.concurrent.ExecutorService;
  7. import java.util.concurrent.Executors;
  8. import java.util.concurrent.Future;
  9. import java.util.concurrent.TimeUnit;
  10. import okhttp3.OkHttpClient;
  11. /**
  12. * 上传管理器
  13. *
  14. * @author wangjian
  15. * @date 2016/5/13 .
  16. */
  17. public class UploadManager {
  18. private static Context mContext;
  19. private static SQLiteDatabase db;
  20. private OkHttpClient mClient;
  21. private int mPoolSize = 20;
  22. // 将执行结果保存在future变量中
  23. private Map<String, Future> mFutureMap;
  24. private ExecutorService mExecutor;
  25. private Map<String, UploadTask> mCurrentTaskList;
  26. static UploadManager manager;
  27. /**
  28. * 方法加锁,防止多线程操作时出现多个实例
  29. */
  30. private static synchronized void init() {
  31. if (manager == null) {
  32. manager = new UploadManager();
  33. }
  34. }
  35. /**
  36. * 获得当前对象实例
  37. *
  38. * @return 当前实例对象
  39. */
  40. public final static UploadManager getInstance() {
  41. if (manager == null) {
  42. init();
  43. }
  44. return manager;
  45. }
  46. /**
  47. * 管理器初始化,建议在application中调用
  48. *
  49. * @param context
  50. */
  51. public static void init(Context context, SQLiteDatabase db1) {
  52. mContext = context;
  53. db = db1;
  54. getInstance();
  55. }
  56. public UploadManager() {
  57. initOkhttpClient();
  58. // 初始化线程池
  59. mExecutor = Executors.newFixedThreadPool(mPoolSize);
  60. mFutureMap = new HashMap<>();
  61. mCurrentTaskList = new HashMap<>();
  62. }
  63. /**
  64. * 初始化okhttp
  65. */
  66. private void initOkhttpClient() {
  67. OkHttpClient.Builder okBuilder = new OkHttpClient.Builder();
  68. okBuilder.connectTimeout( 1000, TimeUnit.SECONDS);
  69. okBuilder.readTimeout( 1000, TimeUnit.SECONDS);
  70. okBuilder.writeTimeout( 1000, TimeUnit.SECONDS);
  71. mClient = okBuilder.build();
  72. }
  73. /**
  74. * 添加上传任务
  75. *
  76. * @param uploadTask
  77. */
  78. public void addUploadTask(UploadTask uploadTask) {
  79. if (uploadTask != null && !isUploading(uploadTask)) {
  80. uploadTask.setClient(mClient);
  81. uploadTask.setUploadStatus(UploadStatus.UPLOAD_STATUS_INIT);
  82. // 保存上传task列表
  83. mCurrentTaskList.put(uploadTask.getId(), uploadTask);
  84. Future future = mExecutor.submit(uploadTask);
  85. mFutureMap.put(uploadTask.getId(), future);
  86. }
  87. }
  88. private boolean isUploading(UploadTask task) {
  89. if (task != null) {
  90. if (task.getUploadStatus() == UploadStatus.UPLOAD_STATUS_UPLOADING) {
  91. return true;
  92. }
  93. }
  94. return false;
  95. }
  96. /**
  97. * 暂停上传任务
  98. *
  99. * @param id 任务id
  100. */
  101. public void pause(String id) {
  102. UploadTask task = getUploadTask(id);
  103. if (task != null) {
  104. task.setUploadStatus(UploadStatus.UPLOAD_STATUS_PAUSE);
  105. }
  106. }
  107. /**
  108. * 重新开始已经暂停的上传任务
  109. *
  110. * @param id 任务id
  111. */
  112. public void resume(String id, UploadTaskListener listener) {
  113. UploadTask task = getUploadTask(id);
  114. if (task != null) {
  115. addUploadTask(task);
  116. }
  117. }
  118. /* */ /**
  119. * 取消上传任务(同时会删除已经上传的文件,和清空数据库缓存)
  120. *
  121. * @param id 任务id
  122. * @param listener
  123. */ /*
  124. public void cancel(String id, UploadTaskListener listener) {
  125. UploadTask task = getUploadTask(id);
  126. if (task != null) {
  127. mCurrentTaskList.remove(id);
  128. mFutureMap.remove(id);
  129. task.setmListener(listener);
  130. task.cancel();
  131. task.setDownloadStatus(UploadStatus.DOWNLOAD_STATUS_CANCEL);
  132. }
  133. }*/
  134. /**
  135. * 实时更新manager中的task信息
  136. *
  137. * @param task
  138. */
  139. public void updateUploadTask(UploadTask task) {
  140. if (task != null) {
  141. UploadTask currTask = getUploadTask(task.getId());
  142. if (currTask != null) {
  143. mCurrentTaskList.put(task.getId(), task);
  144. }
  145. }
  146. }
  147. /**
  148. * 获得指定的task
  149. *
  150. * @param id task id
  151. * @return
  152. */
  153. public UploadTask getUploadTask(String id) {
  154. UploadTask currTask = mCurrentTaskList.get(id);
  155. if (currTask == null) {
  156. currTask = parseEntity2Task( new UploadTask.Builder().build());
  157. // 放入task list中
  158. mCurrentTaskList.put(id, currTask);
  159. }
  160. return currTask;
  161. }
  162. private UploadTask parseEntity2Task(UploadTask currTask) {
  163. UploadTask.Builder builder = new UploadTask.Builder() //
  164. .setUploadStatus(currTask.getUploadStatus())
  165. .setFileName(currTask.getFileName()) //
  166. .setUrl(currTask.getUrl())
  167. .setId(currTask.getId());
  168. currTask.setBuilder(builder);
  169. return currTask;
  170. }
  171. }
FileUtils文件分块类




  
  1. package com.mainaer.wjoklib.okhttp.upload;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.RandomAccessFile;
  5. public class FileUtils {
  6. public static byte[] getBlock( long offset, File file, int blockSize) {
  7. byte[] result = new byte[blockSize];
  8. RandomAccessFile accessFile = null;
  9. try {
  10. accessFile = new RandomAccessFile(file, "r");
  11. accessFile.seek(offset);
  12. int readSize = accessFile.read(result);
  13. if (readSize == - 1) {
  14. return null;
  15. } else if (readSize == blockSize) {
  16. return result;
  17. } else {
  18. byte[] tmpByte = new byte[readSize];
  19. System.arraycopy(result, 0, tmpByte, 0, readSize);
  20. return tmpByte;
  21. }
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. } finally {
  25. if (accessFile != null) {
  26. try {
  27. accessFile.close();
  28. } catch (IOException e1) {
  29. }
  30. }
  31. }
  32. return null;
  33. }
  34. }
UploadTaskListener接口类管理类




  
  1. package com.mainaer.wjoklib.okhttp.upload;
  2. import com.mainaer.wjoklib.okhttp.download.DownloadStatus;
  3. import java.io.File;
  4. /**
  5. * Created by hst on 16/9/21.
  6. */
  7. public interface UploadTaskListener {
  8. /**
  9. * 上传中
  10. *
  11. * @param percent
  12. * @param uploadTask
  13. */
  14. void onUploading(UploadTask uploadTask, String percent,int position)
  15. /**
  16. * 上传成功
  17. *
  18. * @param file
  19. * @param uploadTask
  20. */
  21. void onUploadSuccess (UploadTask uploadTask, File file);
  22. /**
  23. * 上传失败
  24. *
  25. * @param uploadTask
  26. * @param errorCode {@link DownloadStatus}
  27. */
  28. void onError(UploadTask uploadTask, int errorCode,int position);
  29. /**
  30. * 上传暂停
  31. *
  32. * @param uploadTask
  33. *
  34. */
  35. void onPause(UploadTask uploadTask);
  36. }
android源码地址:https://github.com/handsometong/okhttpUpLoader






发布了10 篇原创文章 · 获赞 230 · 访问量 79万+
08-29 00:17