七牛上传图片实践

最近用到七牛上传视频和图片的功能,于是去七牛官网看了文档,写了一个上传文件到七牛的demo,顺便将写的过程中踩的一些坑记录下来。

注册七牛开发者账号

这个就不说了,非常简单,注册完之后,在左边对象存储项下新建一个存储空间,填写存储空间的名字,其他选项直接默认就好了。

然后在左侧个人中心-密钥管理下查看自己的AccessKey和SecretKey,应该很长一串字母和数字组合,说到这里不得不提一下我的坑。

我一开始用的qq浏览器打开的这个网页,可能是浏览器记住了我登录的账号密码,然后不知道怎么回事将这里的两个密钥替换成了我的账号密码,我以为密钥就是这个,结果导致后面的token怎么都不对,调试了半天总是bad token的错误,整个人都不好了。所以能用谷歌浏览器还是用谷歌吧。

七牛云的任务就结束了,还是很简单的,只要创建一个存储空间就好了。

获得token

这一步是非常重要的,先不说这个token是怎么得到的,因为算法有点复杂。我们直接用官网提供的工具先生成token供后面测试。最后会给出生成token的代码。

token在线生成工具

点击左上角的运行


会在右下角看到这个


上一步我们已经看到了两个密钥,将其分别输入到AccessKey和SecretKey中,bucketname就是之前新建的那个存储空间的名字,deadline为这个token的失效时间,其他可以不填,再点击生成uptoken,就会根据这些内容生成一个能供你测试的token。

这里注意一下你设置的token的有效时间,这里踩的一个坑是测试的时间太长,1个小时后token就失效了,所以自然上传也会出错,所以当你碰到error:null body这个错误时可以试试查看是不是token失效了

安卓端代码

七牛安卓sdk文档

添加依赖

这里直接添加比较新的7.3.x,后面会说为什么

compile 'com.qiniu:qiniu-android-sdk:7.3.2'

发送请求

首先看看文档是怎么写的


要准备3个内容,data、key、token

  • token ,这个是最简单的,上一步生成的token直接可以拿过来用
  • key , 这个是指定你的图片或其他文件上传到七牛存储空间后叫什么名字
  • data , 这个是要上传的目标,可以是File类型的文件,可以是String类型的文件所在目录,也可是是byte[]数组,上传图片的话肯定是用前两种比较方便

回调函数的参数文档中也有



在调试的时候可以将info打印到日志中,这样如果没有上传成功也可以看到是什么原因。如果不主动打印日志,那么上传失败,AS是不会打印任何错误信息的。

这里以上传手机中的一张图片为例,图片位置在手机sd卡目录下test.jpg。可仿照文档写出以下代码:

 new Thread(new Runnable() {
            @Override
            public void run() {
                UploadManager uploadManager = new UploadManager();
                String path = Environment.getExternalStorageDirectory() + "/test.jpg";
                File file = new File(path);
                uploadManager.put(file, null, upToken,
                        new UpCompletionHandler() {
                            @Override
                            public void complete(String key, ResponseInfo respInfo,
                                                 JSONObject jsonData) {
                                if (respInfo.isOK()) {
                                    
                                    Toast.makeText(MainActivity.this, "上传成功!", Toast.LENGTH_SHORT).show();
                                } else {
                                    Toast.makeText(MainActivity.this, "上传失败!", Toast.LENGTH_SHORT).show();
                                    Log.d(TAG, "error: " + respInfo.toString());
                                }
                            }

                        }, null);
            }
        }).start();

如果你这就急急忙忙准备运行,而又不打印任何日志,你会发现总是上传失败,并且还找不到原因。一开始我也是一脸茫然,后来把info的信息打印出来之后看到access denied,立马就知道了忘记设置权限了。

这里一定不要忘记给app加上读取sd卡和联网的权限

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>

加了权限这下该没问题了吧,说不准,之前我们说最好是用7.3.x版本,因为我在用7.0.x版本的时候遇到一个错误

    error:incorrect region, please use up-z2.qiniu.com

这个错误跟地区有关,在华南地区需要用up-z2.qiniu.com这个域名去访问。在文档中之前被我忽略的一部分派上用场了

可以在创建UploadManager时给它传入一些设置。

 Configuration config = new Configuration.Builder()
                        .zone(Zone.zone2) // 设置区域,指定不同区域的上传域名、备用域名、备用IP。
                        .build();
                UploadManager uploadManager = new UploadManager(config);
 Configuration config = new Configuration.Builder()
                        .zone(Zone.httpAutoZone) // 自动识别
                        .build();
                UploadManager uploadManager = new UploadManager(config);

点开Zone我们就知道为什么设置这个能解决问题了,出现的问题就是让我们使用up-z2.qiniu.com,而这个域名就在Zone.zone2里。

public abstract class Zone {

    /**
     * 华东机房, http
     */
    public static final Zone zone0 =
            createZone("upload.qiniu.com", "up.qiniu.com", "183.136.139.10", "115.231.182.136");

    /**
     * 华北机房, http
     */
    public static final Zone zone1 =
            createZone("upload-z1.qiniu.com", "up-z1.qiniu.com", "106.38.227.27", "106.38.227.28");

    /**
     * 华南机房, http
     */
    public static final Zone zone2 =
            createZone("upload-z2.qiniu.com", "up-z2.qiniu.com", "183.60.214.197", "14.152.37.7");

    /**
     * 北美机房, http
     */
    public static final Zone zoneNa0 =
            createZone("upload-na0.qiniu.com", "up-na0.qiniu.com", "23.236.102.3", "23.236.102.2");

    /**
     * 自动判断机房, http
     */
    public static final AutoZone httpAutoZone = new AutoZone(false, null);

    /**
     * 自动判断机房, https
     */
    public static final AutoZone httpsAutoZone = new AutoZone(true, null);

    ...
}

结果

上传成功后,在test-demo存储空间中就会增加一张新的图片了

获取图片的外链地址

打开七牛云端,test-demo内容管理可以查看已上传的文件,点击可以查看外链地址:


这个外链地址可以通过domain+key的组合来得到,domain也能在内容管理查看到:


key就是上传后在七牛存储上的文件名。

所以组合后的地址就是http://oq543v9g0.bkt.clouddn.com/lt0EG7Sm0nVKu3YaZAhE9XRoKgBr

token的加密算法

关于token是怎么生成的,可以看看官方文档:

管理凭证token

这里只提供加密的代码,可以对照着官方文档看看是如何加密的:

    //七牛后台的key
    private static String AccessKey = "AccessKey";
    //七牛后台的secret
    private static String SecretKey = "SecretKey";

    private static final String MAC_NAME = "HmacSHA1";
    private static final String ENCODING = "UTF-8";
    public String getToken(){
        JSONObject json = new JSONObject();
        long deadline = System.currentTimeMillis() / 1000 + 3600;
        try {
            json.put("deadline", deadline);// 有效时间为一个小时
            json.put("scope", bucketName);//存储空间的名字
        } catch (JSONException e) {
            e.printStackTrace();
        }
        
        String encodedPutPolicy = UrlSafeBase64.encodeToString(json
                .toString().getBytes());
        byte[] sign = new byte[0];
        
        try {
            sign = HmacSHA1Encrypt(encodedPutPolicy, SecretKey);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        String encodedSign = UrlSafeBase64.encodeToString(sign);
        String uploadToken = AccessKey + ':' + encodedSign + ':'
                + encodedPutPolicy;
        return uploadToken;
    }

    public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey)
            throws Exception {
        byte[] data = encryptKey.getBytes(ENCODING);
        // 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
        SecretKeySpec secretKey = new SecretKeySpec(data, MAC_NAME);
        // 生成一个指定 Mac 算法 的 Mac 对象
        Mac mac = Mac.getInstance(MAC_NAME);
        // 用给定密钥初始化 Mac 对象
        mac.init(secretKey);
        byte[] text = encryptText.getBytes(ENCODING);
        // 完成 Mac 操作
        return mac.doFinal(text);
    }

进阶用法:多图上传

上述代码只能完成一张图片的上传,而做项目的时候经常需要一次性上传多张图片,这里提供两种实现方法。

定义的全局变量:

private String upToken = "你的token";

private Configuration config = new Configuration.Builder()
            .zone(Zone.zone2)
            .build();
private UploadManager uploadManager = new UploadManager(config);
private int[] i = {0};//循环变量,表示现在正在上传第几张图片

循环实现

优点:实现简单
缺点:顺序不好控制

new Thread(new Runnable() {
            @Override
            public void run() {
                //两张图片路径
                String path1  = Environment.getExternalStorageDirectory() + "/test.jpg";
                String path2  = Environment.getExternalStorageDirectory() + "/test-1.jpg";

                final List<String> list = new ArrayList<>();
                list.add(path1);
                list.add(path2);

                for(i[0]=0;i[0]<list.size();i[0]++) {
                    String file = list.get(i[0]);
                    uploadManager.put(file, null, upToken,
                            new UpCompletionHandler() {
                                @Override
                                public void complete(String key, ResponseInfo respInfo,
                                                     JSONObject jsonData) {
                                    if (respInfo.isOK()) {
                                        print("第" + i[0] +"张上传成功!");
                                    } else {
                                        print("第" + i[0] +"张上传失败!");
                                        Log.d(TAG, "error: " + respInfo.error);
                                    }
                                }

                            }, null);
                }
            }
        }).start();

运行结果

说明
问题出来了,为什么打印了两个“第2张上传成功”?原因是因为是循环进行网络请求,效果如下图:

网络请求是需要消耗时间的,而循环在开启一个网络请求upload1后就立马进入下一个循环了(而不会等待网络请求返回结果)。这时,循环变量已经由0变为1,但upload1可能还没有返回结果,这时开启第二个网络请求upload2,所以等两个请求都完成时,循环变量已经变为1,因而两个请求返回结果时都会打印“第2张上传成功”。

递归实现

优点:顺序清晰
缺点:代码多,复杂

  private void click() {
        //两张图片路径
        String path1  = Environment.getExternalStorageDirectory() + "/test.jpg";
        String path2  = Environment.getExternalStorageDirectory() + "/test-1.jpg";

        final List<String> list = new ArrayList<>();
        list.add(path1);
        list.add(path2);

        //递归上传两张图片
        uploadMutliFiles(list, new UploadMutliListener() {
            @Override
            public void onUploadMutliSuccess() {
                print(list.size() + "张图片上传成功!");
            }

            @Override
            public void onUploadMutliFail(Error error) {
                print("上传失败!");
            }
        });

    }

    //上传多张图片
    public void uploadMutliFiles(final List<String> filesUrls, final UploadMutliListener uploadMutliListener) {
        if (filesUrls != null && filesUrls.size() > 0) {
            final String url = filesUrls.get(i[0]);
            uploadFile(url, new UploadListener() {
                @Override
                public void onUploadSuccess() {
                    final UploadListener uploadListener = this;
                    Log.d(TAG, "第" + (i[0]+1) + "张:" + url + "\t上传成功!");
                    i[0]++;
                    //递归边界条件
                    if (i[0] < filesUrls.size()) {
                        //七牛后台对上传的文件名是以时间戳来命名,以秒为单位,如果文件上传过快,两张图片就会重名而上传失败,所以间隔1秒,保证上传成功(具体会不会失败呢?自己试一下看看)
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                uploadFile(filesUrls.get(i[0]), uploadListener);
                            }
                        }, 1000);
                    } else {
                        uploadMutliListener.onUploadMutliSuccess();
                    }
                }

                @Override
                public void onUploadFail(Error error) {
                    print("第" + (i[0]+1) + "张上传失败!" + filesUrls.get(i[0]));
                    uploadMutliListener.onUploadMutliFail(error);
                }
            });

        }
    }

    //上传单个文件
    public void uploadFile(final String filePath, final UploadListener uploadListener) {
        if (filePath == null) return;
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (uploadManager == null) {
                    uploadManager = new UploadManager();
                }
                uploadManager.put(filePath, null, upToken,
                        new UpCompletionHandler() {
                            @Override
                            public void complete(String key, ResponseInfo respInfo,
                                                 JSONObject jsonData) {

                                if (respInfo.isOK()) {
                                    print(jsonData.toString());
                                    uploadListener.onUploadSuccess();

                                } else {
                                    uploadListener.onUploadFail(new Error("上传失败" + respInfo.error));
                                }
                            }

                        }, null);
            }
        }).start();
    }

    //上传回调
    public interface UploadListener {
        void onUploadSuccess();

        void onUploadFail(Error error);
    }

    //上传多张文件回调
    public interface UploadMutliListener {
        void onUploadMutliSuccess();

        void onUploadMutliFail(Error error);
    }

运行结果

说明
看代码规模就能明显感觉两者的不同,递归上传代码量相比循环多了很多,不过它的优点就是它的顺序十分清晰,递归调用的逻辑图可以描绘成以下的效果:

为每个网络请求upload设置一个监听器,只有当upload1的请求成功返回结果,也就是第一张图片上传成功后,才开启第2个上传图片的请求upload2,如果有upload3同样接在upload2的success后,这样形成了链式结构,能确保图片一定是按照顺序上传的。

总结

七牛云不局限于上传图片,其实任何文件都可以,所以如果是上传视频的话,道理都是一样的。

七牛上传文件的过程并不是很难,但是在网上找了一圈没有找到合适的demo,所以在写完了之后立马记了下来以备以后不时之需。

上传demo github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,448评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • //我所经历的大数据平台发展史(三):互联网时代 • 上篇http://www.infoq.com/cn/arti...
    葡萄喃喃呓语阅读 51,180评论 10 200
  • 下午五点,一个神色迷茫的女人来到一家大排档门口。一个小孩路过,她突然猛地一踹,小孩倒地后没来得急哭就慌忙跑了,他哪...
    小鸡萝卜阅读 506评论 0 0
  • 董卿影响我开始我的读书生涯,心想就先从名著开始吧。就拿起了契诃夫的变色龙开看。读到拔萝卜一篇。读完后我发现不知道他...
    给给阅读 108评论 0 0