七牛云存储之文件上传(Android)

七牛云存储之文件上传

项目中要用到七牛云存储,用于存储用户的文件数据,于是,看了一下七牛的文档(Android SDK 和 Java SDK),写了一个 demo 。本文记录一下 android 端上传文件到七牛服务器的步骤,并对七牛云存储使用的一些问题作出了一些思考。demo 实现了单个文件上传,多个文件上传,多个文件排队上传。详情请参考七牛官方文档

一,注册七牛账号,并创建存储空间

  1. 注册
  2. 创建密钥,拿到 AccessKey/SecretKey,用于生成上传的token
  3. 创建存储空间,指定文件上传的空间名称:bucket

这里拿到的资源有:AccessKey、SecretKey、bucket,主要是这三个值。

二、Java后台生成上传文件的token

android 端可以添加七牛的 Java SDK,并可以生成 token,但是使用这个 token 上传文件一直提示:no such bucket(具体原因不知道,但是根据密钥安全使用须知,token 是不应该在APP端生成的,所以也没继续找这个问题)。可以在 android studio 新建一个 Java module,添加七牛 Java SDK 依赖,使用 SDK 生成 token,注意这个 token 是有时效的,过来一段时间后会失效,所以每次上传文件前,应该向后台请求一个上传 token,代码如下(根据不同需求,生成 token 所需参数有所变化,详情请看客户端上传凭证):


    public static void createQiniuToken() {
        String accessKey = "your accesskey";
        String secretKey = "your secretkey";
        String bucket = "your bucket name";
        Auth auth = Auth.create(accessKey, secretKey);
        String upToken = auth.uploadToken(bucket);
        System.out.println(upToken);
    }

拿到token之后就可以在 android 端 demo 使用。

三、上传文件到七牛服务器应该考虑或者注意的问题

文件上传到七牛服务器,有很多业务要处理,获取上传凭证(token),文件的命名,文件覆盖,文件管理等。Android SDK 没有提供这些功能,这些功能在 Java SDK 中实现,通过生成不同的 token 实现不同业务功能。

3.1 关于android SDK 上传文件方法中的key

文件的外链接地址是由存储空间的外链域名和key组成的:外链域名 + key,如我的一个bucket的域名为:http://pfln1bbp9.bkt.clouddn.com/ ,那么上传到这个bucket的文件的外链地址为:http://pfln1bbp9.bkt.clouddn.com/ + key。所以,key如何取值,应该有一套自己项目的标准,确保文件何时是唯一的,何时是不唯一的。

3.2 修改或者覆盖文件该如何处理

有些文件可能会修改,那么修改后的文件如何覆盖旧文件呢?
七牛 Android SDK 并没有提供文件的覆盖功能,文件覆盖上传需要服务端的支持,即在生成token的时候带文件的名字,然后拿着这个 token 去上传文件即可:

String accessKey = "access key";
String secretKey = "secret key";
String bucket = "bucket name";
String key = "file key";

Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket, key);
System.out.println(upToken);

更多内容请查看 覆盖上传的凭证

3.3 文件的管理功能

文件资源管理属于Java SDK的功能,参考资源管理

3.4 视频/音频/图片压缩功能

七牛支持在文件上传到七牛之后,立即对其进行多种指令的数据处理,这个只需要在生成的上传凭证中指定相关的处理参数即可。
参考 带数据处理的凭证

3.5 文件下载

参考下载文件,值得注意的是在拼接链接之前,将文件名进行urlencode以兼容不同的字符。

3.6 是否支持批量上传

七牛目前只支持一个请求上传一个文件,所以一次上传多个文件的话,就等同于一次发送多个请求,七牛不支持。这正是本文 demo 所要解决的问题。

3.7 密钥安全使用须知

参考密钥安全使用须知

四、Android 端文件上传工具

详情请参考七牛官方文档 对象存储Android SDK
本 demo 简单实现了七牛文件上传工具:QiniuUploadManager ,这个类提供三个上传文件的方法,和两个取消上传的方法,具体如下:

4.1 单个文件上传

public boolean upload(QiniuUploadFile param, OnUploadListener uploadListener)

4.2 多文件上传

 public boolean upload(List<QiniuUploadFile> params, OnUploadListener uploadListener)

4.3 多文件排队上传

 public void queueUpload(Queue<QiniuUploadFile> params, OnUploadListener uploadListener)

4.4 取消上传

// 取消某一个listener的任务
public void cancel(OnUploadListener listener)
// 取消所有listener的任务
public void cancel()

4.5 example

private QiniuUploadManager manager;
private String token = "your qiniu upload token";

private void singleUpload(String path) {
        if (manager == null) {
            manager = QiniuUploadManager.getInstance(this);
        }
        String currentTim = String.valueOf(System.currentTimeMillis());
        String key = "files/" + currentTim + "/" + currentTim + ".jpg";
        String mimeType = "image/jpeg";
        QiniuUploadManager.QiniuUploadFile param = new QiniuUploadManager.QiniuUploadFile(path, key, mimeType, token);
        manager.upload(param, new QiniuUploadManager.OnUploadListener() {
            @Override
            public void onStartUpload() {
                Log.e(TAG, "onStartUpload");
            }

            @Override
            public void onUploadProgress(String key, double percent) {
            }

            @Override
            public void onUploadFailed(String key, String err) {
                Log.e(TAG, "onUploadFailed:" + err);
            }

            @Override
            public void onUploadBlockComplete(String key) {
                Log.e(TAG, "onUploadBlockComplete");
            }

            @Override
            public void onUploadCompleted() {
                Log.e(TAG, "onUploadCompleted");
            }

            @Override
            public void onUploadCancel() {
                Log.e(TAG, "onUploadCancel");
            }
        });
    }

4.6 完整代码


import android.content.Context;
import android.util.Log;

import com.qiniu.android.common.FixedZone;
import com.qiniu.android.storage.Configuration;
import com.qiniu.android.storage.KeyGenerator;
import com.qiniu.android.storage.Recorder;
import com.qiniu.android.storage.UploadManager;
import com.qiniu.android.storage.UploadOptions;
import com.qiniu.android.storage.persistent.FileRecorder;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description 七牛文件上传工具
 * @Date 2018.09.26
 */
public class QiniuUploadManager {

    public interface OnUploadListener {
        void onStartUpload();

        void onUploadProgress(String key, double percent);

        void onUploadFailed(String key, String err);

        void onUploadBlockComplete(String key);

        void onUploadCompleted();

        void onUploadCancel();
    }

    private final String TAG = this.getClass().getSimpleName();
    private static QiniuUploadManager manager;

    public static QiniuUploadManager getInstance(Context context) {
        if (manager == null) {
            synchronized(QiniuUploadManager.class) {
                if(manager == null) {
                    manager = new QiniuUploadManager(context);
                }
            }
        }
        return manager;
    }

    private UploadManager uploadManager;
    private Object lock = new Object();
    private HashMap<OnUploadListener, Boolean> cancels = new HashMap<>();
    private List<OnUploadListener> uploadListeners = new ArrayList<>();

    private QiniuUploadManager(Context appContext) {
        initManager(appContext.getApplicationContext());
    }

    private void initManager(Context appContext) {
        String dirPath = appContext.getExternalCacheDir().getPath() + File.separator + "QiniuTemp";
        Log.d(TAG, dirPath);
        Recorder recorder = null;
        try {
            recorder = new FileRecorder(dirPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        KeyGenerator keyGen = (key, file) -> key + "_._" + new StringBuffer(file.getAbsolutePath()).reverse();
        Configuration.Builder builder = new Configuration.Builder();
        builder.chunkSize(512 * 1024)              // 分片上传时,每片的大小。 默认256K
                .putThreshhold(1024 * 1024)        // 启用分片上传阀值。默认512K
                .connectTimeout(10)                // 链接超时。默认10秒
                .responseTimeout(60)               // 服务器响应超时。默认60秒
                .zone(FixedZone.zone0);            // 设置区域,指定不同区域的上传域名、备用域名、备用IP。
        if (recorder != null) {
            builder = builder.recorder(recorder)   // recorder分片上传时,已上传片记录器。默认null
                    .recorder(recorder, keyGen);   // keyGen 分片上传时,生成标识符,用于片记录器区分是那个文件的上传记录
        }
        Configuration config = builder.build();
        uploadManager = new UploadManager(config);
    }

    /**
     * 上传单个文件到七牛服务器
     *
     * @param param
     * @param uploadListener
     * @return 文件有效,开始上传返回true,否则返回false
     */
    public synchronized boolean upload(QiniuUploadFile param, OnUploadListener uploadListener) {
        if (param == null) {
            return false;
        }
        File uploadFile = new File(param.getFilePath());
        if (!uploadFile.exists() || uploadFile.isDirectory()) {
            return false; // 如果是文件夹,或者文件不存在,那么返回false
        }
        if (uploadListener != null) {
            uploadListener.onStartUpload();
            Log.d(TAG, "开始上传(" + param.getKey() + "): " + param.getFilePath());
        }
        // 注册回调对象,用户取消上传时使用这些对象
        uploadListeners.add(uploadListener);
        cancels.put(uploadListener, false);
        uploadManager.put(uploadFile, param.getKey(), param.getToken(),
                (key, info, response) -> {
                    synchronized (lock) {
                        if (uploadListener == null) {
                            return;
                        }
                        if (info.isOK()) {
                            Log.d(TAG, "上传成功(" + key +"): " + info.duration);
                            uploadListener.onUploadBlockComplete(key);
                            uploadListener.onUploadCompleted();
                        } else {
                            Log.d(TAG, "上传失败(" + key + "): " + info.error);
                            uploadListener.onUploadFailed(key, info.error);
                        }
                        // 清理回调等资源
                        Log.d(TAG, "上传完成(" + key +"): " + info.duration);
                        uploadListeners.remove(uploadListener);
                        cancels.remove(uploadListener);
                    }
                },
                new UploadOptions(null, null, false,
                        (key12, percent) -> {
                            synchronized (lock) {
                                Log.d(TAG, "progress(" + key12 + "):" + percent);
                                if (uploadListener != null) {
                                    uploadListener.onUploadProgress(key12, percent);
                                }
                            }
                        },
                        () -> {
                            synchronized (lock) {
                                if (uploadListener == null) {
                                    return false;
                                }
                                Boolean result = cancels.get(uploadListener);
                                // Log.d(TAG, "检查取消标识(" + param.getKey() + "): " + result);
                                if (result != null && result) {
                                    cancels.remove(uploadListener);
                                }
                                // 有出现一次true后还继续调用的情况,需要判null
                                return result == null ? true : result;
                            }
                        }));
        return true;
    }

    /**
     * 同时上传多个文件
     * @param params 需要上传的文件
     * @param uploadListener 回调
     * @return 开始上传返回 true,如果参数无效,或者文件不存在等,返回false,不上传
     */
    public synchronized boolean upload(List<QiniuUploadFile> params, OnUploadListener uploadListener) {
        if (params == null || params.size() == 0) {
            return false;
        }
        AtomicInteger completedCount = new AtomicInteger();  // 完成(失败也算完成)的数量
        List<QiniuUploadFile> needUploadFile = new ArrayList<>();
        for (QiniuUploadFile param : params) {
            File uploadFile = new File(param.getFilePath());
            if (!uploadFile.exists() || uploadFile.isDirectory()) {
                continue;  // 过滤无效的文件
            }
            needUploadFile.add(param);
        }
        if (needUploadFile.size() == 0) {
            return false;
        }
        if (uploadListener != null) {
            Log.d(TAG, "开始上传(size=" + needUploadFile.size() + ")");
            uploadListener.onStartUpload(); // 开始上传任务
        }
        uploadListeners.add(uploadListener);
        cancels.put(uploadListener, false);
        for (QiniuUploadFile param : needUploadFile) {
            File uploadFile = new File(param.getFilePath());
            uploadManager.put(uploadFile, param.getKey(), param.getToken(),
                    (key, info, response) -> {
                        synchronized (lock) {
                            completedCount.getAndIncrement();
                            if (uploadListener == null) {
                                return;
                            }
                            if (info.isOK()) {
                                Log.d(TAG, "上传成功(" + key +"): " + info.duration);
                                uploadListener.onUploadBlockComplete(key);
                            } else {
                                Log.d(TAG, "上传失败(" + key + "): " + info.error);
                                uploadListener.onUploadFailed(key, info.error);
                            }
                            if (completedCount.get() == needUploadFile.size()) {
                                Log.d(TAG, "上传完成(" + needUploadFile.size() +")");
                                uploadListener.onUploadCompleted();
                                // 如果所有任务都完成了,那么清理回调资源
                                uploadListeners.remove(uploadListener);
                                cancels.remove(uploadListener);
                            }
                        }
                    },
                    new UploadOptions(null, param.getMimeType(), false,
                            (key1, percent) -> {
                                synchronized (lock) {
                                    Log.d(TAG, "progress(" + key1 + "):" + percent);
                                    if (uploadListener != null) {
                                        uploadListener.onUploadProgress(key1, percent);
                                    }
                                }
                            },
                            () -> {
                                if (uploadListener == null) {
                                    return false;
                                }
                                Boolean result = cancels.get(uploadListener);
                                // Log.d(TAG, "检查取消标识(" + param.getKey() + "): " + result);
                                if (result != null && result) {
                                    cancels.remove(uploadListener);
                                }
                                // 由于同时上传多个文件是共享一个:uploadListener,
                                // 所以,后面读取到的都是null,null,标识取消
                                return result == null ? true : result;
                            }));
        }
        return true;
    }

    /**
     * 排队的方式上传文件,上传完前一个才继续上传下一个
     * 注意,这个方法没有 onStartUpload 回调
     * @param params 需要上传的文件
     * @param uploadListener 回调接口
     */
    public synchronized void queueUpload(Queue<QiniuUploadFile> params, OnUploadListener uploadListener) {
        if (params == null || params.size() == 0) {
            return;
        }
        Queue<QiniuUploadFile> files = new LinkedList<>();
        for(QiniuUploadFile param : params) {
            File uploadFile = new File(param.getFilePath());
            if (uploadFile.exists() && !uploadFile.isDirectory()) {
                files.add(param);
            }
        }
        QiniuUploadFile param = files.poll();
        if(param == null) {
            return;
        }
        File uploadFile = new File(param.getFilePath());
        // 注册回调对象
        uploadListeners.add(uploadListener);
        Boolean cancel = cancels.get(uploadListener);
        if(cancel != null && cancel) {
            cancels.remove(uploadListener); // 在这里移除取消任务标志
            return;
        }
        cancels.put(uploadListener, false);
        uploadManager.put(uploadFile, param.getKey(), param.getToken(),
                (key, info, response) -> {
                    synchronized (lock) {
                        if (info.isOK()) {
                            Log.d(TAG, "上传成功(" + key +"): " + info.duration);
                            if(uploadListener != null) {
                                uploadListener.onUploadBlockComplete(key);
                            }
                        } else {
                            Log.d(TAG, "上传失败(" + key + "): " + info.error);
                            if(uploadListener != null) {
                                uploadListener.onUploadFailed(key, info.error);
                            }
                        }
                        if(files.size() == 0) {
                            // 清理回调等资源
                            Log.d(TAG, "上传完成(" + key + "): " + info.duration);
                            if(uploadListener != null) {
                                uploadListeners.remove(uploadListener);
                                cancels.remove(uploadListener);
                            }
                        } else {
                            // 未上传完成,继续队列中的下一个任务
                            queueUpload(files, uploadListener);
                        }
                    }
                },
                new UploadOptions(null, null, false,
                        (key12, percent) -> {
                            synchronized (lock) {
                                Log.d(TAG, "progress(" + key12 + "):" + percent);
                                if (uploadListener != null) {
                                    uploadListener.onUploadProgress(key12, percent);
                                }
                            }
                        },
                        () -> {
                            synchronized (lock) {
                                if (uploadListener == null) {
                                    return false;
                                }
                                Boolean result = cancels.get(uploadListener);
                                //Log.d(TAG, "取消(" + param.getKey() + "): " + result);
                                // 有出现一次true后还继续调用的情况,所以需要判null
                                return result == null ? true : result;
                            }
                        }));
    }

    /**
     * 取消指定上传任务
     *
     * @param listener
     */
    public void cancel(OnUploadListener listener) {
        synchronized (lock) {
            cancels.put(listener, true);
            for (OnUploadListener uploadListener : uploadListeners) {
                if (uploadListener == listener) {
                    try {
                        uploadListener.onUploadCancel();
                        Log.d(TAG, "取消上传");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
                }
            }
            uploadListeners.remove(listener);
        }
    }

    /**
     * 取消所有的上传任务
     */
    public void cancel() {
        synchronized (lock) {
            for (OnUploadListener key : cancels.keySet()) {
                cancels.put(key, true);
            }
            for (OnUploadListener listener : uploadListeners) {
                try {
                    listener.onUploadCancel();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            uploadListeners.clear();
            Log.d(TAG, "取消所有上传任务");
        }
    }

    public static class QiniuUploadFile {
        private String filePath;  // 文件的路径
        private String key;       // 文件上传到服务器的路径,如:files/images/test.jpg
        private String mimeType;  // 文件类型
        private String token;     // 从后台获取的token值,只在一定时间内有效

        public QiniuUploadFile(String filePath, String key, String mimeType, String token) {
            this.filePath = filePath;
            this.key = key;
            this.mimeType = mimeType;
            this.token = token;
        }

        public String getFilePath() {
            return filePath;
        }

        public String getKey() {
            return key;
        }

        public String getMimeType() {
            return mimeType;
        }

        public String getToken() {
            return token;
        }
    }
}
原创文章 25 获赞 12 访问量 1万+

猜你喜欢

转载自blog.csdn.net/half_bottle/article/details/82858545
今日推荐