草庐IT

java - Android:使用 HttpURLConnection 中的进度回调在 Cloudinary 中上传照片

coder 2023-11-24 原文

我正在尝试修改cloudinary的开源库,以便可以监听我的照片上传进度。库类包含一个 MultipartUtility java 类,我对其进行修改以监听上传进度。

修改前的原始代码可以在github上找到:https://github.com/cloudinary/cloudinary_java/blob/master/cloudinary-android/src/main/java/com/cloudinary/android/MultipartUtility.java

我从字面上修改了它,使其类似于另一个支持上传文件/图像等的进度的云服务 CloudFS 的代码:

https://github.com/bitcasa/CloudFS-Android/blob/master/app/src/main/java/com/bitcasa/cloudfs/api/MultipartUpload.java

package com.cloudinary.android;

import com.cloudinary.Cloudinary;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

/**
 * This utility class provides an abstraction layer for sending multipart HTTP
 * POST requests to a web server.
 *
 * @author www.codejava.net
 * @author Cloudinary
 */
public class MultipartUtility {
    private final String boundary;
    private static final String LINE_FEED = "\r\n";
    private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
    private HttpURLConnection httpConn;
    private String charset;
    private OutputStream outputStream;
    private PrintWriter writer;
    UploadingCallback uploadingCallback;
    public final static String USER_AGENT = "CloudinaryAndroid/" + Cloudinary.VERSION;
    Long filesize;

    public void setUploadingCallback(UploadingCallback uploadingCallback) {
        this.uploadingCallback = uploadingCallback;
    }

    /**
     * This constructor initializes a new HTTP POST request with content type is
     * set to multipart/form-data
     *
     * @param requestURL
     * @param charset
     * @throws IOException
     */
    public MultipartUtility(String requestURL, String charset, String boundary, Map<String, String> headers, Long filesize) throws IOException {
        this.charset = charset;
        this.boundary = boundary;
        this.filesize = filesize;
        URL url = new URL(requestURL);
        httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setDoOutput(true); // indicates POST method
        httpConn.setDoInput(true);
        httpConn.setFixedLengthStreamingMode(filesize); //added this in

        if (headers != null) {
            for (Map.Entry<String, String> header : headers.entrySet()) {
                httpConn.setRequestProperty(header.getKey(), header.getValue());
            }
        }
        httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
        httpConn.setRequestProperty("User-Agent", USER_AGENT);
        outputStream = httpConn.getOutputStream();
        writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), true);
    }

    public MultipartUtility(String requestURL, String charset, String boundary) throws IOException {
        this(requestURL, charset, boundary, null, 0L);
    }

    /**
     * Adds a form field to the request
     *
     * @param name  field name
     * @param value field value
     */
    public void addFormField(String name, String value) {
        writer.append("--" + boundary).append(LINE_FEED);
        writer.append("Content-Disposition: form-data; name=\"" + name + "\"").append(LINE_FEED);
        writer.append("Content-Type: text/plain; charset=" + charset).append(LINE_FEED);
        writer.append(LINE_FEED);
        writer.append(value).append(LINE_FEED);
        writer.flush();
    }

    /**
     * Adds a upload file section to the request
     *
     * @param fieldName  name attribute in {@code <input type="file" name="..." />}
     * @param uploadFile a File to be uploaded
     * @throws IOException
     */
    public void addFilePart(String fieldName, File uploadFile, String fileName) throws IOException {
        if (fileName == null) fileName = uploadFile.getName();
        FileInputStream inputStream = new FileInputStream(uploadFile);
        addFilePart(fieldName, inputStream, fileName);
    }

    public void addFilePart(String fieldName, File uploadFile) throws IOException {
        addFilePart(fieldName, uploadFile, "file");
    }

    public void addFilePart(String fieldName, InputStream inputStream, String fileName) throws IOException {
        if (fileName == null) fileName = "file";
        writer.append("--" + boundary).append(LINE_FEED);
        writer.append("Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"" + fileName + "\"").append(LINE_FEED);
        writer.append("Content-Type: ").append(APPLICATION_OCTET_STREAM).append(LINE_FEED);
        writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED);
        writer.append(LINE_FEED);
        writer.flush();

        int progress = 0;
        byte[] buffer = new byte[4096];
        int bytesRead = -1;

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
            progress += bytesRead;
/*            int percentage = ((progress / filesize.intValue()) * 100);*/
            if (uploadingCallback != null) {
                uploadingCallback.uploadListener(progress);
            }

        }
        outputStream.flush();
        writer.flush();
        uploadingCallback = null;
        inputStream.close();
        writer.append(LINE_FEED);
        writer.flush();
    }

    public void addFilePart(String fieldName, InputStream inputStream) throws IOException {
        addFilePart(fieldName, inputStream, "file");
    }

    /**
     * Completes the request and receives response from the server.
     *
     * @return a list of Strings as response in case the server returned status
     * OK, otherwise an exception is thrown.
     * @throws IOException
     */
    public HttpURLConnection execute() throws IOException {
        writer.append("--" + boundary + "--").append(LINE_FEED);
        writer.close();

        return httpConn;
    }

}

我所做的更改是按照此线程的建议将以下内容添加到 httpURLConnection:How to implement file upload progress bar in android : httpConn.setFixedLengthStreamingMode(filesize);

然后我创建了一个简单的界面来监听上传进度:

public interface UploadingCallback {

    void uploadListener(int progress);

}

然后我在 HttpURLConnection 写入照片时附加了它:

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
            progress += bytesRead;
/*            int percentage = ((progress / filesize.intValue()) * 100);*/
            if (uploadingCallback != null) {
                uploadingCallback.uploadListener(progress);
            }

        }

代码已运行,但上传进度似乎未正确测量。照片大约 365kb,上传时间大约为十分之一秒(我在 17:56:55.481 开始上传,到 17:56:55.554 完成,也就是 0.7 多秒)。我不相信我的互联网连接有那么快,预计至少需要 5 秒。我有一种感觉,它正在测量将照片写入缓冲区所花费的时间,而不是将其发送到 cloudinary 的服务器所花费的时间。

如何让它测量上传照片所需的时间,以便我可以将数据用于我的进度条?

04-24 17:56:55.481 28306-28725/com.a  upload  4096
04-24 17:56:55.486 28306-28725/com.a  upload  8192
04-24 17:56:55.486 28306-28725/com.a  upload  12288
04-24 17:56:55.486 28306-28725/com.a  upload  16384
04-24 17:56:55.487 28306-28725/com.a  upload  20480
04-24 17:56:55.487 28306-28725/com.a  upload  24576
04-24 17:56:55.487 28306-28725/com.a  upload  28672
04-24 17:56:55.487 28306-28725/com.a  upload  32768
04-24 17:56:55.491 28306-28725/com.a  upload  36864
04-24 17:56:55.492 28306-28725/com.a  upload  40960
04-24 17:56:55.493 28306-28725/com.a  upload  45056
04-24 17:56:55.493 28306-28725/com.a  upload  49152
04-24 17:56:55.493 28306-28725/com.a  upload  53248
04-24 17:56:55.493 28306-28725/com.a  upload  57344
04-24 17:56:55.494 28306-28725/com.a  upload  61440
04-24 17:56:55.494 28306-28725/com.a  upload  65536
04-24 17:56:55.494 28306-28725/com.a  upload  69632
04-24 17:56:55.494 28306-28725/com.a  upload  73728
04-24 17:56:55.494 28306-28725/com.a  upload  77824
04-24 17:56:55.495 28306-28725/com.a  upload  81920
04-24 17:56:55.495 28306-28725/com.a  upload  86016
04-24 17:56:55.495 28306-28725/com.a  upload  90112
04-24 17:56:55.495 28306-28725/com.a  upload  94208
04-24 17:56:55.495 28306-28725/com.a  upload  98304
04-24 17:56:55.495 28306-28725/com.a  upload  102400
04-24 17:56:55.495 28306-28725/com.a  upload  106496
04-24 17:56:55.496 28306-28725/com.a  upload  110592
04-24 17:56:55.496 28306-28725/com.a  upload  114688
04-24 17:56:55.496 28306-28725/com.a  upload  118784
04-24 17:56:55.497 28306-28725/com.a  upload  122880
04-24 17:56:55.498 28306-28725/com.a  upload  126976
04-24 17:56:55.498 28306-28725/com.a  upload  131072
04-24 17:56:55.498 28306-28725/com.a  upload  135168
04-24 17:56:55.498 28306-28725/com.a  upload  139264
04-24 17:56:55.499 28306-28725/com.a  upload  143360
04-24 17:56:55.506 28306-28725/com.a  upload  147456
04-24 17:56:55.510 28306-28725/com.a  upload  151552
04-24 17:56:55.510 28306-28725/com.a  upload  155648
04-24 17:56:55.514 28306-28725/com.a  upload  159744
04-24 17:56:55.515 28306-28725/com.a  upload  163840
04-24 17:56:55.517 28306-28725/com.a  upload  167936
04-24 17:56:55.517 28306-28725/com.a  upload  172032
04-24 17:56:55.518 28306-28725/com.a  upload  176128
04-24 17:56:55.518 28306-28725/com.a  upload  180224
04-24 17:56:55.518 28306-28725/com.a  upload  184320
04-24 17:56:55.519 28306-28725/com.a  upload  188416
04-24 17:56:55.519 28306-28725/com.a  upload  192512
04-24 17:56:55.519 28306-28725/com.a  upload  196608
04-24 17:56:55.519 28306-28725/com.a  upload  200704
04-24 17:56:55.520 28306-28725/com.a  upload  204800
04-24 17:56:55.525 28306-28725/com.a  upload  208896
04-24 17:56:55.526 28306-28725/com.a  upload  212992
04-24 17:56:55.527 28306-28725/com.a  upload  217088
04-24 17:56:55.530 28306-28725/com.a  upload  221184
04-24 17:56:55.530 28306-28725/com.a  upload  225280
04-24 17:56:55.530 28306-28725/com.a  upload  229376
04-24 17:56:55.530 28306-28725/com.a  upload  233472
04-24 17:56:55.530 28306-28725/com.a  upload  237568
04-24 17:56:55.531 28306-28725/com.a  upload  241664
04-24 17:56:55.532 28306-28725/com.a  upload  245760
04-24 17:56:55.532 28306-28725/com.a  upload  249856
04-24 17:56:55.532 28306-28725/com.a  upload  253952
04-24 17:56:55.533 28306-28725/com.a  upload  258048
04-24 17:56:55.533 28306-28725/com.a  upload  262144
04-24 17:56:55.535 28306-28725/com.a  upload  266240
04-24 17:56:55.540 28306-28725/com.a  upload  270336
04-24 17:56:55.540 28306-28725/com.a  upload  274432
04-24 17:56:55.541 28306-28725/com.a  upload  278528
04-24 17:56:55.541 28306-28725/com.a  upload  282624
04-24 17:56:55.543 28306-28725/com.a  upload  286720
04-24 17:56:55.545 28306-28725/com.a  upload  290816
04-24 17:56:55.545 28306-28725/com.a  upload  294912
04-24 17:56:55.547 28306-28725/com.a  upload  299008
04-24 17:56:55.547 28306-28725/com.a  upload  303104
04-24 17:56:55.547 28306-28725/com.a  upload  307200
04-24 17:56:55.547 28306-28725/com.a  upload  311296
04-24 17:56:55.547 28306-28725/com.a  upload  315392
04-24 17:56:55.548 28306-28725/com.a  upload  319488
04-24 17:56:55.548 28306-28725/com.a  upload  323584
04-24 17:56:55.548 28306-28725/com.a  upload  327680
04-24 17:56:55.548 28306-28725/com.a  upload  331776
04-24 17:56:55.549 28306-28725/com.a  upload  335872
04-24 17:56:55.549 28306-28725/com.a  upload  339968
04-24 17:56:55.549 28306-28725/com.a  upload  344064
04-24 17:56:55.550 28306-28725/com.a  upload  348160
04-24 17:56:55.550 28306-28725/com.a  upload  352256
04-24 17:56:55.551 28306-28725/com.a  upload  356352
04-24 17:56:55.551 28306-28725/com.a  upload  360448
04-24 17:56:55.552 28306-28725/com.a  upload  364544
04-24 17:56:55.554 28306-28725/com.a  upload  365790

要亲自测试,您需要在 cloudinary 网站上创建一个免费帐户以获取您的 cloudname,这样您就可以将您的 Android SDK 连接到他们的服务,以便从android 直接连接到他们的服务器。

编辑:

这是我尝试过的,当上传实际在 7 秒内完成时,它仍然在 0.7 秒内从 0 跳到 100%:

    while ((bytesRead = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, bytesRead);
        progress += bytesRead;
        Log.d("MultiPart", "file transferred so far: "
                + progress);
        if (uploadingCallback != null) {
            uploadingCallback.uploadListener(progress);
        }
        Log.d("Flushing", "flush the writer");
        outputStream.flush();
        writer.flush();
    }

最佳答案

使用flush()方法和调用update callback()的时间有问题

从你的代码可以看出,每次读取图片的一部分时,你都将它写入输出缓冲区,但这并不意味着它已发送到服务器,它可能会被缓冲,然后再写入'n到服务器。

您有两个选择,要么在每个 outputStream.write() 之后调用 outputStream.flush(),但这会降低上传性能,因为您将失去缓冲的好处。

或者您可以在方法末尾的 outputStream.flush() 之后调用 updateCallback()。因为在 outputStream.flush() 之后你确定数据在服务器上了,那个进度就结束了。

有关冲洗的更多信息,请参阅此线程 What is the purpose of flush() in Java streams?

关于java - Android:使用 HttpURLConnection 中的进度回调在 Cloudinary 中上传照片,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36826326/

有关java - Android:使用 HttpURLConnection 中的进度回调在 Cloudinary 中上传照片的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  5. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  6. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  7. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  8. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  9. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  10. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

随机推荐