在Java中恢复HTTP文件下载

61
URL url = new URL("http://download.thinkbroadband.com/20MB.zip");

URLConnection connection = url.openConnection();
File fileThatExists = new File(path); 
OutputStream output = new FileOutputStream(path, true);
connection.setRequestProperty("Range", "bytes=" + fileThatExists.length() + "-");

connection.connect();

int lenghtOfFile = connection.getContentLength();

InputStream input = new BufferedInputStream(url.openStream());
byte data[] = new byte[1024];

long total = 0;

while ((count = input.read(data)) != -1) {
    total += count;

    output.write(data, 0 , count);
}

在这段代码中,我尝试恢复下载。目标文件大小为20MB。但是当我在10MB处停止下载,然后继续下载,我得到的文件大小为30MB。看起来它会继续写入文件,但无法从服务器部分下载。Wget -c可以很好地处理该文件。如何恢复文件下载?


2
可能是重复的问题:如何恢复中断的下载 - 第2部分 - BalusC
7个回答

61
 HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    if(ISSUE_DOWNLOAD_STATUS.intValue()==ECMConstant.ECM_DOWNLOADING){
        File file=new File(DESTINATION_PATH);
        if(file.exists()){
             downloaded = (int) file.length();
             connection.setRequestProperty("Range", "bytes="+(file.length())+"-");
        }
    }else{
        connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
    }
    connection.setDoInput(true);
    connection.setDoOutput(true);
    progressBar.setMax(connection.getContentLength());
     in = new BufferedInputStream(connection.getInputStream());
     fos=(downloaded==0)? new FileOutputStream(DESTINATION_PATH): new FileOutputStream(DESTINATION_PATH,true);
     bout = new BufferedOutputStream(fos, 1024);
    byte[] data = new byte[1024];
    int x = 0;
    while ((x = in.read(data, 0, 1024)) >= 0) {
        bout.write(data, 0, x);
         downloaded += x;
         progressBar.setProgress(downloaded);
    }

这不是我的代码,但它能够正常工作。


8
您好,'ISSUE_DOWNLOAD_STATUS.intValue()',我不太理解这个值,请给我提供相关的参考资料。 - DynamicMind
1
我预计这段代码不会很好地工作。如果if(ISSUE_DOWNLOAD_STATUS.intValue()==ECMConstant.ECM_DOWNLOADING)的条件失败,那么将执行else{}语句,其中“downloaded”变量未定义。 - asiby
4
假设我们都会写一点代码,这段代码给你提供了解决问题的提示而不是完全的解决方案。 - Ceetn
17
嘿,@Ceetn,如果你不能提供任何有建设性的帮助,请闭嘴。这里大多数人都在努力提供功能性的解决方案。这个代码看起来像是从某个地方借鉴来的,但缺少变量声明。我尝试使用它,花了几分钟时间才理解。ISSUE_DOWNLOAD_STATUS和ECMConstant是什么?也许对你来说很清楚,但你完全错误地认为在寻求帮助的人都能编程。如果你的老师在编程课上像这样做,你会有不同的反应。 - asiby
4
我不明白为什么这个答案得到更多的赞同并被接受。有很多没有解释清楚的事情,引用了外部内容。常量没有值。下载属性的值设置在if分支中,却在else分支中使用。if和else分支是一样的。为什么将setDoOutput设置为true呢?有任何POST传递吗?3个地方都使用相同的缓冲区大小,没有使用常量。完全没有错误处理。 - HoGo
显示剩余5条评论

19

我猜测你遇到的问题是在调用url.openConnection()之后又调用了url.openStream()

url.openStream()等价于url.openConnection().getInputStream()。因此,你实际上请求了两次该URL。特别是第二次,它没有指定范围属性。因此下载始终从开头开始。

你应该将url.openStream()替换为connection.getInputStream()


非常感谢你,真的帮了我大忙!我遇到了类似的问题,你的解决方案非常完美。谢谢! - mohamede1945
我已经搜索了一个小时来解决我的问题,而您,先生,您让我感到非常开心并解决了它!非常感谢! - mehrmoudi

7
这是我使用的以块为单位下载文件并更新进度的界面。
/*
 * @param callback = To update the UI with appropriate action
 * @param fileName = Name of the file by which downloaded file will be saved.
 * @param downloadURL = File downloading URL
 * @param filePath = Path where file will be saved
 * @param object = Any object you want in return after download is completed to do certain operations like insert in DB or show toast
 */

public void startDownload(final IDownloadCallback callback, String fileName, String downloadURL, String filePath, Object object) {
    callback.onPreExecute(); // Callback to tell that the downloading is going to start
    int count = 0;
    File outputFile = null; // Path where file will be downloaded
    try {
        File file = new File(filePath);
        file.mkdirs();
        long range = 0;
        outputFile = new File(file, fileName);
        /**
         * Check whether the file exists or not
         * If file doesn't exists then create the new file and range will be zero.
         * But if file exists then get the length of file which will be the starting range,
         * from where the file will be downloaded
         */
        if (!outputFile.exists()) {
            outputFile.createNewFile();
            range = 0;
        } else {
            range = outputFile.length();
        }
        //Open the Connection
        URL url = new URL(downloadURL);
        URLConnection con = url.openConnection();
        // Set the range parameter in header and give the range from where you want to start the downloading
        con.setRequestProperty("Range", "bytes=" + range + "-");
        /**
         * The total length of file will be the total content length given by the server + range.
         * Example: Suppose you have a file whose size is 1MB and you had already downloaded 500KB of it.
         * Then you will pass in Header as "Range":"bytes=500000".
         * Now the con.getContentLength() will be 500KB and range will be 500KB.
         * So by adding the two you will get the total length of file which will be 1 MB
         */
        final long lenghtOfFile = (int) con.getContentLength() + range;

        FileOutputStream fileOutputStream = new FileOutputStream(outputFile, true);
        InputStream inputStream = con.getInputStream();

        byte[] buffer = new byte[1024];

        long total = range;
        /**
         * Download the save the content into file
         */
        while ((count = inputStream.read(buffer)) != -1) {
            total += count;
            int progress = (int) (total * 100 / lenghtOfFile);
            EntityDownloadProgress entityDownloadProgress = new EntityDownloadProgress();
            entityDownloadProgress.setProgress(progress);
            entityDownloadProgress.setDownloadedSize(total);
            entityDownloadProgress.setFileSize(lenghtOfFile);
            callback.showProgress(entityDownloadProgress);
            fileOutputStream.write(buffer, 0, count);
        }
        //Close the outputstream
        fileOutputStream.close();
        // Disconnect the Connection
        if (con instanceof HttpsURLConnection) {
            ((HttpsURLConnection) con).disconnect();
        } else if (con instanceof HttpURLConnection) {
            ((HttpURLConnection) con).disconnect();
        }
        inputStream.close();
        /**
         * If file size is equal then return callback as success with downlaoded filepath and the object
         * else return failure
         */
        if (lenghtOfFile == outputFile.length()) {
            callback.onSuccess(outputFile.getAbsolutePath(), object);
        } else {
            callback.onFailure(object);
        }
    } catch (Exception e) {
        e.printStackTrace();
        callback.onFailure(object);
    }
}

接口 IDownloadCallback {

    void onPreExecute(); // Callback to tell that the downloading is going to start
    void onFailure(Object o); // Failed to download file
    void onSuccess(String path, Object o); // Downloaded file successfully with downloaded path
    void showProgress(EntityDownloadProgress entityDownloadProgress); // Show progress
}

公共类EntityDownloadProgress {

    int progress; // range from 1-100
    long fileSize;// Total size of file to be downlaoded
    long downloadedSize; // Size of the downlaoded file

    public void setProgress(int progress) {this.progress = progress;}

    public void setFileSize(long fileSize) {this.fileSize = fileSize;}

    public void setDownloadedSize(long downloadedSize) {this.downloadedSize = downloadedSize;}
}

这应该是被接受的答案,因为它完美地处理了进度条的变化。 - Kartik

3

请查看此帖子,其中有一个与您类似的问题。如果wget正常工作,则服务器明显支持恢复下载。看起来您没有设置If-Range头,如上面链接中的已接受答案所述。即添加:

// Initial download.
String lastModified = connection.getHeaderField("Last-Modified");

// ...

// Resume download.
connection.setRequestProperty("If-Range", lastModified); 

使用您的代码后,我的应用程序无法下载任何内容。lastmodified字符串包含有效日期(2008年5月14日)。可能是什么问题? - POMATu

3

这个怎么样?

public static void download(DownloadObject object) throws IOException{
    String downloadUrl = object.getDownloadUrl();
    String downloadPath = object.getDownloadPath();
    long downloadedLength = 0;

    File file = new File(downloadPath);
    URL url = new URL(downloadUrl);

    BufferedInputStream inputStream = null;
    BufferedOutputStream outputStream = null;

    URLConnection connection = url.openConnection();

    if(file.exists()){
        downloadedLength = file.length();
        connection.setRequestProperty("Range", "bytes=" + downloadedLength + "-");
        outputStream = new BufferedOutputStream(new FileOutputStream(file, true));

    }else{
        outputStream = new BufferedOutputStream(new FileOutputStream(file));

    }

    connection.connect();

    inputStream = new BufferedInputStream(connection.getInputStream());


    byte[] buffer = new byte[1024*8];
    int byteCount;

    while ((byteCount = inputStream.read(buffer)) != -1){
        outputStream.write(buffer, 0, byteCount);
        break;

    }

    inputStream.close();
    outputStream.flush();
    outputStream.close();

}

使用 break; 来测试代码.. ;)


2
除了提供一些代码之外,请添加一些文本来解释为什么您的代码有效。 - buczek
@buczek 这是标准的下载文件代码,除了connect.setRequestProperty方法和FileOutputStream的append属性设置为true。 - Valeriy K.
请在Postman中检查请求,您可能需要添加一个检查,以确保服务器是否完整或部分地响应。我不得不添加:connection.setRequestProperty("Accept-Encoding", "") - Sergey Shamanayev

3
我有一个方法可以让你的代码正常运行。
  1. First, check if the file exits or not
  2. If the file exists, set the connection:

    connection.setRequestProperty("Range", "bytes=" + bytedownloaded + "-");
    
  3. If file does not exist, do the same download in a new file.


1

由于问题标记为Android: 您尝试过使用DownloadManager吗? 它可以很好地为您处理所有这些内容。


谢谢,我会切换到它,下载应该会更加稳定! - POMATu
请记住,它需要API级别9(Android 2.3)。我不知道这是否会有问题。2.2已经基本淘汰了。 - koljaTM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接