android实现大文件断点上传

来源:互联网 发布:如何做网络推广员 编辑:程序博客网 时间:2024/04/30 18:11

前言

 之前项目需要上传大文件的功能,上传大文件经常遇到上传一半由于网络或者其他一些原因上传失败。然后又得重新上传(很麻烦),所以就想能不能做个断点上传的功能。于是网上搜索,发现市面上很少有断点上传的案例,有找到一个案例也是采用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;@Overridepublic 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();}}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// TODO Auto-generated method stubString 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 blocke.printStackTrace();}}}

接口2

@WebServlet(urlPatterns = { "/uploaderWithContinuinglyTransferring" })public class UploaderServletWithContinuinglyTransferring extends HttpServlet {private static final long serialVersionUID = 1L;private FileUploadStatusServiceI statusService;String repositoryPath;String uploadPath;@Overridepublic 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
0 0