MINIO存储服务Android上传进度实现

1 篇文章 0 订阅
1 篇文章 0 订阅

项目场景:

公司存储服务阿里Oss需要逐步替换为自己的MinIO服务器,客户端需要把文件上传模块修改为对应的MinIO库上传


问题描述:

在使用文件上传服务时发现,官方API没有提供上传进度回调接口,而对于之前的Oss来说需要加入上传进度回调接口,我们需要自己来实现进度回调,普通上传文件实现代码如下

   MinioClient minioClient = MinioClient.builder()
                        .endpoint("yourendpoint")
                        .region("beijin")
                        .credentials("ACCESS_ID", "ACCESS_KEY")
                        .build();

   File file = new File(Environment.getExternalStorageDirectory() + "/testv.mp4");
   try {
       minioClient.putObject(PutObjectArgs.builder()
       						.bucket("test-xzy")
       						.object("my-objectname.mp4")
       						.stream(newFileInputStream(file),file.length(),-1)
                                  .build());
        System.out.println("my-objectname is uploaded successfully");
       } catch (IOException | ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
            e.printStackTrace();
        }

原因分析:

由于没有相关的的Android版本库,使用Java版本上传库 MiniIO-java 8.1.0
而由于上传的putObject方法及相关模块没有上传进度回调接口


解决方案:

有三种方法:
  1. 不修改库源码的情况下,我们可以从inputStream下手,在上传的过程中读取文件流的字节数作为上传进度 实现代码
 	  MinioClient minioClient = MinioClient.builder()
                        .endpoint("endpoint")
                        .region("beijin")
                        .credentials("accessKey", "secretKey")
                        .build();

                File file = new File(Environment.getExternalStorageDirectory() + "/testv.mp4");
                try {
                    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
                    ProgressInputStream pmis =
                            new ProgressInputStream(bis, new UploadListener() {
                                @Override
                                public void onProgress(int process) {
                                   //回调上传进度
                                }
                            });
                    minioClient.putObject(
                            PutObjectArgs.builder().bucket("test-xzy").object("my-objectname.mp4").stream(
                                    pmis, pmis.available(), -1)
                                    .build());
                    System.out.println("my-objectname is uploaded successfully");
                  
                } catch (IOException | ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
                    e.printStackTrace();
                }
	```
 	就在这个ProgressInputStream里处理回调逻辑代码如下

```java
package com.sky.testminio;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class ProgressInputStream extends FilterInputStream
{
    private UploadListener monitor;
    private long             nread = 0;
    private long             size = 0;


    /**
     * Constructs an object to monitor the progress of an input stream.
     *
     * @param message Descriptive text to be placed in the dialog box
     *                if one is popped up.
     * @param parentComponent The component triggering the operation
     *                        being monitored.
     * @param in The input stream to be monitored.
     */
    public ProgressInputStream(InputStream in, UploadListener listener) {
        super(in);
        try {
            size = in.available();
        }
        catch(IOException ioe) {
            size = 0;
        }
        monitor = listener;
    }


    /**
     * Get the ProgressMonitor object being used by this stream. Normally
     * this isn't needed unless you want to do something like change the
     * descriptive text partway through reading the file.
     * @return the ProgressMonitor object used by this object
     */
    public UploadListener getListener() {
        return monitor;
    }


    /**
     * Overrides <code>FilterInputStream.read</code>
     * to update the progress monitor after the read.
     */
    public int read() throws IOException {
        int c = in.read();
        if (c >= 0) notifyProcess(++nread);
        return c;
    }


    /**
     * Overrides <code>FilterInputStream.read</code>
     * to update the progress monitor after the read.
     */
    public int read(byte b[]) throws IOException {
        int nr = in.read(b);
        if (nr > 0) notifyProcess(nread += nr);

        return nr;
    }


    /**
     * Overrides <code>FilterInputStream.read</code>
     * to update the progress monitor after the read.
     */
    public int read(byte b[],
                    int off,
                    int len) throws IOException {
        int nr = in.read(b, off, len);
        if (nr > 0) notifyProcess(nread += nr);
        return nr;
    }


    /**
     * Overrides <code>FilterInputStream.skip</code>
     * to update the progress monitor after the skip.
     */
    public long skip(long n) throws IOException {
        long nr = in.skip(n);
        if (nr > 0) notifyProcess(nread += nr);
        return nr;
    }


    /**
     * Overrides <code>FilterInputStream.close</code>
     * to close the progress monitor as well as the stream.
     */
    public void close() throws IOException {
        in.close();
    }


    /**
     * Overrides <code>FilterInputStream.reset</code>
     * to reset the progress monitor as well as the stream.
     */
    public synchronized void reset() throws IOException {
        in.reset();
        nread = size - in.available();

    }

    private void  notifyProcess(long read){
        if(monitor != null){
            monitor.onProgress((int) ((float)read * 100 / size));
        }
    }
}

回调接口

public interface UploadListener {
   void onProgress(int process);
}
  1. 下载源码修改的情况下,由于MinIO-java版本地层上传使用了Okhttp,我们就可以从RequestBody下手,在writeTo方法中去回调上传进度
  @Override
  public void writeTo(BufferedSink sink) throws IOException {
    if (data instanceof InputStream) {
      InputStream stream = (InputStream) data;
      sink.writeAll(Okio.source(stream));
    } else if (data instanceof RandomAccessFile) {
      RandomAccessFile file = (RandomAccessFile) data;
      Source source = Okio.source(Channels.newInputStream(file.getChannel()));
      long total = 0;
      long read, toRead, remain;
      while (total < len){
        remain = len - total;
        toRead = Math.min(remain,SEGMENT_SIZE);

        read = source.read(sink.buffer(),toRead);

        if(read == -1){
          break;
        }

        total += read;
        sink.flush();

        //log progress
        Log.d("HttpRequestBody","upload progress="+ ((float)total/len));
      }
      //需要在分块的时候,最后一个上传完关闭
      if(source != null){
        source.close();
      }


    } else if (data instanceof byte[]) {
      byte[] bytes = (byte[]) data;
      sink.write(bytes, 0, len);
    } else {
      sink.writeUtf8(data.toString());
    }
  }

3.直接寻找替代SDK,AWS S3
使用的代码示例

  AmazonS3 s3 = new AmazonS3Client(new AWSCredentials() {
                    @Override
                    public String getAWSAccessKeyId() {
                        return "minio";
                    }

                    @Override
                    public String getAWSSecretKey() {
                        return "minio123";
                    }
                }, Region.getRegion(Regions.CN_NORTH_1),new ClientConfiguration());
				//这里我们原来的地址是http://oss.izxc.com
				//但是它最后的规则会把桶名拼到域名前面,这里我们把oss去掉
                s3.setEndpoint("http://izxcs.com");

                File file = new File(Environment.getExternalStorageDirectory() + "/testv.mp4");
                GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(
                        "oss", "test-xzy/myS3File.mp4");
                //这里我们上传文件的时候bucketName就需要替换为oss,bucketName拼到文件名的前面
                s3.putObject(new PutObjectRequest("oss","test-xzy/myS3File.mp4",file).withGeneralProgressListener(new ProgressListener(){
                    int readedbyte = 0;
                    @Override
                    public void progressChanged(ProgressEvent progressEvent) {
                        readedbyte += progressEvent.getBytesTransferred();
                        System.out.println("=========progress=================" + (float)(readedbyte / (float)file.length()) + "============URL=============");
                    }
                }));
                URL url = s3.generatePresignedUrl(urlRequest);
                System.out.println("=========URL=================" + url.toString() + "============URL=============");

总结:

以上就是实现进度回调的三种方式,每种方式都有各自的优缺点,
方法一,不会破坏原有框架结构,可以配合最新的MinIO版本使用
方法二,由于修改了源码,使用起来很方便可以结合自身项目开发,但是追踪新版本特性困难
方法三,采用了第三方库进行上传,在上传文件的时候取巧来配合我们自己的项目上传结构

遇到的问题:

在使用Android项目中使用Java项目时,可能会报一些类找不到的异常例如
2021-03-22 16:42:50.599 26054-26133/com.sky.testminio E/AndroidRuntime: FATAL EXCEPTION: Thread-6
    Process: com.sky.testminio, PID: 26054
    java.lang.NoClassDefFoundError: Failed resolution of: Ljavax/xml/stream/XMLInputFactory;
        at org.simpleframework.xml.stream.StreamProvider.<init>(StreamProvider.java:61)
        at org.simpleframework.xml.stream.ProviderFactory.getInstance(ProviderFactory.java:38)
        at org.simpleframework.xml.stream.NodeBuilder.<clinit>(NodeBuilder.java:45)
        at org.simpleframework.xml.stream.NodeBuilder.read(NodeBuilder.java:71)
        at org.simpleframework.xml.core.Persister.read(Persister.java:562)
        at org.simpleframework.xml.core.Persister.read(Persister.java:462)
        at io.minio.Xml.unmarshal(Xml.java:53)
        at io.minio.MinioClient.createMultipartUpload(MinioClient.java:4216)
        at io.minio.MinioClient.putObject(MinioClient.java:2718)
        at io.minio.MinioClient.putObject(MinioClient.java:2823)
        at com.sky.testminio.MainActivity$3.run(MainActivity.java:118)
        at java.lang.Thread.run(Thread.java:784)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "javax.xml.stream.XMLInputFactory" on path: DexPathList[[zip file "/data/app/com.sky.testminio-tnupjENouSCrVhtuUWJpsQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.sky.testminio-tnupjENouSCrVhtuUWJpsQ==/lib/arm64, /system/lib64, /vendor/lib64, /product/lib64, /preas/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)

实际上这是因为android.jar包中的相关包跟jdk中的包冲突了,但Android包缺少相关的类而jdk中有,所以在java环境下运行没有问题,在android环境中就崩溃了。解决办法很简单,直接在android编译的时候加入相应的类即可,代码如下

dependencies {
	...
    implementation 'io.minio:minio:8.0.0'
    implementation files ('/usr/local/jdk1.8.0_241/jre/lib/rt.jar')
}

这样就可以在Android项目里运行起来了。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值