android中okhttp实现断点上传示例
程序员文章站
2023-12-20 15:59:16
前言
之前项目需要上传大文件的功能,上传大文件经常遇到上传一半由于网络或者其他一些原因上传失败。然后又得重新上传(很麻烦),所以就想能不能做个断点上传的功能。于是网上...
前言
之前项目需要上传大文件的功能,上传大文件经常遇到上传一半由于网络或者其他一些原因上传失败。然后又得重新上传(很麻烦),所以就想能不能做个断点上传的功能。于是网上搜索,发现市面上很少有断点上传的案例,有找到一个案例也是采用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); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: Java逃逸分析详解及代码示例
推荐阅读
-
Android中FTP上传、下载的功能实现(含进度)
-
php+resumablejs实现的分块上传 断点续传功能示例
-
Android 8.0 中如何实现视频通话的画中画模式的示例
-
php+resumablejs实现的分块上传 断点续传功能示例
-
Android实现动态向Gallery中添加图片及倒影与3D效果示例
-
Android实现TCP断点上传 后台C#服务接收
-
Android实现动态向Gallery中添加图片及倒影与3D效果示例
-
Android实现TCP断点上传 后台C#服务接收
-
使用Android的OkHttp包实现基于HTTP协议的文件上传下载
-
Android的OkHttp包中的HTTP拦截器Interceptor用法示例