Android 跟踪 Azure CloudBlockBlob 上传的进度

Android Track progress of Azure CloudBlockBlob upload

如何打印调用后已上传的字节数 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


}

}

如您所见,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 线程上调用的,因此请相应地采取行动。

编辑:

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

Reads the next byte of data from the input stream

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

然后方法 onRead(long) get 在每次至少 minimumBytesPerCall 被读取时调用(这是为了避免为每个字节回调)并在流的末尾再次调用(当它 returns -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 只是将文件缓存在自己的内存中以供以后上传,或者执行您无法控制的任何其他奇怪的事情。这段代码所做的只是检查是否读取了完整的文件。

编码愉快!

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

希望对您有所帮助。