七牛上傳圖片實踐

最近用到七牛上傳視頻和圖片的功能,于是去七牛官網看了文檔,寫了一個上傳文件到七牛的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閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,761評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,829評論 18 139
  • //我所經歷的大數據平臺發展史(三):互聯網時代 ? 上篇http://www.infoq.com/cn/arti...
    葡萄喃喃囈語閱讀 51,284評論 10 200
  • 下午五點,一個神色迷茫的女人來到一家大排檔門口。一個小孩路過,她突然猛地一踹,小孩倒地后沒來得急哭就慌忙跑了,他哪...
    小雞蘿卜閱讀 535評論 0 0
  • 董卿影響我開始我的讀書生涯,心想就先從名著開始吧。就拿起了契訶夫的變色龍開看。讀到拔蘿卜一篇。讀完后我發現不知道他...
    給給閱讀 113評論 0 0