【问题标题】:Android Track progress of Azure CloudBlockBlob uploadAndroid 跟踪 Azure CloudBlockBlob 上传的进度
【发布时间】:2017-09-12 12:12:38
【问题描述】:

如何在调用blob.upload(new FileInputStream(imageFile), imageFile.length()); 后打印已上传的字节数我想记录类似“已上传 100/totalBytes 字节,已上传 224/totalBytes 字节...”这样我可以创建上传进度的进度条。

这是代码:

//AzureBlobLoader 扩展了 AsyncTask

public class AzureBlobUploader extends AzureBlobLoader {
private Activity act;
private String userName;
private TaggedImageObject img;
private Fragment histFragment;

public AzureBlobUploader(Fragment f, Activity act, String userName, TaggedImageObject img) {
    super();
    this.act = act;
    this.userName = userName;
    this.img = img;
    this.histFragment = f;
}


@Override
protected Object doInBackground(Object[] params) {

    File imageFile = new File(this.img.getImgPath());

    try {

        // Define the path to a local file.
        final String filePath = imageFile.getPath();

        // Create or overwrite the blob with contents from the local file.
        String[] imagePathArray = filePath.split("/");
        String imageName = imagePathArray[imagePathArray.length-1];

        System.out.println("Image Name: " + imageName);

        String containerName = userName + "/" + imageName;

        System.out.println("Container Name: " + containerName);

        CloudBlockBlob blob= this.getContainer().getBlockBlobReference(containerName);


        //UPLOAD!
       blob.upload(new FileInputStream(imageFile), imageFile.length());

        //-----DATABASE-----//
        //create client
        this.setDBClient(
                new MobileServiceClient(
                        "URL",
                        this.act.getApplicationContext()
                )
        );

        this.setImageTable(this.getDBClient().getTable(Image.class));
        this.setIcavTable(this.getDBClient().getTable(ICAV.class));

        //IMG TABLE QUERY
        String validImageID = containerName.replace("/", "_");
        Log.d("Azure", "Valid Image ID: " + validImageID);

        Image img = new Image(validImageID, this.img.getUser(), this.img.getLat(), this.img.getLon());
        this.getImageTable().insert(img);

        for(String context : this.img.getContextAttributeMap().keySet()){
            Map<String,String> attributeValueMap = this.img.getContextAttributeMap().get(context);

            for(String attribute : attributeValueMap.keySet()){
                String value = attributeValueMap.get(attribute);


                ICAV icavRow = new ICAV();
                icavRow.setImageID(validImageID);
                icavRow.setContextID(context);
                icavRow.setAttributeID(attribute);
                icavRow.setValue(value);

                this.getIcavTable().insert(icavRow);
            }

        }
    } catch (Exception e) {
        System.out.println(e.toString());
    }

    return null;
}



@Override
protected void onProgressUpdate(Object... object) {
    super.onProgressUpdate(object);
    Log.d("progressUpdate", "progress: "+((Integer)object[0] * 2) + "%");
}

@Override
protected void onPostExecute(Object o) {
    // to do


}

}

【问题讨论】:

    标签: java android azure-mobile-services azure-blob-storage


    【解决方案1】:

    如您所见,Azure SDK 并不直接允许这样做,但将您的输入流包装在另一个输入流中应该相当容易,该输入流可以为读取的字节提供回调。类似的东西:

    public class ListenableInputStream extends InputStream {
    
        private final InputStream wraped;
        private final ReadListener listener;
        private final long minimumBytesPerCall;
        private long bytesRead;
    
        public ListenableInputStream(InputStream wraped, ReadListener listener, int minimumBytesPerCall) {
            this.wraped = wraped;
            this.listener = listener;
            this.minimumBytesPerCall = minimumBytesPerCall;
        }
    
    
        @Override
        public int read() throws IOException {
            int read = wraped.read();
            if (read >= 0) {
                bytesRead++;
            }
            if (bytesRead > minimumBytesPerCall || read == -1) {
                listener.onRead(bytesRead);
                bytesRead = 0;
            }
            return read;
        }
    
        @Override
        public int available() throws IOException {
            return wraped.available();
        }
    
        @Override
        public void close() throws IOException {
            wraped.close();
        }
    
        @Override
        public synchronized void mark(int readlimit) {
            wraped.mark(readlimit);
        }
    
        @Override
        public synchronized void reset() throws IOException {
            wraped.reset();
        }
    
        @Override
        public boolean markSupported() {
            return wraped.markSupported();
        }
    
        interface ReadListener {
            void onRead(long bytes);
        }
    }
    

    minimumBytesPerCall 应该用一些合理的数字来初始化,因为您可能不想在每个字节上都被调用,也许每半兆字节应该是好的。

    请记住,这一切都是在 doInBackground 线程上调用的,因此请采取相应措施。

    编辑:

    我已经编辑了上面的类,在计算 bytesRead 值时出现了一个小错误。 官方文档解释了你的后续问题https://developer.android.com/reference/java/io/InputStream.html#read()

    从输入流中读取下一个字节的数据

    所以read() 在到达末尾时读取 1 个字节的数据(或返回 -1)。所以是的,它必须多次调用才能读取整个图像。

    然后每次至少读取minimumBytesPerCall 时调用方法onRead(long) get (这是为了避免对每个字节进行回调)并在流的末尾再次调用一次(当它返回-1时)

    传递给onRead(long) 的值是自上次调用以来已读取的数量。所以在你的 AsyncTask 上实现这个,你必须累积这个值并与文件的总大小进行比较。

    asynctask 中的以下代码应该可以正常工作(假设 Progress 泛型参数是 Long):

        private long fileLength;
        private long totalBytes;
    
        private final ListenableInputStream.ReadListener readListener = new ListenableInputStream.ReadListener() {
            @Override
            public void onRead(long bytes) {
                totalBytes += bytes;
                publishProgress(totalBytes);
            }
        };
    

    在你的上传部分你替换为:

    FileInputStream fis = new FileInputStream(imageFile);
    fileLength = imageFile.length();
    ListenableInputStream lis = new ListenableInputStream(fi, readListener, 256 * 1024); // this will call onRead(long) every 256kb
    blob.upload(lis, fileLength);
    

    最后一点,请记住CloudBlockBlob 在内部只是将文件缓存在自己的内存中以供以后上传,或者做任何其他您无法控制的奇怪事情。这段代码所做的只是检查是否读取了完整的文件。

    编码愉快!

    【讨论】:

    • ps.:我刚刚编辑了我的答案,为一个好的包装器实现添加了必要的方法。
    • 所以要使用它我会做 blob.upload(new ListenableInputStream(new FileInputStream(imageFile), x, y), imageFile.length());我不确定如何使用 ReadListener 以及我应该为 minimumBytesPerCall 传递什么。我会在 read 方法中记录在 ListenableInputStream 类中读取的字节吗?
    • ReadListener 是一个接口。您应该阅读 Java 中的接口。你可以在你的AzureBlobUploader 上写implements ReadListener 并传递this 关键字。每次发送的字节数超过minimumBytesPerCall 时,都会调用ListenableInputStream.onRead(int)。在答案末尾阅读我对此的评论。大约 256k~512kb 可能适合不向 UI 线程发送垃圾邮件并保持进度条移动。
    • @Budius 我看你的想法是对的,但我搜索了两个ReadListener 接口。一个是JavaEE interface,不适用于Android,另一个是third-party interface,用于条形码等解码器。它们似乎不适合这种情况。请指出哪个ReadListener。谢谢!
    • 我正在调用 blob.upload(new ListenableInputStream(new FileInputStream(imageFile), this, 500000), imageFile.length());我正在 AzureBlobUploader 中实现 ListenableInputStream 并实现 onRead 方法。我得到了一些我不知道如何用来获得百分比的数字。有时打印停止并且图像尚未上传,有时在图像上传后继续打印。我尝试摆脱 read 方法中的 if 语句并将 bytesRead 传递给 onRead 然后执行 (bytes/imageFile.length())*100 以获得百分比,但我没有得到合理的数字
    【解决方案2】:

    另一种方式满足您的需求,有一个 MS 博客介绍了 uploading a blob to Azure Storage with progress bar and variable upload block size。该代码是用 C# 编写的,但对于 Java/Android 开发人员来说阅读起来非常简单,我认为您可以轻松地用 Java 为 Android 重写它,以计算上传进程条比率以通过一些公共变量共享。

    希望对你有帮助。

    【讨论】:

      猜你喜欢
      • 2014-06-03
      • 2016-03-09
      • 1970-01-01
      • 1970-01-01
      • 2012-01-01
      • 2013-05-19
      • 1970-01-01
      • 2012-04-15
      • 1970-01-01
      相关资源
      最近更新 更多