项目场景:
公司存储服务阿里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方法及相关模块没有上传进度回调接口
解决方案:
有三种方法:- 不修改库源码的情况下,我们可以从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);
}
- 下载源码修改的情况下,由于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项目里运行起来了。