首頁 > 軟體

Android實現斷點續傳功能

2022-07-27 14:01:22

本文範例為大家分享了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裡處理上傳結果,我這裡有多個檔案連續上傳,所以結果處理看起來有點凌亂。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com