最近用到七牛上傳視頻和圖片的功能,于是去七牛官網看了文檔,寫了一個上傳文件到七牛的demo,順便將寫的過程中踩的一些坑記錄下來。
注冊七牛開發者賬號
這個就不說了,非常簡單,注冊完之后,在左邊對象存儲項下新建一個存儲空間,填寫存儲空間的名字,其他選項直接默認就好了。
然后在左側個人中心-密鑰管理下查看自己的AccessKey和SecretKey,應該很長一串字母和數字組合,說到這里不得不提一下我的坑。
我一開始用的qq瀏覽器打開的這個網頁,可能是瀏覽器記住了我登錄的賬號密碼,然后不知道怎么回事將這里的兩個密鑰替換成了我的賬號密碼,我以為密鑰就是這個,結果導致后面的token怎么都不對,調試了半天總是bad token的錯誤,整個人都不好了。所以能用谷歌瀏覽器還是用谷歌吧。
七牛云的任務就結束了,還是很簡單的,只要創建一個存儲空間就好了。
獲得token
這一步是非常重要的,先不說這個token是怎么得到的,因為算法有點復雜。我們直接用官網提供的工具先生成token供后面測試。最后會給出生成token的代碼。
點擊左上角的運行
會在右下角看到這個
上一步我們已經看到了兩個密鑰,將其分別輸入到AccessKey和SecretKey中,bucketname就是之前新建的那個存儲空間的名字,deadline為這個token的失效時間,其他可以不填,再點擊生成uptoken,就會根據這些內容生成一個能供你測試的token。
這里注意一下你設置的token的有效時間,這里踩的一個坑是測試的時間太長,1個小時后token就失效了,所以自然上傳也會出錯,所以當你碰到error:null body
這個錯誤時可以試試查看是不是token失效了
安卓端代碼
添加依賴
這里直接添加比較新的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是怎么生成的,可以看看官方文檔:
這里只提供加密的代碼,可以對照著官方文檔看看是如何加密的:
//七牛后臺的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,所以在寫完了之后立馬記了下來以備以后不時之需。