本文实例为大家分享了Android实现断点续传的具体代码,供大家参考,具体内容如下
断点续传功能,在文件上传中断时,下次上传同一文件时,能在上次的断点处继续上传,可节省时间和流量
总结规划步骤:
1.给大文件分片,每一个大文件发送前,相对应的创建一个文件夹存放所有分片
2.上传接口,每一个分片上传完成就删掉,直到所有分片上传完成,再删掉存放分片的文件夹,服务器把分片合成完整的文件。
先看分片功能,传入的3个参数分别为源文件地址,分片大小,存放分片的文件夹地址。返回的是分片个数。
/** * * @param sourceFilePath 源文件地址 * @param partFileLength 分割文件的每一个片段大小标准 * @param splitPath 分割之后片段所在文件夹 * @return * @throws Exception */ public static int splitFile(String sourceFilePath, int partFileLength, String splitPath) throws Exception { File sourceFile = null; File targetFile = null; InputStream ips = null; OutputStream ops = null; OutputStream configOps = null;//该文件流用于存储文件分割后的相关信息,包括分割后的每个子文件的编号和路径,以及未分割前文件名 Properties partInfo = null;//properties用于存储文件分割的信息 byte[] buffer = null; int partNumber = 1; sourceFile = new File(sourceFilePath);//待分割文件 ips = new FileInputStream(sourceFile);//找到读取源文件并获取输入流 //创建一个存放分片的文件夹 File tempFile = new File(splitPath); if (!tempFile.exists()) { tempFile.mkdirs(); } configOps = new FileOutputStream(new File(tempFile.getAbsolutePath() + File.separator + "config.properties")); buffer = new byte[partFileLength];//开辟缓存空间 int tempLength = 0; partInfo = new Properties();//key:1开始自动编号 value:文件路径 int sliceCount = 0; while ((tempLength = ips.read(buffer, 0, partFileLength)) != -1) { String targetFilePath = tempFile.getAbsolutePath() + File.separator + "part_" + (partNumber);//分割后的文件路径+文件名 sliceCount = partNumber; partInfo.setProperty((partNumber++) + "", targetFilePath);//将相关信息存储进properties targetFile = new File(targetFilePath); ops = new FileOutputStream(targetFile);//分割后文件 ops.write(buffer, 0, tempLength);//将信息写入碎片文件 ops.close();//关闭碎片文件 } partInfo.setProperty("name", sourceFile.getName());//存储源文件名 partInfo.setProperty("sliceCount", sliceCount + "");//存储分片个数 partInfo.store(configOps, "ConfigFile");//将properties存储进实体文件中 ips.close();//关闭源文件流 return sliceCount; }
接下来,和服务器协商接口,一共2个接口
1.uploadLargeFilePre,传入参数除了常规上传文件参数之外加了分片个数sliceCount和fileHashcode。这一步是给服务器开辟存放分片的控件的,不执行上传操作,文件哈希值作为识别分片归属的唯一标准。返回int类型rCode,根据rCode结果判断是否执行第2步上传动作
2.uploadLargeFile,传入参数除了常规上传文件参数之外加了分片path,分片index,isFinished。isFinished是最后一片的依据。客户端就在上传接口的response里继续上传下一个分片,直到最后一片上传完成,服务器才会返给这个大文件的url。
看代码
protected void addFileMsg(String path) { forceLogin(); if (path == null) { return; } File file = new File(path); if (file.exists()) { ImMsg msg = new ImMsg(); msg.setType(ImMsg.MSG_TYPE_FILE); msg.setTime(System.currentTimeMillis()); msg.setMsgReaded(); msg.setReceiveTime(System.currentTimeMillis()); AccountInfo accountInfo = IMApp.instance().getAccountInfo(); msg.setUserId(accountInfo.getUserId()); msg.setSex(accountInfo.getSex()); msg.setUserName(accountInfo.mNickName); msg.setHeadUrl(accountInfo.mHead); msg.mMasterId = mMasterId; msg.setMsg(new File(path).getName()); msg.setPicPath(path); msg.setState(ImMsg.STATE_SEND_UPLOADING); String ext = null; if (path.endsWith("doc") || path.endsWith("docx")) { ext = "doc"; } else if (path.endsWith("xls") || path.endsWith("xlsx")) { ext = "xls"; } else if (path.endsWith("ppt") || path.endsWith("pptx")) { ext = "ppt"; } else if (path.endsWith("pdf")) { ext = "pdf"; } msg.setMsg_extend3(ext); msg.mFileSize = (int) new File(path).length(); msg.setHasAtt(hasAtt()); addMsgToDB(msg); isFileListSingle = fileList.size() == 1; SPHelper spHelper = SPHelper.build(); int lastSlices = spHelper.get(slice_old, 0); String aesPath = spHelper.get(slice_aesEncPath, ""); String lastPath = spHelper.get(slice_picPath, ""); int tempTotalCount = spHelper.get(CommonUtils.FAILED_TEMP_SEND_TOTAL_COUNT, 0);//实际发送总个数 if (lastSlices == 1 && tempTotalCount > 1) { /** * 读取上次失败文件的进度,如果还剩一个失败文件,但是上次发送的总文件个数大于1, * 则fileList.size() == 1这个判断不能用,在所有判断处加一个标志 */ isFileListSingle = false; } //根据文件大小,判断是否用分片上传 modify hexiaokang 2019年11月5日11点01分 if (new File(path).length() < Global.FILE_SPILT_LENGTH) {//文件小于5M,常规上传方式 upLoadFile(msg); } else { File sourceFile = new File(path); //分片所在文件夹,以未做AES加密的源文件名作为 分片文件夹名字 String sliceDir = sourceFile.getParent() + File.separator + sourceFile.getName() + "_split"; if (lastSlices == 1 && path.equals(lastPath) && new File(sliceDir).exists() && !aesPath.equals("") && new File(aesPath).exists()) {//发送上次的旧文件中断的分片 //只走一次 spHelper.put(slice_old, 0); sliceIndex = spHelper.get(slice_sliceIndex, 0); int count = spHelper.get(slice_sliceCount, 0); LogUtil2.i(TAG + "&onUploadComplete", "sendOldFiles sliceIndex = " + sliceIndex + ", sliceCount = " + count); largeFilePre = true; upLoadLargeFilePre(msg, count, sliceDir, aesPath); } else {//发送大文件正常流程 //给文件分片 int partFileLength = Global.FILE_SPILT_LENGTH;//指定分割的子文件大小为5M //先对文件做AES加密,再进行分片,这里很重要 String aesEncPath = EncryptMgr.encFile(path, accountInfo.getEncTime(), accountInfo.getEncKey()); File f = new File(aesEncPath); LogUtil2.i(TAG + "&onUploadComplete", "ChatMsgActivity.addFileMsg: big file enc path=" + path); try { sliceCount = FileSliceUtil.splitFile(aesEncPath, partFileLength, sliceDir);//将文件分割 } catch (Exception e) { LogUtil2.e(TAG, "split.e:" + e.getMessage()); e.printStackTrace(); } // LogUtil2.e(TAG+"&onUploadComplete", "ChatMsgActivity.addFileMsg: sliceCount:" + sliceCount); //分片上传 largeFilePre = false; upLoadLargeFilePre(msg, sliceCount, sliceDir, aesEncPath); } } } }
上面这一块代码,除了小文件常规上传,就是大文件上传方式了。大文件上传这里分了2种情况,
1.发送上次中断的大文件分片,这里就不用分片了,直接从sp拿到上次中断的分片count、index等信息直接上传
2.从分片到发送的全部流程,因为我这里对文件做了AES加密,所以是加密之后再分片
接下来就是上传过程
public void upLoadLargeFilePre(final ImMsg msg, final int sliceCount, final String sliceDir, final String aesEncPath) { if (msg.getState() != ImMsg.STATE_SEND_WAITING) { msg.setState(ImMsg.STATE_SEND_UPLOADING); mListAdapter.notifyDataSetChanged(); } AccountInfo accountInfo = IMApp.instance().getAccountInfo(); UploadFileHelper helper = new UploadFileHelper(accountInfo.getEncTime(), accountInfo.getEncKey(), new UploadFileCallback() { @Override public void onError(Call call, Exception e) { e.printStackTrace(); LogUtil2.e("onUploadComplete", "ChatMsgActivity.onError: error(split_upload):" + e.toString()+", call = "+call); failCount++; // if (msg.getPicPath() != null && msg.getPicPath().contains(SDCardUtil.getSDcardPathEx())) { // new File(msg.getPicPath()).delete(); // } btn_other_sendfile.setClickable(true); CommonUtils.isSendBtnClickable = true; Intent comIntent = new Intent(); comIntent.setAction(CommonUtils.USBFILE_COMPLETE_SEND); comIntent.putExtra("fail_count", failCount); sendBroadcast(comIntent); tempProgress = 0; largeFilePre = false; //todo 上传失败,上传任务终止 (需要保存失败msg的信息,以及分片信息) String picPath = msg.getPicPath(); // 保存picPath, sliceIndex, sliceCount, aesEncPath SPHelper spHelper = SPHelper.build(); spHelper.put(slice_picPath, picPath); spHelper.put(slice_sliceIndex, sliceIndex); spHelper.put(slice_sliceCount, sliceCount); spHelper.put(slice_aesEncPath, aesEncPath); spHelper.put(slice_old, 1);//标记,1表示有上次失败的碎片,处理完上次失败的碎片之后设置为0 return; } @Override public void onResponse(UploadFileAckPacket uploadFileAckPacket) { LogUtil2.i("onUploadComplete", "ChatMsgActivity.onResponse: pre upload ack packet code="+uploadFileAckPacket.getRetCode()+", desc="+uploadFileAckPacket.getRetDesc()); if (getMsgFromDB(msg.getId()) == null) { msg.setState(ImMsg.STATE_SEND_FAILED); LogUtil2.i("onUploadComplete", "msg not exist d = (split_upload)" + msg.getId()); updateMsgToDB(msg); mListAdapter.notifyDataSetChanged(); failCount++; btn_other_sendfile.setClickable(true); CommonUtils.isSendBtnClickable = true; Intent comIntent = new Intent(); comIntent.setAction(CommonUtils.USBFILE_COMPLETE_SEND); sendBroadcast(comIntent); tempProgress = 0; return; } if (uploadFileAckPacket != null && uploadFileAckPacket.isSuccess()) { LogUtil2.i("onUploadComplete", "msg exist and sucess(uploadFileAckPacket.sliceIndex(split_upload)):" + uploadFileAckPacket.sliceIndex+", sliceCount="+sliceCount+", url="+uploadFileAckPacket.mUrl); if (sliceIndex < sliceCount && TextUtils.isEmpty(uploadFileAckPacket.mUrl)) { //更新进度条 if (isFileListSingle) {//单个大文件 int pro = 100 * sliceIndex / sliceCount; Intent uploadIntent = new Intent(); uploadIntent.setAction(CommonUtils.USBFILE_PROGRESS_SEND); uploadIntent.putExtra("sentCount", -1);//-1 代表发送的是单个文件 uploadIntent.putExtra("sentPro", pro); sendBroadcast(uploadIntent); } else {//一次发送多个文件,包括大文件 } //删除掉上传成功的分片 String slicePath = sliceDir + File.separator + "part_" + (sliceIndex); new File(slicePath).delete(); sliceIndex++; //还有待上传的分片 upLoadLargeFilePre(msg, sliceCount, sliceDir, aesEncPath); } else { //所有分片上传完成 largeFilePre = false; LogUtil2.i("onUploadComplete", "ChatMsgActivity.onResponse: 所有分片上传完成 largeFilePre="+largeFilePre); msg.updateImageUpLoadProgress(100); msg.setPicBigUrl(uploadFileAckPacket.mUrl); //这里删除原文件 if (msg.getPicPath() != null && msg.getPicPath().contains(SDCardUtil.getSDcardPathEx())) { File file = new File(msg.getPicPath()); //删除分片所在的文件夹 FileUtil.deleteFolderFile(sliceDir, true); //删除文件 file.delete(); // EventBus.getDefault().post(new EncFileEvent(EncFileEvent.COM_FILE_DELETE, msg.getPicPath(), 0)); msg.setPicPath(""); } if (msg.getType() == ImMsg.MSG_TYPE_IMAGE) { msg.setPicSmallUrl(uploadFileAckPacket.mThumbnail); msg.mThumbWidth = this.mThumbWidth; msg.mThumbHeight = this.mThumbHeight; } LogUtil2.i("onUploadComplete", "msg exist and sucess(msg.getPicBigUrl()(split_upload)):" + msg.getPicBigUrl()); sendMsg(msg); sentCount++; sliceIndex = 1; if (isFileListSingle) {//单个文件不用处理 } else { //传递上传进度 Intent uploadIntent = new Intent(); uploadIntent.setAction(CommonUtils.USBFILE_PROGRESS_SEND); uploadIntent.putExtra("sentCount", sentCount); sendBroadcast(uploadIntent); } //继续上传下一个文件 if (sentCount < fileList.size()) { addFileMsg(fileList.get(sentCount).getAbsolutePath()); } else { //所有文件上传完成 btn_other_sendfile.setClickable(true); CommonUtils.isSendBtnClickable = true; Intent comIntent = new Intent(); comIntent.setAction(CommonUtils.USBFILE_COMPLETE_SEND); sendBroadcast(comIntent); tempProgress = 0; } } } else { LogUtil2.e("onUploadComplete", "rCode(split_upload)):" + uploadFileAckPacket.getRetCode()+", sliceIndex="+uploadFileAckPacket.sliceIndex); if(uploadFileAckPacket.getRetCode()==1343688774){ LogUtil2.e("onUploadComplete", "ChatMsgActivity.onResponse: 缺片了!"); } msg.setState(ImMsg.STATE_SEND_FAILED); updateMsgToDB(msg); if (!IMMsgMgr.notifyActivity(IMMsgMgr.IM_CMD_IMP2PMSG_ACK, msg.getUserId(), msg.hasAtt(), false, msg)) { } mListAdapter.notifyDataSetChanged(); } } @Override public void inProgress(float progress) { super.inProgress(progress); // LogUtil2.i("onUploadProgress", "inProgress " + progress+", path="+msg.getPicPath()+", sliceDir="+sliceDir); LogUtil2.i("onUploadProgress", "ChatMsgActivity.inProgress: sliceCount="+sliceCount+", sliceIndex="+sliceIndex+", sentCount="+sentCount+", list.size="+fileList.size()+", progress="+progress+", tempProgress="+tempProgress); int pro = (int) (progress * 100); if (new File(msg.getPicPath()).length() < Global.FILE_SPILT_LENGTH) { // }else{ int allPro= (int)(100*(progress+(sliceIndex-1))/sliceCount); if (isFileListSingle && allPro - tempProgress >= 1) { //todo 分片上传,进度条重新计算, //多个文件上传不用考虑,这里重新计算单个文件的上传问题 LogUtil2.i("onUploadProgress", "ChatMsgActivity.inProgress: big file allPro="+allPro); Intent uploadIntent = new Intent(); uploadIntent.setAction(CommonUtils.USBFILE_PROGRESS_SEND); uploadIntent.putExtra("sentCount", -1);//-1代表发送的是单个文件,这里是针对用户感知而言的代码逻辑 uploadIntent.putExtra("sentPro", allPro); tempProgress = allPro; sendBroadcast(uploadIntent); } } // 更新 mListAdapter.notifyDataSetChanged(); } }, msg); LogUtil2.i("onUploadComplete", "ChatMsgActivity.upLoadLargeFilePre: largeFilePre="+largeFilePre); if (largeFilePre) {//todo 是否有过预处理 String slicePath = sliceDir + File.separator + "part_" + (sliceIndex); int isFinished = sliceIndex == sliceCount ? 1 : 0; helper.upLoadLargeFile(aesEncPath, slicePath, sliceIndex, isFinished); } else { //上传大文件 预处理 int rCode = helper.upLoadLargeFilePre(aesEncPath, sliceCount); if (rCode == 0) {//预处理成功,可以执行上传任务 largeFilePre = true; String slicePath = sliceDir + File.separator + "part_" + (sliceIndex); int isFinished = sliceIndex == sliceCount ? 1 : 0; helper.upLoadLargeFile(aesEncPath, slicePath, sliceIndex, isFinished); } else { //预处理失败,不执行上传任务 LogUtil2.i("onUploadComplete", "somewordsrCode:" + rCode+", before plus faileCount="+failCount); msg.setState(ImMsg.STATE_SEND_FAILED); updateMsgToDB(msg); mListAdapter.notifyDataSetChanged(); failCount++; btn_other_sendfile.setClickable(true); CommonUtils.isSendBtnClickable = true; Intent comIntent = new Intent(); comIntent.putExtra("upload_error", 0); comIntent.putExtra("fail_count",failCount); comIntent.setAction(CommonUtils.USBFILE_COMPLETE_SEND); LogUtil.LogShow("onUploadComplete", "ChatMsgActivity.upLoadLargeFilePre: before sendIntent failCount="+failCount); sendBroadcast(comIntent); failCount=0; tempProgress = 0; } } }
上面这一块就是上传过程
int rCode = helper.upLoadLargeFilePre(aesEncPath, sliceCount);
这一句为第一个接口预处理,根据结果决定是否执行上传。
helper.upLoadLargeFile(aesEncPath, slicePath, sliceIndex, isFinished);
这一句执行上传,在onResponse里处理上传结果,我这里有多个文件连续上传,所以结果处理看起来有点凌乱。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。