最近项目中刚好遇到这个大文件上传断点续传的问题,花了两个星期时间解决了,顺便总结下解决思路
刚开始去网上查看别人的解决思路,最终确定了两种方案:第一种:socket解决方式,第二种是利用传统的http请求,进行分块请求
鉴于第一种方式需要自己写服务端,而且处理起来相当麻烦,稳定性不容易保证,(后来做完了才发现有一种方案是websocket方案,有兴趣的童鞋可以考虑改进下),所以选择了第二种方案,利用分片技术进行上传
下面给出关键代码
首先是线程池的控制
代码参考自:http://blog.csdn.net/hsuxu/article/details/8985931
利用多线程来并发处理上传的问题,把所有要上传的块都放到线程池中来执行,下面是代码
public class UploadThread { // 线程池中默认线程的个数为5 private static int worker_num = 5; // 工作线程 private WorkThread[] workThrads; // 未处理的任务 private static volatile int finished_task = 0; // 任务队列,作为一个缓冲,List线程不安全 private List<Runnable> taskQueue = new LinkedList<Runnable>(); private static UploadThread threadPool; // 创建具有默认线程个数的线程池 private UploadThread() { this(5); } // 创建线程池,worker_num为线程池中工作线程的个数 private UploadThread(int worker_num) { UploadThread.worker_num = worker_num; workThrads = new WorkThread[worker_num]; for (int i = 0; i < worker_num; i++) { workThrads[i] = new WorkThread(); workThrads[i].start();// 开启线程池中的线程 } } // 单态模式,获得一个默认线程个数的线程池 public static UploadThread getThreadPool() { return getThreadPool(UploadThread.worker_num); } // 单态模式,获得一个指定线程个数的线程池,worker_num(>0)为线程池中工作线程的个数 // worker_num<=0创建默认的工作线程个数 public static UploadThread getThreadPool(int worker_num1) { if (worker_num1 <= 0) worker_num1 = UploadThread.worker_num; if (threadPool == null) threadPool = new UploadThread(worker_num1); return threadPool; } // 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定 public void execute(Runnable task) { synchronized (taskQueue) { taskQueue.add(task); taskQueue.notify(); } } // 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定 public void execute(Runnable[] task) { synchronized (taskQueue) { for (Runnable t : task) taskQueue.add(t); taskQueue.notify(); } } // 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定 public void execute(List<Runnable> task) { synchronized (taskQueue) { for (Runnable t : task) taskQueue.add(t); taskQueue.notify(); } } // 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁 public void destroy() { while (!taskQueue.isEmpty()) {// 如果还有任务没执行完成,就先睡会吧 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } // 工作线程停止工作,且置为null for (int i = 0; i < worker_num; i++) { workThrads[i].stopWorker(); workThrads[i] = null; } threadPool=null; taskQueue.clear();// 清空任务队列 } // 返回工作线程的个数 public int getWorkThreadNumber() { return worker_num; } // 返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成 public int getFinishedTasknumber() { return finished_task; } // 返回任务队列的长度,即还没处理的任务个数 public int getWaitTasknumber() { return taskQueue.size(); } // 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数 @Override public String toString() { return "WorkThread number:" + worker_num + " finished task number:" + finished_task + " wait task number:" + getWaitTasknumber(); } /** * 内部类,工作线程 */ private class WorkThread extends Thread { // 该工作线程是否有效,用于结束该工作线程 private boolean isRunning = true; /* * 关键所在啊,如果任务队列不空,则取出任务执行,若任务队列空,则等待 */ @Override public void run() { Runnable r = null; while (isRunning) {// 注意,若线程无效则自然结束run方法,该线程就没用了 synchronized (taskQueue) { while (isRunning && taskQueue.isEmpty()) {// 队列为空 try { taskQueue.wait(20); } catch (InterruptedException e) { e.printStackTrace(); } } if (!taskQueue.isEmpty()) r = taskQueue.remove(0);// 取出任务 } if (r != null) { r.run();// 执行任务 } finished_task++; r = null; } } // 停止工作,让该线程自然执行完run方法,自然结束 public void stopWorker() { isRunning = false; } } }
下面是上传线程,利用randomaccessfile来进行分片处理,并上传
class UploadRunnable implements Runnable { UploadModel model; private UploadRunnable(UploadModel model) { this.model = model; } @Override public void run() { L.d("文件分块数" + model.getCutNum()); Map map = new HashMap(); try { RandomAccessFile oSavedFile = new RandomAccessFile(model.getPath(), "rw");// 创建一个随机访问文件类,可读写模式 int mBufferSize = (int) model.getCutSize(); // 每次处理1024 * 100字节 byte[] buffer = new byte[mBufferSize]; //设置随机文件起始位置 oSavedFile.seek(model.getStart()); //读取分片文件大小,并赋值给buffer oSavedFile.read(buffer); map.put("operation", "uploadCut"); map.put("folder", options.getFolder()); map.put("model", JSON.toJSONString(model)); HttpClient4Util client4Util = new HttpClient4Util(); String result = client4Util.uploadFile(options.getUrl(), map, buffer); if (result != null && result.equals("success")) { success_task++; int i = (int) (((float) success_task / (float) size) * 100); if (l != null) { l.processing(i); } model.setStatus(1); // model.setCutData(""); uploadModelBaseDao.update(model); } else { err_task++; } } catch (Exception e) { err_task++; } finally { //每上传一次要记录下来,用来统计上传的记录 finish_task++; if (finish_task == size) { //文件上传完毕,等待下次上传 isUpload = false; if (err_task > 0) { if (l != null) { l.failure("上传失败"); } } else { Map map1 = new HashMap(); map1.put("model", JSON.toJSON(model)); map1.put("operation", "getCheckUpload"); map1.put("folder", options.getFolder()); String result = new GetData().postDataBase(options.getUrl(), map1); if (l != null) { if (result != null) { JSONObject jsonObject = JSON.parseObject(result); if (jsonObject != null && "200".equals(jsonObject.getString("Code")) && jsonObject.get("Data") != null) { uploadModelBaseDao.delete("path", model.getPath()); JSONObject jsonObject1 = jsonObject.getJSONObject("Data"); String id = jsonObject1.getString("Id"); FileModel fileModel = new FileModel(); fileModel.setMd5(model.getMd5()); fileModel.setPath(model.getPath()); fileModel.setFolder(model.getFolder()); fileModel.setDate(new Date()); fileModel.setFileType(model.getFileType()); fileModel.setStatus(1); fileModel.setId(id); fileModelBaseDao.insert(fileModel, false); l.success(id); } else { uploadModelBaseDao.delete("path", model.getPath()); l.failure("上传失败"); } } else { uploadModelBaseDao.delete("path", model.getPath()); l.failure("上传失败"); } } } } } } }
具体的代码还没整理好,整理好一并上传上来,第一次写博客,不怎么会写,以后慢慢总结吧