如今客戶端app開發(fā)中 涉及到上傳文件之類的功能大多數(shù)都是直接上傳到第三方服務(wù)器 而自己應(yīng)用服務(wù)器中只保留邏輯代碼不建議保留文件 本文我將介紹android客戶端直接上傳到阿里云服務(wù)器(OSS)流程 以及關(guān)鍵代碼
其實(shí)上傳到oss和上傳到應(yīng)用服務(wù)器方式大同小異 只是前提是必須配置OSS的一些參數(shù) 用OSS官方文檔提供的上傳請(qǐng)求對(duì)象來(lái)實(shí)現(xiàn)上傳
首先第一步 在項(xiàng)目中依賴OSS服務(wù)器SDK 也可以以添加jar包方式
dependencies {
compile 'com.aliyun.dpa:oss-android-sdk:2.4.5'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.okio:okio:1.9.0'
}
添加需要的權(quán)限 如果有則不需要重復(fù)添加
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
重新編譯項(xiàng)目 如果沒(méi)有報(bào)錯(cuò)便是集成成功
這個(gè)時(shí)候我們其實(shí)可以用SKD中一些類 和方法 來(lái)寫請(qǐng)求上傳的代碼了
但是在這之前 還有一步很重要的操作 就是和oss認(rèn)證 如果沒(méi)有認(rèn)證的話oss是不允許訪問(wèn)的 換句話來(lái)說(shuō) 如果服務(wù)器都不知道你是誰(shuí) 怎么可能讓你隨便訪問(wèn)它
而OSS認(rèn)證方式有2種
1:STS鑒權(quán)模式(官方推薦)
2:自簽名模式
第一種STS方式 是官方推薦的 安全度也很高 所以本文我采用這種方式來(lái)認(rèn)證
簡(jiǎn)單來(lái)說(shuō)這種方式 就是每次連接OSS之前 需要獲取一些配置參數(shù) 然后用這些參數(shù)來(lái)初始化OSS SKD 才能進(jìn)行上傳 而這個(gè)初始化就相當(dāng)于認(rèn)證
最關(guān)鍵的三個(gè)參數(shù)
AccessKeyId
SecretKeyId
SecurityToken
其中AccessKeyId和SecretKeyId是從阿里云服務(wù)器直接申請(qǐng)的 SecurityToken類似于一個(gè)標(biāo)識(shí) 重要的是這個(gè)參數(shù)是有時(shí)效性的 也就是說(shuō)會(huì)過(guò)期的
那么這三個(gè)參數(shù) 我這里是從應(yīng)用服務(wù)器 和后臺(tái)對(duì)接好 然后后臺(tái)幫我獲取的
大概流程是 我每次要上傳文件之前 先去自己應(yīng)用服務(wù)器 獲取這三個(gè)參數(shù)
而應(yīng)用服務(wù)器負(fù)責(zé)從阿里云oss服務(wù)器 獲取 在給我返回
返回的json格式如下

其中的Expiration字段是過(guò)期時(shí)間 暫時(shí)不理會(huì)
這三個(gè)參數(shù)拿到了之后我們就開始初始化oss SDK
比如某個(gè)頁(yè)面要有上傳文件功能
那么進(jìn)入這個(gè)頁(yè)面開始 我們首先就配置
一般在onStart方法中執(zhí)行就可以
private void getOssConfig(){
OkHttpUtils.ResultCallback<OssConfigBean> loadNewsCallback = new OkHttpUtils.ResultCallback<OssConfigBean>() {
@Override
public void onSuccess(OssConfigBean response) {
OSSCredentialProvider credentialProvider = new OSSStsTokenCredentialProvider(response.getAccessKeyId(), response.getAccessKeySecret(), response.getSecurityToken());
ClientConfiguration conf = new ClientConfiguration();
conf.setConnectionTimeout(15 * 1000); // 連接超時(shí),默認(rèn)15秒
conf.setSocketTimeout(15 * 1000); // socket超時(shí),默認(rèn)15秒
conf.setMaxConcurrentRequest(8); // 最大并發(fā)請(qǐng)求數(shù),默認(rèn)5個(gè)
conf.setMaxErrorRetry(2); // 失敗后最大重試次數(shù),默認(rèn)2次
oss = new OSSClient(getApplicationContext(), Urls.OSSENDOPINT, credentialProvider,conf);
}
@Override
public void onFailure(Exception e) {
}
};
OkHttpUtils.get(Urls.GETOSSDATACONFIG,loadNewsCallback);
}
代碼很簡(jiǎn)單 通過(guò)自己封裝的oKHttp執(zhí)行一個(gè)get請(qǐng)求 在請(qǐng)求成功的方法中來(lái)獲取三個(gè)參數(shù)
其中OssConfigBean是一個(gè)存放三個(gè)參數(shù)信息的Bean 因?yàn)榉祷氐氖莏son
封裝的okHttp中直接寫好了 Gson解析的代碼 所以泛型直接傳入該Bean就好
OssConfigBean如下:

接下來(lái)創(chuàng)建OSSCredentialProvider 對(duì)象 構(gòu)造中直接配置獲取的三個(gè)參數(shù)
創(chuàng)建ClientConfiguration 對(duì)象配來(lái)配置一些設(shè)置 如果不配置就是默認(rèn)設(shè)置
最后創(chuàng)建全局對(duì)象
oss = new OSSClient(getApplicationContext(), Urls.OSSENDOPINT, credentialProvider,conf);
其中有三個(gè)參數(shù) 第一個(gè)參數(shù)是上下文對(duì)象 不必多說(shuō)了
第二個(gè)是oss服務(wù)器的地址域名 是一個(gè)字符串
比如
http://oss-cn-beijing.aliyuncs.com
這個(gè)域名地址 創(chuàng)建oss服務(wù)器的時(shí)候就應(yīng)該是有的
第三個(gè)參數(shù) 就是我們剛才創(chuàng)建的OSSCredentialProvider 對(duì)象
第四個(gè)參數(shù)是一些配置信息 如果不要自定義配置 則不傳
到這里 其實(shí)如果參數(shù)沒(méi)有獲取錯(cuò)誤的話 就初始化成功了
其實(shí)我們最終就是得到了一個(gè)oss對(duì)象 因?yàn)榻酉聛?lái)我們要用這個(gè)對(duì)象來(lái)發(fā)起請(qǐng)求 所以都是步步相關(guān) 不難理解
接下來(lái)我們開始寫上傳文件代碼
首先創(chuàng)建一個(gè)PutObjectRequest對(duì)象
PutObjectRequest put = new PutObjectRequest(Urls.OSSBUCKET, OssPath,path);
這里需要三個(gè)參數(shù)
第一個(gè)參數(shù)是BUCKETNAME
這里要說(shuō)一下這個(gè)BUCKETNAME 是什么東西
我們通過(guò)app客戶端直接上傳到oss 并沒(méi)有指定我們要傳到服務(wù)器中哪個(gè)位置
而BUCKET我們就可以理解為是一個(gè)倉(cāng)庫(kù) BUCKETNAME 就是倉(cāng)庫(kù)名字
第一個(gè)參數(shù)需要的就是這個(gè)
我們上傳的文件 如果指定了是這個(gè)倉(cāng)庫(kù)名字 那么都會(huì)傳入到這里
下面是oss客戶端 給大家看一下方便理解

很顯然我這里的名字就是haoyuehuyu 所以我第一個(gè)參數(shù)填寫的就是這個(gè)
這個(gè)倉(cāng)庫(kù)在創(chuàng)建oss服務(wù)器的管理員可以創(chuàng)建
接下來(lái)看第二個(gè)參數(shù)
這個(gè)參數(shù)是objectKey
那么什么是objectKey呢?
其實(shí)就是一個(gè)自己編寫的字符串路徑 意思就是你想把要上傳的文件上傳到倉(cāng)庫(kù)中哪個(gè)位置 因?yàn)閭}(cāng)庫(kù)下面肯定要分好多文件夾的
比如我這里寫的就是
test/auth/當(dāng)前年月日/時(shí)分秒毫秒.jpg
所以如果上傳成功了
我們?cè)趥}(cāng)庫(kù)中按照文件夾路徑就可以看到這個(gè)文件

注意:這個(gè)路徑一定要指定到文件級(jí)別
接下來(lái)看第三個(gè)參數(shù)
uploadFilePath
實(shí)際上要的就是一個(gè)本地文件的路徑
注意:這里要的不是一個(gè)file文件 是一個(gè)本地圖片路徑
創(chuàng)建成功請(qǐng)求對(duì)象之后
我們可以選擇異步上傳和同步上傳
相信這里也不需要選擇了 因?yàn)閍ndroid根本不建議同步執(zhí)行網(wǎng)絡(luò)任務(wù)
所以來(lái)寫異步
通過(guò)剛才創(chuàng)建的 PutObjectRequest對(duì)象可以設(shè)置一個(gè)異步進(jìn)度回調(diào)
// 異步上傳時(shí)可以設(shè)置進(jìn)度回調(diào)
put.setProgressCallback(new OSSProgressCallback<PutObjectRequest>() {
@Override
public void onProgress(PutObjectRequest request, long currentSize, long totalSize) {
Log.d("PutObject", "當(dāng)前大小: " + currentSize + " 總大小: " + totalSize);
}
});
OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
@Override
public void onSuccess(PutObjectRequest request, PutObjectResult result) {
Log.d("PutObject", "UploadSuccess");
Log.d("ETag", result.getETag());
Log.d("RequestId", result.getRequestId());
}
@Override
public void onFailure(PutObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
// 請(qǐng)求異常
if (clientExcepion != null) {
// 本地異常如網(wǎng)絡(luò)異常等
clientExcepion.printStackTrace();
}
if (serviceException != null) {
// 服務(wù)異常
Log.e("ErrorCode", serviceException.getErrorCode());
Log.e("RequestId", serviceException.getRequestId());
Log.e("HostId", serviceException.getHostId());
Log.e("RawMessage", serviceException.getRawMessage());
}
}
});
這里通過(guò)最開始初始化時(shí)候創(chuàng)建好的oss對(duì)象來(lái)執(zhí)行異步上傳文件
很顯然 分為成功和失敗回調(diào)
但是如果仔細(xì)看 實(shí)際上是一個(gè)task任務(wù)
所以我們可以根據(jù)狀態(tài)
// task.cancel(); // 可以取消任務(wù)
// task.waitUntilFinished(); // 可以等待直到任務(wù)完成
其實(shí)到這里上傳到oss的全部?jī)?nèi)容已經(jīng)寫完了 過(guò)程當(dāng)中我遇到一個(gè)問(wèn)題
就是配置參數(shù) 都配置好了 代碼寫的也沒(méi)問(wèn)題 但是上傳的時(shí)候執(zhí)行上傳失敗的方法 打印log :Access denied by authorizer policy.
意思為是訪問(wèn)被授權(quán)人政策拒絕。
也就是說(shuō)我們客戶端沒(méi)問(wèn)題 但是oss服務(wù)器認(rèn)為我們還是沒(méi)權(quán)限訪問(wèn) 而這個(gè)權(quán)限策略 是oss管理員來(lái)編寫的 所以要和后臺(tái)人員協(xié)調(diào)好
如果遇到問(wèn)題了可以去這里排查一下具體是什么問(wèn)題
https://www.alibabacloud.com/help/zh/doc-detail/42777.htm
最后把上傳全部代碼貼上 這里我以上傳多圖舉例:
首先在初始化頁(yè)面的時(shí)候 初始化oss 服務(wù)器sdk
private void getOssConfig(){
OkHttpUtils.ResultCallback<OssConfigBean> loadNewsCallback = new OkHttpUtils.ResultCallback<OssConfigBean>() {
@Override
public void onSuccess(OssConfigBean response) {
OSSCredentialProvider credentialProvider = new OSSStsTokenCredentialProvider(response.getAccessKeyId(), response.getAccessKeySecret(), response.getSecurityToken());
ClientConfiguration conf = new ClientConfiguration();
conf.setConnectionTimeout(15 * 1000); // 連接超時(shí),默認(rèn)15秒
conf.setSocketTimeout(15 * 1000); // socket超時(shí),默認(rèn)15秒
conf.setMaxConcurrentRequest(8); // 最大并發(fā)請(qǐng)求數(shù),默認(rèn)5個(gè)
conf.setMaxErrorRetry(2); // 失敗后最大重試次數(shù),默認(rèn)2次
oss = new OSSClient(getApplicationContext(), Urls.OSSENDOPINT, credentialProvider,conf);
}
@Override
public void onFailure(Exception e) {
}
};
OkHttpUtils.get(Urls.GETOSSDATACONFIG,loadNewsCallback);
}
private void postToOss(String path) {
Log.i(TAG,"size:"+paths.size()+"-"+num+"--"+path);
num++;
if(progress==null){
progress = CustomProgress.show(this, "正在上傳", false, null);
}
String[] photoFileName = SynthesisUtils.getPhotoFileName();
String OssPath = "test/auth/"+photoFileName[0]+"/"+photoFileName[1];
PutObjectRequest put = new PutObjectRequest(Urls.OSSBUCKET, OssPath,path);
// 異步上傳時(shí)可以設(shè)置進(jìn)度回調(diào)
put.setProgressCallback(new OSSProgressCallback<PutObjectRequest>() {
@Override
public void onProgress(PutObjectRequest request, long currentSize, long totalSize) {
if(currentSize==totalSize){
progress.dismiss();
}
Log.d("PutObject", "當(dāng)前大小: " + currentSize + " 總大小: " + totalSize);
}
});
OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
@Override
public void onSuccess(PutObjectRequest request, PutObjectResult result) {
Log.d("PutObject", "UploadSuccess");
Log.d("ETag", result.getETag());
Log.d("RequestId", result.getRequestId());
// 這里進(jìn)行遞歸單張圖片上傳
if (num <= paths.size() - 1) {
posyToOss(paths.get(num));
}else{
//這里是出口
}
}
@Override
public void onFailure(PutObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
// 請(qǐng)求異常
if (clientExcepion != null) {
// 本地異常如網(wǎng)絡(luò)異常等
clientExcepion.printStackTrace();
}
if (serviceException != null) {
// 服務(wù)異常
Log.e("ErrorCode", serviceException.getErrorCode());
Log.e("RequestId", serviceException.getRequestId());
Log.e("HostId", serviceException.getHostId());
Log.e("RawMessage", serviceException.getRawMessage());
}
}
});
}
這里的progress是我自己封裝的上傳時(shí)候的一個(gè)等待彈框
SynthesisUtils.getPhotoFileName()這個(gè)方法中我會(huì)返回一個(gè)數(shù)組
數(shù)組【0】是當(dāng)前年月日的字符串 比如20180125
數(shù)組【1】是當(dāng)前時(shí)分秒毫秒字符串
也就是說(shuō)數(shù)組0下標(biāo)返回的是 文件夾名字 以當(dāng)前年月日命名
數(shù)組【1】返回的是文件名字
所以最終的路徑也就是 test/auth/年月日/時(shí)分秒+文件后綴名
也就是說(shuō)我們要把這個(gè)文件傳入到這個(gè)路徑
然后paths集合中我存放的是一選擇的圖片路徑
這里采用遞歸方式調(diào)用
代碼不難 最重要的是配置那些參數(shù)信息要搞懂 希望對(duì)大家有幫助 如果有問(wèn)題可以留言