android实现大文件断点上传
程序员文章站
2022-06-02 14:42:00
...
前言
之前项目需要上传大文件的功能,上传大文件经常遇到上传一半由于网络或者其他一些原因上传失败。然后又得重新上传(很麻烦),所以就想能不能做个断点上传的功能。于是网上搜索,发现市面上很少有断点上传的案例,有找到一个案例也是采用SOCKET作为上传方式(大文件上传,不适合使用POST,GET形式)。由于大文件夹不适合http上传的方式,所以就想能不能把大文件切割成n块小文件,然后上传这些小文件,所有小文件全部上传成功后再在服务器上进行拼接。这样不就可以实现断点上传,又解决了http不适合上传大文件的难题了吗!!!
原理分析
*******Android客户端********
首先,android端调用服务器接口1,参数为filename(服务器标识判断是否上传过)
如果存在filename,说明之前上传过,则续传;如果没有,则从零开始上传。
然后,android端调用服务器接口2,传入参数name,chunck(传到第几块),chuncks(总共多少块)
*******服务器端********
接口一:根据上传文件名称filename 判断是否之前上传过,没有则返回客户端chunck=1,有则读取记录chunck并返回。
接口二:上传文件,如果上传块数chunck=chuncks,遍历所有块文件拼接成一个完整文件。
接口1
-
@WebServlet(urlPatterns = { "/ckeckFileServlet" })
-
public class CkeckFileServlet extends HttpServlet {
-
-
private FileUploadStatusServiceI statusService;
-
String repositoryPath;
-
String uploadPath;
-
-
@Override
-
public void init(ServletConfig config) throws ServletException {
-
ServletContext servletContext = config.getServletContext();
-
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
-
statusService = (FileUploadStatusServiceI) context.getBean("fileUploadStatusServiceImpl");
-
-
repositoryPath = FileUtils.getTempDirectoryPath();
-
uploadPath = config.getServletContext().getRealPath("datas/uploader");
-
File up = new File(uploadPath);
-
if (!up.exists()) {
-
up.mkdir();
-
}
-
}
-
-
@Override
-
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
// TODO Auto-generated method stub
-
-
String fileName = new String(req.getParameter("filename"));
-
//String chunk = req.getParameter("chunk");
-
//System.out.println(chunk);
-
System.out.println(fileName);
-
resp.setContentType("text/json; charset=utf-8");
-
-
TfileUploadStatus file = statusService.get(fileName);
-
-
try {
-
if (file != null) {
-
int schunk = file.getChunk();
-
deleteFile(uploadPath + schunk + "_" + fileName);
-
//long off = schunk * Long.parseLong(chunkSize);
-
resp.getWriter().write("{\"off\":" + schunk + "}");
-
-
} else {
-
resp.getWriter().write("{\"off\":1}");
-
}
-
} catch (Exception e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
}
-
}
-
}
接口2
-
@WebServlet(urlPatterns = { "/uploaderWithContinuinglyTransferring" })
-
public class UploaderServletWithContinuinglyTransferring extends HttpServlet {
-
-
private static final long serialVersionUID = 1L;
-
-
private FileUploadStatusServiceI statusService;
-
String repositoryPath;
-
String uploadPath;
-
-
@Override
-
public void init(ServletConfig config) throws ServletException {
-
ServletContext servletContext = config.getServletContext();
-
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
-
statusService = (FileUploadStatusServiceI) context.getBean("fileUploadStatusServiceImpl");
-
-
repositoryPath = FileUtils.getTempDirectoryPath();
-
System.out.println("临时目录:" + repositoryPath);
-
uploadPath = config.getServletContext().getRealPath("datas/uploader");
-
System.out.println("目录:" + uploadPath);
-
File up = new File(uploadPath);
-
if (!up.exists()) {
-
up.mkdir();
-
}
-
}
-
-
@SuppressWarnings("unchecked")
-
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-
response.setCharacterEncoding("UTF-8");
-
Integer schunk = null;// 分割块数
-
Integer schunks = null;// 总分割数
-
String name = null;// 文件名
-
BufferedOutputStream outputStream = null;
-
if (ServletFileUpload.isMultipartContent(request)) {
-
try {
-
DiskFileItemFactory factory = new DiskFileItemFactory();
-
factory.setSizeThreshold(1024);
-
factory.setRepository(new File(repositoryPath));// 设置临时目录
-
ServletFileUpload upload = new ServletFileUpload(factory);
-
upload.setHeaderEncoding("UTF-8");
-
upload.setSizeMax(5 * 1024 * 1024 * 1024);// 设置附近大小
-
List<FileItem> items = upload.parseRequest(request);
-
// 生成新文件名
-
-
String newFileName = null;
-
for (FileItem item : items) {
-
if (!item.isFormField()) {// 如果是文件类型
-
name = newFileName;// 获得文件名
-
if (name != null) {
-
String nFname = newFileName;
-
if (schunk != null) {
-
nFname = schunk + "_" + name;
-
}
-
File savedFile = new File(uploadPath, nFname);
-
item.write(savedFile);
-
}
-
} else {
-
// 判断是否带分割信息
-
if (item.getFieldName().equals("chunk")) {
-
schunk = Integer.parseInt(item.getString());
-
//System.out.println(schunk);
-
}
-
if (item.getFieldName().equals("chunks")) {
-
schunks = Integer.parseInt(item.getString());
-
}
-
-
if (item.getFieldName().equals("name")) {
-
newFileName = new String(item.getString());
-
-
}
-
-
}
-
-
}
-
//System.out.println(schunk + "/" + schunks);
-
if (schunk != null && schunk == 1) {
-
TfileUploadStatus file = statusService.get(newFileName);
-
if (file != null) {
-
statusService.updateChunk(newFileName, schunk);
-
} else {
-
statusService.add(newFileName, schunk, schunks);
-
}
-
-
} else {
-
TfileUploadStatus file = statusService.get(newFileName);
-
if (file != null) {
-
statusService.updateChunk(newFileName, schunk);
-
}
-
}
-
if (schunk != null && schunk.intValue() == schunks.intValue()) {
-
outputStream = new BufferedOutputStream(new FileOutputStream(new File(uploadPath, newFileName)));
-
// 遍历文件合并
-
for (int i = 1; i <= schunks; i++) {
-
//System.out.println("文件合并:" + i + "/" + schunks);
-
File tempFile = new File(uploadPath, i + "_" + name);
-
byte[] bytes = FileUtils.readFileToByteArray(tempFile);
-
outputStream.write(bytes);
-
outputStream.flush();
-
tempFile.delete();
-
}
-
outputStream.flush();
-
}
-
response.getWriter().write("{\"status\":true,\"newName\":\"" + newFileName + "\"}");
-
} catch (FileUploadException e) {
-
e.printStackTrace();
-
response.getWriter().write("{\"status\":false}");
-
} catch (Exception e) {
-
e.printStackTrace();
-
response.getWriter().write("{\"status\":false}");
-
} finally {
-
try {
-
if (outputStream != null)
-
outputStream.close();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
}
-
-
}
-
android端UploadTask 上传线程类
-
package com.mainaer.wjoklib.okhttp.upload;
-
import android.database.sqlite.SQLiteDatabase;
-
import android.os.Environment;
-
import android.os.Handler;
-
import android.os.Looper;
-
import android.os.Message;
-
import android.text.TextUtils;
-
-
import java.io.Closeable;
-
import java.io.File;
-
import java.io.IOException;
-
import java.text.DecimalFormat;
-
import java.util.HashMap;
-
import java.util.Map;
-
-
import okhttp3.Headers;
-
import okhttp3.MediaType;
-
import okhttp3.MultipartBody;
-
import okhttp3.OkHttpClient;
-
import okhttp3.Request;
-
import okhttp3.RequestBody;
-
import okhttp3.Response;
-
-
/**
-
* 上传线程
-
*
-
* @author hst
-
* @date 2016/9/6 .
-
*/
-
public class UploadTask implements Runnable {
-
-
private static String FILE_MODE = "rwd";
-
private OkHttpClient mClient;
-
private SQLiteDatabase db;
-
private UploadTaskListener mListener;
-
-
private Builder mBuilder;
-
private String id;// task id
-
private String url;// file url
-
private String fileName; // File name when saving
-
private int uploadStatus;
-
private int chunck, chuncks;//流块
-
private int position;
-
-
private int errorCode;
-
static String BOUNDARY = "----------" + System.currentTimeMillis();
-
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("multipart/form-data;boundary=" + BOUNDARY);
-
-
private UploadTask(Builder builder) {
-
mBuilder = builder;
-
mClient = new OkHttpClient();
-
this.id = mBuilder.id;
-
this.url = mBuilder.url;
-
this.fileName = mBuilder.fileName;
-
this.uploadStatus = mBuilder.uploadStatus;
-
this.chunck = mBuilder.chunck;
-
this.setmListener(mBuilder.listener);
-
// 以kb为计算单位
-
}
-
-
@Override
-
public void run() {
-
try {
-
int blockLength = 1024 * 1024;
-
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator +fileName);
-
if (file.length() % blockLength == 0) {
-
chuncks = (int) file.length() / blockLength;
-
} else {
-
chuncks = (int) file.length() / blockLength + 1;
-
-
}
-
while (chunck <= chuncks&&uploadStatus!= UploadStatus.UPLOAD_STATUS_PAUSE&&uploadStatus!= UploadStatus.UPLOAD_STATUS_ERROR)
-
{
-
-
uploadStatus = UploadStatus.UPLOAD_STATUS_UPLOADING;
-
Map<String, String> params = new HashMap<String, String>();
-
params.put("name", fileName);
-
params.put("chunks", chuncks + "");
-
params.put("chunk", chunck + "");
-
final byte[] mBlock = FileUtils.getBlock((chunck - 1) * blockLength, file, blockLength);
-
MultipartBody.Builder builder = new MultipartBody.Builder()
-
.setType(MultipartBody.FORM);
-
addParams(builder, params);
-
RequestBody requestBody = RequestBody.create(MEDIA_TYPE_MARKDOWN, mBlock);
-
builder.addFormDataPart("mFile", fileName, requestBody);
-
Request request = new Request.Builder()
-
.url(url+ "uploaderWithContinuinglyTransferring")
-
.post(builder.build())
-
.build();
-
Response response = null;
-
response = mClient.newCall(request).execute();
-
if (response.isSuccessful()) {
-
onCallBack();
-
chunck++;
-
/* if (chunck <= chuncks) {
-
run();
-
}*/
-
}
-
else
-
{
-
uploadStatus = UploadStatus.UPLOAD_STATUS_ERROR;
-
onCallBack();
-
}
-
-
}
-
} catch (IOException e) {
-
uploadStatus = UploadStatus.UPLOAD_STATUS_ERROR;
-
onCallBack();
-
e.printStackTrace();
-
}
-
}
-
-
-
/* *//**
-
* 删除数据库文件和已经上传的文件
-
*//*
-
public void cancel() {
-
if (mListener != null)
-
mListener.onCancel(UploadTask.this);
-
}*/
-
-
/**
-
* 分发回调事件到ui层
-
*/
-
private void onCallBack() {
-
mHandler.sendEmptyMessage(uploadStatus);
-
// 同步manager中的task信息
-
//UploadManager.getInstance().updateUploadTask(this);
-
}
-
-
Handler mHandler = new Handler(Looper.getMainLooper()) {
-
@Override
-
public void handleMessage(Message msg) {
-
int code = msg.what;
-
switch (code) {
-
// 上传失败
-
case UploadStatus.UPLOAD_STATUS_ERROR:
-
mListener.onError(UploadTask.this, errorCode,position);
-
break;
-
// 正在上传
-
case UploadStatus.UPLOAD_STATUS_UPLOADING:
-
mListener.onUploading(UploadTask.this, getDownLoadPercent(), position);
-
// 暂停上传
-
break;
-
case UploadStatus.UPLOAD_STATUS_PAUSE:
-
mListener.onPause(UploadTask.this);
-
break;
-
-
}
-
}
-
};
-
-
private String getDownLoadPercent() {
-
String baifenbi = "0";// 接受百分比的值
-
if (chunck >= chuncks) {
-
return "100";
-
}
-
double baiy = chunck * 1.0;
-
double baiz = chuncks * 1.0;
-
// 防止分母为0出现NoN
-
if (baiz > 0) {
-
double fen = (baiy / baiz) * 100;
-
//NumberFormat nf = NumberFormat.getPercentInstance();
-
//nf.setMinimumFractionDigits(2); //保留到小数点后几位
-
// 百分比格式,后面不足2位的用0补齐
-
//baifenbi = nf.format(fen);
-
//注释掉的也是一种方法
-
DecimalFormat df1 = new DecimalFormat("0");//0.00
-
baifenbi = df1.format(fen);
-
}
-
return baifenbi;
-
}
-
-
-
private String getFileNameFromUrl(String url) {
-
if (!TextUtils.isEmpty(url)) {
-
return url.substring(url.lastIndexOf("/") + 1);
-
}
-
return System.currentTimeMillis() + "";
-
}
-
-
private void close(Closeable closeable) {
-
try {
-
closeable.close();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
-
-
public void setClient(OkHttpClient mClient) {
-
this.mClient = mClient;
-
}
-
-
public Builder getBuilder() {
-
return mBuilder;
-
}
-
-
public void setBuilder(Builder builder) {
-
this.mBuilder = builder;
-
}
-
-
public String getId() {
-
if (!TextUtils.isEmpty(id)) {
-
} else {
-
id = url;
-
}
-
return id;
-
}
-
-
public String getUrl() {
-
return url;
-
}
-
-
public String getFileName() {
-
return fileName;
-
}
-
-
-
public void setUploadStatus(int uploadStatus) {
-
this.uploadStatus = uploadStatus;
-
}
-
-
public int getUploadStatus() {
-
return uploadStatus;
-
}
-
-
-
public void setmListener(UploadTaskListener mListener) {
-
this.mListener = mListener;
-
}
-
-
public static class Builder {
-
private String id;// task id
-
private String url;// file url
-
private String fileName; // File name when saving
-
private int uploadStatus = UploadStatus.UPLOAD_STATUS_INIT;
-
private int chunck;//第几块
-
private UploadTaskListener listener;
-
-
/**
-
* 作为上传task开始、删除、停止的key值,如果为空则默认是url
-
*
-
* @param id
-
* @return
-
*/
-
public Builder setId(String id) {
-
this.id = id;
-
return this;
-
}
-
-
/**
-
* 上传url(not null)
-
*
-
* @param url
-
* @return
-
*/
-
public Builder setUrl(String url) {
-
this.url = url;
-
return this;
-
}
-
-
/**
-
* 设置上传状态
-
*
-
* @param uploadStatus
-
* @return
-
*/
-
public Builder setUploadStatus(int uploadStatus) {
-
this.uploadStatus = uploadStatus;
-
return this;
-
}
-
-
/**
-
* 第几块
-
*
-
* @param chunck
-
* @return
-
*/
-
public Builder setChunck(int chunck) {
-
this.chunck = chunck;
-
return this;
-
}
-
-
-
/**
-
* 设置文件名
-
*
-
* @param fileName
-
* @return
-
*/
-
public Builder setFileName(String fileName) {
-
this.fileName = fileName;
-
return this;
-
}
-
-
/**
-
* 设置上传回调
-
*
-
* @param listener
-
* @return
-
*/
-
public Builder setListener(UploadTaskListener listener) {
-
this.listener = listener;
-
return this;
-
}
-
-
public UploadTask build() {
-
return new UploadTask(this);
-
}
-
}
-
-
private void addParams(MultipartBody.Builder builder, Map<String, String> params) {
-
if (params != null && !params.isEmpty()) {
-
for (String key : params.keySet()) {
-
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),
-
RequestBody.create(null, params.get(key)));
-
}
-
}
-
}
-
}
UploadManager上传管理类
-
package com.mainaer.wjoklib.okhttp.upload;
-
import android.content.Context;
-
import android.database.sqlite.SQLiteDatabase;
-
-
import java.util.HashMap;
-
import java.util.Map;
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
import java.util.concurrent.Future;
-
import java.util.concurrent.TimeUnit;
-
-
import okhttp3.OkHttpClient;
-
-
/**
-
* 上传管理器
-
*
-
* @author wangjian
-
* @date 2016/5/13 .
-
*/
-
public class UploadManager {
-
-
private static Context mContext;
-
-
private static SQLiteDatabase db;
-
private OkHttpClient mClient;
-
-
private int mPoolSize = 20;
-
// 将执行结果保存在future变量中
-
private Map<String, Future> mFutureMap;
-
private ExecutorService mExecutor;
-
private Map<String, UploadTask> mCurrentTaskList;
-
-
static UploadManager manager;
-
-
/**
-
* 方法加锁,防止多线程操作时出现多个实例
-
*/
-
private static synchronized void init() {
-
if (manager == null) {
-
manager = new UploadManager();
-
}
-
}
-
-
/**
-
* 获得当前对象实例
-
*
-
* @return 当前实例对象
-
*/
-
public final static UploadManager getInstance() {
-
if (manager == null) {
-
init();
-
}
-
return manager;
-
}
-
-
/**
-
* 管理器初始化,建议在application中调用
-
*
-
* @param context
-
*/
-
public static void init(Context context, SQLiteDatabase db1) {
-
mContext = context;
-
db = db1;
-
getInstance();
-
}
-
-
public UploadManager() {
-
initOkhttpClient();
-
-
// 初始化线程池
-
mExecutor = Executors.newFixedThreadPool(mPoolSize);
-
mFutureMap = new HashMap<>();
-
mCurrentTaskList = new HashMap<>();
-
}
-
-
/**
-
* 初始化okhttp
-
*/
-
private void initOkhttpClient() {
-
OkHttpClient.Builder okBuilder = new OkHttpClient.Builder();
-
okBuilder.connectTimeout(1000, TimeUnit.SECONDS);
-
okBuilder.readTimeout(1000, TimeUnit.SECONDS);
-
okBuilder.writeTimeout(1000, TimeUnit.SECONDS);
-
mClient = okBuilder.build();
-
}
-
-
/**
-
* 添加上传任务
-
*
-
* @param uploadTask
-
*/
-
public void addUploadTask(UploadTask uploadTask) {
-
if (uploadTask != null && !isUploading(uploadTask)) {
-
uploadTask.setClient(mClient);
-
uploadTask.setUploadStatus(UploadStatus.UPLOAD_STATUS_INIT);
-
// 保存上传task列表
-
mCurrentTaskList.put(uploadTask.getId(), uploadTask);
-
Future future = mExecutor.submit(uploadTask);
-
mFutureMap.put(uploadTask.getId(), future);
-
}
-
}
-
-
private boolean isUploading(UploadTask task) {
-
if (task != null) {
-
if (task.getUploadStatus() == UploadStatus.UPLOAD_STATUS_UPLOADING) {
-
return true;
-
}
-
}
-
return false;
-
}
-
-
/**
-
* 暂停上传任务
-
*
-
* @param id 任务id
-
*/
-
public void pause(String id) {
-
UploadTask task = getUploadTask(id);
-
if (task != null) {
-
task.setUploadStatus(UploadStatus.UPLOAD_STATUS_PAUSE);
-
}
-
}
-
-
/**
-
* 重新开始已经暂停的上传任务
-
*
-
* @param id 任务id
-
*/
-
public void resume(String id, UploadTaskListener listener) {
-
UploadTask task = getUploadTask(id);
-
if (task != null) {
-
addUploadTask(task);
-
}
-
}
-
-
/* *//**
-
* 取消上传任务(同时会删除已经上传的文件,和清空数据库缓存)
-
*
-
* @param id 任务id
-
* @param listener
-
*//*
-
public void cancel(String id, UploadTaskListener listener) {
-
UploadTask task = getUploadTask(id);
-
if (task != null) {
-
mCurrentTaskList.remove(id);
-
mFutureMap.remove(id);
-
task.setmListener(listener);
-
task.cancel();
-
task.setDownloadStatus(UploadStatus.DOWNLOAD_STATUS_CANCEL);
-
}
-
}*/
-
-
/**
-
* 实时更新manager中的task信息
-
*
-
* @param task
-
*/
-
public void updateUploadTask(UploadTask task) {
-
if (task != null) {
-
UploadTask currTask = getUploadTask(task.getId());
-
if (currTask != null) {
-
mCurrentTaskList.put(task.getId(), task);
-
}
-
}
-
}
-
-
/**
-
* 获得指定的task
-
*
-
* @param id task id
-
* @return
-
*/
-
public UploadTask getUploadTask(String id) {
-
UploadTask currTask = mCurrentTaskList.get(id);
-
if (currTask == null) {
-
currTask = parseEntity2Task(new UploadTask.Builder().build());
-
// 放入task list中
-
mCurrentTaskList.put(id, currTask);
-
}
-
-
return currTask;
-
}
-
-
-
private UploadTask parseEntity2Task(UploadTask currTask) {
-
-
UploadTask.Builder builder = new UploadTask.Builder()//
-
.setUploadStatus(currTask.getUploadStatus())
-
.setFileName(currTask.getFileName())//
-
.setUrl(currTask.getUrl())
-
.setId(currTask.getId());
-
-
currTask.setBuilder(builder);
-
-
return currTask;
-
}
-
}
-
FileUtils文件分块类
-
package com.mainaer.wjoklib.okhttp.upload;
-
import java.io.File;
-
import java.io.IOException;
-
import java.io.RandomAccessFile;
-
-
public class FileUtils {
-
-
-
public static byte[] getBlock(long offset, File file, int blockSize) {
-
byte[] result = new byte[blockSize];
-
RandomAccessFile accessFile = null;
-
try {
-
accessFile = new RandomAccessFile(file, "r");
-
accessFile.seek(offset);
-
int readSize = accessFile.read(result);
-
if (readSize == -1) {
-
return null;
-
} else if (readSize == blockSize) {
-
return result;
-
} else {
-
byte[] tmpByte = new byte[readSize];
-
System.arraycopy(result, 0, tmpByte, 0, readSize);
-
return tmpByte;
-
}
-
-
} catch (IOException e) {
-
e.printStackTrace();
-
} finally {
-
if (accessFile != null) {
-
try {
-
accessFile.close();
-
} catch (IOException e1) {
-
}
-
}
-
}
-
return null;
-
}
-
}
UploadTaskListener接口类管理类
-
package com.mainaer.wjoklib.okhttp.upload;
-
-
import com.mainaer.wjoklib.okhttp.download.DownloadStatus;
-
-
import java.io.File;
-
-
/**
-
* Created by hst on 16/9/21.
-
*/
-
public interface UploadTaskListener {
-
/**
-
* 上传中
-
*
-
* @param percent
-
* @param uploadTask
-
*/
-
void onUploading(UploadTask uploadTask, String percent,int position)
-
-
/**
-
* 上传成功
-
*
-
* @param file
-
* @param uploadTask
-
*/
-
void onUploadSuccess(UploadTask uploadTask, File file);
-
-
/**
-
* 上传失败
-
*
-
* @param uploadTask
-
* @param errorCode {@link DownloadStatus}
-
*/
-
void onError(UploadTask uploadTask, int errorCode,int position);
-
-
/**
-
* 上传暂停
-
*
-
* @param uploadTask
-
*
-
*/
-
void onPause(UploadTask uploadTask);
-
}
-
android源码地址:https://github.com/handsometong/okhttpUpLoader