【问题标题】:How can I retrieve the size of a file during the downloading from an URL (using a http connection)?在从 URL 下载期间(使用 http 连接)如何检索文件的大小?
【发布时间】:2013-03-05 14:09:38
【问题描述】:

我正在开发一个使用 http 连接下载文件的项目。我在下载过程中显示一个带有进度条状态的水平进度条。 我的函数如下所示:

.......
try {           
        InputStream myInput = urlconnect.getInputStream();
        BufferedInputStream buffinput = new BufferedInputStream(myInput);

        ByteArrayBuffer baf = new ByteArrayBuffer(capacity);
        int current = 0;
        while((current = buffinput.read()) != -1) {
            baf.append((byte) current);
        }
File outputfile = new File(createRepertory(app, 0), Filename);
        FileOutputStream myOutPut = new FileOutputStream(outputfile);
        myOutPut.write(baf.toByteArray());
...
}

我事先知道我的文件的大小,所以我需要在下载期间检索大小(在我的 while 块中)。这样我就可以确定进度条的状态了。

progressBarStatus = ((int) downloadFileHttp(url, app) * 100)/sizefile;

long downloadFileHttp(.., ..) 是我的函数名。

我已经尝试使用 outputfile.length 来检索它,但他的值是“1”,这可能是我尝试下载的文件数。

有什么办法可以解决吗?

更新 1

我没有任何线索可以让我弄清楚这一点。 目前我有一个水平进度条,它只显示 0 和 100% 的中间值。一世 考虑另一种方法。如果我知道我的 wifi 的速率和 文件的大小我可以决定下载的时间。

我知道我可以检索我的 Wifi 的那条信息 连接和我要下载的文件的大小。

有没有人已经工作过或者有过讨论?

【问题讨论】:

  • 由于您是从流中手动读取字节,因此您可以在 while 循环中保留一个计数器,它将复制的字节数相加。让它变得易失,你可以从另一个线程中读取它。你也可以使用 baf.length(),但这不是线程安全的(我相信)。
  • 感谢@AlexanderTorstling。我已经尝试检索 baf 的长度,但是当我尝试返回他的值时,它捕获了一个异常
  • 那有什么用?用于更新 UI 状态?
  • @chintankhetiya 是的,我正在尝试显示带有状态的进度条。

标签: java android http download


【解决方案1】:

我假设您使用的是HttpURLConnection。在这种情况下,您需要在urlconnect 上调用getContentLength() 方法。

但是,服务器不需要发送一个有效的内容长度,所以你应该准备好它是-1。

【讨论】:

  • 任何有价值的服务器都会发送一个 Content-Length 标头。如果它不存在,大量客户端将挂起。
  • @Kylar “物超所值”——这将伴随我很长时间。忍不住笑了。
【解决方案2】:

AsyncTask 可能是您的完美解决方案:

private class DownloadFileTask extends AsyncTask<URL, Integer, Long> {
 protected Long doInBackground(URL... urls) {
    Url url = urls[0];
    //connect to url here
    .......
    try {           
        InputStream myInput = urlconnect.getInputStream();
        BufferedInputStream buffinput = new BufferedInputStream(myInput);

        ByteArrayBuffer baf = new ByteArrayBuffer(capacity);
        int current = 0;
        while((current = buffinput.read()) != -1) {
            baf.append((byte) current);
            //here you can send data to onProgressUpdate
            publishProgress((int) (((float)baf.length()/ (float)sizefile) * 100));
        }
    File outputfile = new File(createRepertory(app, 0), Filename);
    FileOutputStream myOutPut = new FileOutputStream(outputfile);
    myOutPut.write(baf.toByteArray());
    ...
 }

 protected void onProgressUpdate(Integer... progress) {
     //here you can set progress bar in UI thread
     progressBarStatus = progress;
 }

}

在您的方法中在此处启动 AsyncTask 调用

new DownloadFileTask().execute(url);

【讨论】:

  • 我同意@Alex 的回答。这是要走的路。您可以从响应的 Content-Length 标头 (URLConnection.getContentLength()) 中获取“sizeFile”。但是不是一次读取一个字节(buffinput.read()),而是同时读取一堆(buffinput.read(byteArray)),除非您想冒险获得数十万次调用onProgressUpdate。如果内容长度未知,那么就没有进度条。
  • 如果您不知道文件的大小,唯一的选择是将进度条设置为indeterminate
【解决方案3】:

简单。下面的代码:

try {
    URL url = new URL(yourLinkofFile);
    URLConnection conn = url.openConnection();
    conn.connect();
    totalFileSize = conn.getContentLength();
} catch (Exception e) {
    Log.e(TAG, "ERROR: " + e.toString());
}

【讨论】:

    【解决方案4】:

    检查响应中的 Content-Length 标头。它应该被设置。所有主要的 HTTP 服务器都使用此标头。

    【讨论】:

      【解决方案5】:

      在 HTTP 1.1 规范中,chunk 响应数据应该在多轮中被拉回。实际上,块响应中的内容长度为-1,因此我们不能在Inputstream中使用availble方法。顺便说一句,Inputstream.availble 方法仅在 ByteArrayInputStream 中获取内容长度是稳定的。

      如果你只是想得到总长度,你需要在每一轮读取中自己计算它。请参阅 apache commons-io 项目中的 IOUtils 类,如下所示:

      //-----------------------------------------------------------------------
      /**
       * Copy bytes from an <code>InputStream</code> to an
       * <code>OutputStream</code>.
       * <p>
       * This method buffers the input internally, so there is no need to use a
       * <code>BufferedInputStream</code>.
       * <p>
       * Large streams (over 2GB) will return a bytes copied value of
       * <code>-1</code> after the copy has completed since the correct
       * number of bytes cannot be returned as an int. For large streams
       * use the <code>copyLarge(InputStream, OutputStream)</code> method.
       * 
       * @param input  the <code>InputStream</code> to read from
       * @param output  the <code>OutputStream</code> to write to
       * @return the number of bytes copied
       * @throws NullPointerException if the input or output is null
       * @throws IOException if an I/O error occurs
       * @throws ArithmeticException if the byte count is too large
       * @since Commons IO 1.1
       */
      public static int copy(InputStream input, OutputStream output) throws IOException {
          long count = copyLarge(input, output);
          if (count > Integer.MAX_VALUE) {
              return -1;
          }
          return (int) count;
      }
      
      /**
       * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
       * <code>OutputStream</code>.
       * <p>
       * This method buffers the input internally, so there is no need to use a
       * <code>BufferedInputStream</code>.
       * 
       * @param input  the <code>InputStream</code> to read from
       * @param output  the <code>OutputStream</code> to write to
       * @return the number of bytes copied
       * @throws NullPointerException if the input or output is null
       * @throws IOException if an I/O error occurs
       * @since Commons IO 1.3
       */
      public static long copyLarge(InputStream input, OutputStream output)
              throws IOException {
          byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
          long count = 0;
          int n = 0;
          while (-1 != (n = input.read(buffer))) {
              output.write(buffer, 0, n);
              count += n;
          }
          return count;
      }
      

      如果您想检查下载进度,您需要在每个 read 回合中从 InputStream 输入回调到 OutputStream 复制到磁盘过程中的输出。在回调中,您可以复制一条数据并将数量添加到旨在用于您的进度条功能的计数器中。有点复杂

      【讨论】:

        【解决方案6】:

        听起来您的主要问题不是获取文件的长度或找出实际值,而是如何从另一个线程访问当前值以便您可以适当地更新状态栏。

        你有几种方法可以解决这个问题:

        1.) 在您的进度条项目中有一个回调,可让您设置值并在每次更新下载线程中的计数时调用该方法。 2.) 将值放在两个线程都可以访问的某个字段中(可能不是线程安全的)。

        如果是我,在我的进度条项目中,我会有一个方法可以用一些值更新进度。然后我会从正在下载文件的线程中调用该方法。

        所以基本上是用户 --> 点击一些下载按钮 --> 处理程序调用方法开始下载,将回调传递给更新进度条方法 --> 下载线程在每个迭代周期中以更新百分比调用该方法完成。

        【讨论】:

          【解决方案7】:

          我认为你把你的生活弄得太复杂了:)

          首先:由于progressBarStatus = ((int) downloadFileHttp(url, app) * 100)/sizefile; 始终为 0 或 100,因此您可能没有正确计算该值。您没有在此处发布整个方法,但不要忘记您正在处理 int,因此 sizefile 始终为 int,并且划分到更高或等于 sizefile 总是会返回 0 或 1。我怀疑是您需要研究的方向... 此外,我没有在您的代码中看到您在读取中间字节后更新进度条的位置。

          第二:我认为分块阅读会更有效率。读取效率更高,您无需为每个下载的字节通知 UI 线程。 Adamski from this thread 的回答可能会对您有所帮助。只需使用较小的字节数组。我通常使用 256 (3G) 或 512 (Wi-Fi) - 但也许您不需要详细说明。因此,一旦您读取了一个新数组,请计算读取的总字节数,通知 UI 并继续读取直到流结束。

          第三:在下载到 sizeFile 之前设置progressBar.setMax(),根据“First”的注释正确计算下载的字节数,然后使用计算出的数字调用 setProgress。只是不要忘记更新 UI 线程上的进度条。 AsyncTask 有一个很好的机制来帮助你。

          祝你好运!

          【讨论】:

            【解决方案8】:

            这应该能帮到你

            URLConnection connection = servletURL.openConnection();
            BufferedInputStream buff = new BufferedInputStream(connection .getInputStream());
            ObjectInputStream input = new ObjectInputStream(buff );
            int avail = buff .available();
            
            System.out.println("Response content size = " + avail);
            

            【讨论】:

            • 感谢@Sudhakar 的回答!!我读到这个方法返回缓冲区中可用的字节数加上源流中可用的字节数。但它有时会返回一个小于前一个值的值。看起来很奇怪
            • 嗯,我在我工作过的项目中尝试了各种方法,这最符合要求:)
            • 不,这是错误的。正如 Java 文档本身所述,available() 方法很少有用,因为它与可以从流中读取的实际数据量没有真正的关系。
            • 你能指出文档中的哪个位置,上面写着docs.oracle.com/javase/6/docs/api/java/io/…
            • developer.android.com/reference/java/io/… Quote: '请注意,这种方法提供的保证很弱,在实践中并不是很有用。'
            猜你喜欢
            • 2016-02-18
            • 1970-01-01
            • 2014-08-26
            • 1970-01-01
            • 2013-03-04
            • 2012-11-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多