七牛上傳圖片實(shí)踐

最近用到七牛上傳視頻和圖片的功能,于是去七牛官網(wǎng)看了文檔,寫(xiě)了一個(gè)上傳文件到七牛的demo,順便將寫(xiě)的過(guò)程中踩的一些坑記錄下來(lái)。

注冊(cè)七牛開(kāi)發(fā)者賬號(hào)

這個(gè)就不說(shuō)了,非常簡(jiǎn)單,注冊(cè)完之后,在左邊對(duì)象存儲(chǔ)項(xiàng)下新建一個(gè)存儲(chǔ)空間,填寫(xiě)存儲(chǔ)空間的名字,其他選項(xiàng)直接默認(rèn)就好了。

然后在左側(cè)個(gè)人中心-密鑰管理下查看自己的AccessKey和SecretKey,應(yīng)該很長(zhǎng)一串字母和數(shù)字組合,說(shuō)到這里不得不提一下我的坑。

我一開(kāi)始用的qq瀏覽器打開(kāi)的這個(gè)網(wǎng)頁(yè),可能是瀏覽器記住了我登錄的賬號(hào)密碼,然后不知道怎么回事將這里的兩個(gè)密鑰替換成了我的賬號(hào)密碼,我以為密鑰就是這個(gè),結(jié)果導(dǎo)致后面的token怎么都不對(duì),調(diào)試了半天總是bad token的錯(cuò)誤,整個(gè)人都不好了。所以能用谷歌瀏覽器還是用谷歌吧。

七牛云的任務(wù)就結(jié)束了,還是很簡(jiǎn)單的,只要?jiǎng)?chuàng)建一個(gè)存儲(chǔ)空間就好了。

獲得token

這一步是非常重要的,先不說(shuō)這個(gè)token是怎么得到的,因?yàn)樗惴ㄓ悬c(diǎn)復(fù)雜。我們直接用官網(wǎng)提供的工具先生成token供后面測(cè)試。最后會(huì)給出生成token的代碼。

token在線生成工具

點(diǎn)擊左上角的運(yùn)行


會(huì)在右下角看到這個(gè)


上一步我們已經(jīng)看到了兩個(gè)密鑰,將其分別輸入到AccessKey和SecretKey中,bucketname就是之前新建的那個(gè)存儲(chǔ)空間的名字,deadline為這個(gè)token的失效時(shí)間,其他可以不填,再點(diǎn)擊生成uptoken,就會(huì)根據(jù)這些內(nèi)容生成一個(gè)能供你測(cè)試的token。

這里注意一下你設(shè)置的token的有效時(shí)間,這里踩的一個(gè)坑是測(cè)試的時(shí)間太長(zhǎng),1個(gè)小時(shí)后token就失效了,所以自然上傳也會(huì)出錯(cuò),所以當(dāng)你碰到error:null body這個(gè)錯(cuò)誤時(shí)可以試試查看是不是token失效了

安卓端代碼

七牛安卓sdk文檔

添加依賴(lài)

這里直接添加比較新的7.3.x,后面會(huì)說(shuō)為什么

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

發(fā)送請(qǐng)求

首先看看文檔是怎么寫(xiě)的


要準(zhǔn)備3個(gè)內(nèi)容,data、key、token

  • token ,這個(gè)是最簡(jiǎn)單的,上一步生成的token直接可以拿過(guò)來(lái)用
  • key , 這個(gè)是指定你的圖片或其他文件上傳到七牛存儲(chǔ)空間后叫什么名字
  • data , 這個(gè)是要上傳的目標(biāo),可以是File類(lèi)型的文件,可以是String類(lèi)型的文件所在目錄,也可是是byte[]數(shù)組,上傳圖片的話肯定是用前兩種比較方便

回調(diào)函數(shù)的參數(shù)文檔中也有



在調(diào)試的時(shí)候可以將info打印到日志中,這樣如果沒(méi)有上傳成功也可以看到是什么原因。如果不主動(dòng)打印日志,那么上傳失敗,AS是不會(huì)打印任何錯(cuò)誤信息的。

這里以上傳手機(jī)中的一張圖片為例,圖片位置在手機(jī)sd卡目錄下test.jpg??煞抡瘴臋n寫(xiě)出以下代碼:

 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();

如果你這就急急忙忙準(zhǔn)備運(yùn)行,而又不打印任何日志,你會(huì)發(fā)現(xiàn)總是上傳失敗,并且還找不到原因。一開(kāi)始我也是一臉茫然,后來(lái)把info的信息打印出來(lái)之后看到access denied,立馬就知道了忘記設(shè)置權(quán)限了。

這里一定不要忘記給app加上讀取sd卡和聯(lián)網(wǎng)的權(quán)限

    <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"/>

加了權(quán)限這下該沒(méi)問(wèn)題了吧,說(shuō)不準(zhǔn),之前我們說(shuō)最好是用7.3.x版本,因?yàn)槲以谟?.0.x版本的時(shí)候遇到一個(gè)錯(cuò)誤

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

這個(gè)錯(cuò)誤跟地區(qū)有關(guān),在華南地區(qū)需要用up-z2.qiniu.com這個(gè)域名去訪問(wèn)。在文檔中之前被我忽略的一部分派上用場(chǎng)了

可以在創(chuàng)建UploadManager時(shí)給它傳入一些設(shè)置。

 Configuration config = new Configuration.Builder()
                        .zone(Zone.zone2) // 設(shè)置區(qū)域,指定不同區(qū)域的上傳域名、備用域名、備用IP。
                        .build();
                UploadManager uploadManager = new UploadManager(config);
 Configuration config = new Configuration.Builder()
                        .zone(Zone.httpAutoZone) // 自動(dòng)識(shí)別
                        .build();
                UploadManager uploadManager = new UploadManager(config);

點(diǎn)開(kāi)Zone我們就知道為什么設(shè)置這個(gè)能解決問(wèn)題了,出現(xiàn)的問(wèn)題就是讓我們使用up-z2.qiniu.com,而這個(gè)域名就在Zone.zone2里。

public abstract class Zone {

    /**
     * 華東機(jī)房, http
     */
    public static final Zone zone0 =
            createZone("upload.qiniu.com", "up.qiniu.com", "183.136.139.10", "115.231.182.136");

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

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

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

    /**
     * 自動(dòng)判斷機(jī)房, http
     */
    public static final AutoZone httpAutoZone = new AutoZone(false, null);

    /**
     * 自動(dòng)判斷機(jī)房, https
     */
    public static final AutoZone httpsAutoZone = new AutoZone(true, null);

    ...
}

結(jié)果

上傳成功后,在test-demo存儲(chǔ)空間中就會(huì)增加一張新的圖片了

獲取圖片的外鏈地址

打開(kāi)七牛云端,test-demo內(nèi)容管理可以查看已上傳的文件,點(diǎn)擊可以查看外鏈地址:


這個(gè)外鏈地址可以通過(guò)domain+key的組合來(lái)得到,domain也能在內(nèi)容管理查看到:


key就是上傳后在七牛存儲(chǔ)上的文件名。

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

token的加密算法

關(guān)于token是怎么生成的,可以看看官方文檔:

管理憑證token

這里只提供加密的代碼,可以對(duì)照著官方文檔看看是如何加密的:

    //七牛后臺(tái)的key
    private static String AccessKey = "AccessKey";
    //七牛后臺(tái)的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);// 有效時(shí)間為一個(gè)小時(shí)
            json.put("scope", bucketName);//存儲(chǔ)空間的名字
        } 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);
        // 根據(jù)給定的字節(jié)數(shù)組構(gòu)造一個(gè)密鑰,第二參數(shù)指定一個(gè)密鑰算法的名稱(chēng)
        SecretKeySpec secretKey = new SecretKeySpec(data, MAC_NAME);
        // 生成一個(gè)指定 Mac 算法 的 Mac 對(duì)象
        Mac mac = Mac.getInstance(MAC_NAME);
        // 用給定密鑰初始化 Mac 對(duì)象
        mac.init(secretKey);
        byte[] text = encryptText.getBytes(ENCODING);
        // 完成 Mac 操作
        return mac.doFinal(text);
    }

進(jìn)階用法:多圖上傳

上述代碼只能完成一張圖片的上傳,而做項(xiàng)目的時(shí)候經(jīng)常需要一次性上傳多張圖片,這里提供兩種實(shí)現(xiàn)方法。

定義的全局變量:

private String upToken = "你的token";

private Configuration config = new Configuration.Builder()
            .zone(Zone.zone2)
            .build();
private UploadManager uploadManager = new UploadManager(config);
private int[] i = {0};//循環(huán)變量,表示現(xiàn)在正在上傳第幾張圖片

循環(huán)實(shí)現(xiàn)

優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單
缺點(diǎn):順序不好控制

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();

運(yùn)行結(jié)果

說(shuō)明
問(wèn)題出來(lái)了,為什么打印了兩個(gè)“第2張上傳成功”?原因是因?yàn)槭茄h(huán)進(jìn)行網(wǎng)絡(luò)請(qǐng)求,效果如下圖:

網(wǎng)絡(luò)請(qǐng)求是需要消耗時(shí)間的,而循環(huán)在開(kāi)啟一個(gè)網(wǎng)絡(luò)請(qǐng)求upload1后就立馬進(jìn)入下一個(gè)循環(huán)了(而不會(huì)等待網(wǎng)絡(luò)請(qǐng)求返回結(jié)果)。這時(shí),循環(huán)變量已經(jīng)由0變?yōu)?,但upload1可能還沒(méi)有返回結(jié)果,這時(shí)開(kāi)啟第二個(gè)網(wǎng)絡(luò)請(qǐng)求upload2,所以等兩個(gè)請(qǐng)求都完成時(shí),循環(huán)變量已經(jīng)變?yōu)?,因而兩個(gè)請(qǐng)求返回結(jié)果時(shí)都會(huì)打印“第2張上傳成功”。

遞歸實(shí)現(xiàn)

優(yōu)點(diǎn):順序清晰
缺點(diǎn):代碼多,復(fù)雜

  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()) {
                        //七牛后臺(tái)對(duì)上傳的文件名是以時(shí)間戳來(lái)命名,以秒為單位,如果文件上傳過(guò)快,兩張圖片就會(huì)重名而上傳失敗,所以間隔1秒,保證上傳成功(具體會(huì)不會(huì)失敗呢?自己試一下看看)
                        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);
                }
            });

        }
    }

    //上傳單個(gè)文件
    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();
    }

    //上傳回調(diào)
    public interface UploadListener {
        void onUploadSuccess();

        void onUploadFail(Error error);
    }

    //上傳多張文件回調(diào)
    public interface UploadMutliListener {
        void onUploadMutliSuccess();

        void onUploadMutliFail(Error error);
    }

運(yùn)行結(jié)果

說(shuō)明
看代碼規(guī)模就能明顯感覺(jué)兩者的不同,遞歸上傳代碼量相比循環(huán)多了很多,不過(guò)它的優(yōu)點(diǎn)就是它的順序十分清晰,遞歸調(diào)用的邏輯圖可以描繪成以下的效果:

為每個(gè)網(wǎng)絡(luò)請(qǐng)求upload設(shè)置一個(gè)監(jiān)聽(tīng)器,只有當(dāng)upload1的請(qǐng)求成功返回結(jié)果,也就是第一張圖片上傳成功后,才開(kāi)啟第2個(gè)上傳圖片的請(qǐng)求upload2,如果有upload3同樣接在upload2的success后,這樣形成了鏈?zhǔn)浇Y(jié)構(gòu),能確保圖片一定是按照順序上傳的。

總結(jié)

七牛云不局限于上傳圖片,其實(shí)任何文件都可以,所以如果是上傳視頻的話,道理都是一樣的。

七牛上傳文件的過(guò)程并不是很難,但是在網(wǎng)上找了一圈沒(méi)有找到合適的demo,所以在寫(xiě)完了之后立馬記了下來(lái)以備以后不時(shí)之需。

上傳demo github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,326評(píng)論 25 708
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評(píng)論 19 139
  • //我所經(jīng)歷的大數(shù)據(jù)平臺(tái)發(fā)展史(三):互聯(lián)網(wǎng)時(shí)代 ? 上篇http://www.infoq.com/cn/arti...
    葡萄喃喃囈語(yǔ)閱讀 51,715評(píng)論 10 199
  • 下午五點(diǎn),一個(gè)神色迷茫的女人來(lái)到一家大排檔門(mén)口。一個(gè)小孩路過(guò),她突然猛地一踹,小孩倒地后沒(méi)來(lái)得急哭就慌忙跑了,他哪...
    小雞蘿卜閱讀 617評(píng)論 0 0
  • 董卿影響我開(kāi)始我的讀書(shū)生涯,心想就先從名著開(kāi)始吧。就拿起了契訶夫的變色龍開(kāi)看。讀到拔蘿卜一篇。讀完后我發(fā)現(xiàn)不知道他...
    給給閱讀 149評(píng)論 0 0

友情鏈接更多精彩內(nèi)容