1. 背景
有个Android演示项目需要上传图片/视频,并要求上传的图片/视频能在公网访问。
公司的服务器只能提供内网访问,如果公网进行访问,需要申请很繁琐的流程,且需要加上验签等各种条件,相当麻烦。
所以决定让Android App直接对接阿里云OSS,直接将文件上传到阿里云OSS上。
目标 :
实现 Android App上传图片/视频
到阿里云OSS上,并使该图片/视频
能直接在公网访问。
因为是Demo项目,我们为了快速实现功能,没有使用STS鉴权模式,而是直接将
AccessKeyId
和AccessKeySecret
直接保存在终端用来加签请求,官方建议使用STS鉴权模式或自签名模式来提高安全性。
2. 接入阿里OSS
2.1 OSS信息
首先,我们需要知道OSS相关的信息
val accessKey = "你的AccessKey"
val secretKey = "你的SecretKey"
val endpoint = "oss-cn-hangzhou.aliyuncs.com"
val bucketName = "my-dev-oss"
其中,endpoint 可以从访问域名和数据中心 对照表中获取
2.2 添加权限
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
2.3 添加阿里云OSS依赖
在 build.gradle
中添加
implementation 'com.liulishuo.filedownloader:library:1.7.7'
2.4 构造OSSCredentialProvider
val accessKey = "你的AccessKey" //TODO 这里输入你的OSS的accessKey
val secretKey = "你的SecretKey" //TODO 这里输入你的OSS的secretKey
val provider: OSSCustomSignerCredentialProvider =
object : OSSCustomSignerCredentialProvider() {
override fun signContent(content: String): String {
// 此处本应该是客户端将contentString发送到自己的业务服务器,然后由业务服务器返回签名后的content。关于在业务服务器实现签名算法
// 详情请查看http://help.aliyun.com/document_detail/oss/api-reference/access-control/signature-header.html。客户端
// 的签名算法实现请参考OSSUtils.sign(accessKey,screctKey,content)
return OSSUtils.sign(
accessKey,
secretKey,
content
)
}
}
2.5 构造OSSClient
val endpoint = "oss-cn-hangzhou.aliyuncs.com"
val oss: OSS = OSSClient(applicationContext, endpoint, provider)
2.6 构造上传请求
val bucketName = "my-dev-oss"
val uploadFile = File(getExternalFilesDir("fodder"), "photo2.jpeg")
val put = PutObjectRequest(bucketName, /*objectKey*/uploadFile.name,/*uploadFilePath*/uploadFile.path)
// 异步上传时可以设置进度回调。
put.progressCallback =
OSSProgressCallback { request, currentSize, totalSize ->
Log.d(
"PutObject",
"currentSize: $currentSize totalSize: $totalSize"
)
}
2.7 上传文件
val task: OSSAsyncTask<*> = oss.asyncPutObject(
put,
object : OSSCompletedCallback<PutObjectRequest?, PutObjectResult> {
override fun onSuccess(request: PutObjectRequest?, result: PutObjectResult) {
Log.d("PutObject", "UploadSuccess")
Log.d("ETag", result.eTag)
Log.d("RequestId", result.requestId)
}
override fun onFailure(
request: PutObjectRequest?,
clientExcepion: ClientException?,
serviceException: ServiceException?
) {
// 请求异常。
clientExcepion?.printStackTrace()
if (serviceException != null) {
// 服务异常。
Log.e("ErrorCode", serviceException.getErrorCode())
Log.e("RequestId", serviceException.getRequestId())
Log.e("HostId", serviceException.getHostId())
Log.e("RawMessage", serviceException.getRawMessage())
}
}
})
// task.cancel(); // 可以取消任务。
// task.waitUntilFinished(); // 等待上传完成。
3. 访问上传的图片/视频
3.1 对外访问的URL
上传文件成功后,要怎么访问呢,一番搜索,似乎找到了答案。
文中说,如果文件的读写权限ACL为公共读,那么文件URL的格式为https://BucketName.Endpoint/ObjectName
已知,我们OSS
的BucketName
为my-dev-oss
,Endpoint
为oss-cn-hangzhou.aliyuncs.com
,由于Bucket
下没有文件夹,所以ObjectName
直接为文件名
,所以URL为http://my-dev-oss.oss-cn-hangzhou.aliyuncs.com/photo2.jpeg
我们在浏览器中用这个URL进行访问
结果提示我们You have no right to access this object because of bucket acl.
似乎是缺少权限 ?
3.2 设置上传文件的权限
查阅了文档,才发现,有一个参数是用来设置上传文件的访问权限的
这就需要我们修改2.6 构造上传请求
中的代码了
val bucketName = "devops-dev-oss"
val put = PutObjectRequest(bucketName, /*objectKey*/uploadFile.name,/*uploadFilePath*/uploadFile.path)
val metaData = ObjectMetadata()
metaData.setHeader("x-oss-object-acl", "public-read")
put.metadata = metaData
我们将上传文件的访问权限设置为public-read
公共读,然后再进行一次上传
3.3 再次访问URL
我们修改好代码,重新上传文件后,再次在浏览器中使用3.1 对外访问的URL
中的URL http://my-dev-oss.oss-cn-hangzhou.aliyuncs.com/photo2.jpeg
进行访问
可以看到,文件直接就被下载下来了,这样我们就可以直接公网访问上传的文件了
4. 上传多个文件
4.1 阿里云OSS上传工具类
我们会有同时上传多个文件的需求,对此进行了封装
object AliOssUtils {
val accessKey = "你的AccessKey"
val secretKey = "你的SecretKey"
val endpoint = "oss-cn-hangzhou.aliyuncs.com"
val bucketName = "my-dev-oss"
init {
OSSLog.enableLog() //调用此方法开启日志
}
/**
* 上传多个文件
*/
fun uploadFiles(
application: Context,
uploadFiles: List<File>,
callBack: (List<String>) -> Unit
) {
if (uploadFiles.isEmpty()) return
var uploadResultList = ArrayList<String>()
var uploadFileCount = uploadFiles.size
var uploadResultCount = AtomicInteger(0)
uploadFiles.forEach {
uploadFile(application, it) { url ->
val count = uploadResultCount.addAndGet(1)
if (url != null && !TextUtils.isEmpty(url)) {
uploadResultList.add(url)
}
if (count == uploadFileCount) {
callBack.invoke(uploadResultList)
}
}
}
}
/**
* 上传单个文件
*/
fun uploadFile(
application: Context,
uploadFile: File,
callBack: ((String?) -> Unit)? = null
) {
val provider: OSSCustomSignerCredentialProvider =
object : OSSCustomSignerCredentialProvider() {
override fun signContent(content: String): String {
// 此处本应该是客户端将contentString发送到自己的业务服务器,然后由业务服务器返回签名后的content。关于在业务服务器实现签名算法
// 详情请查看http://help.aliyun.com/document_detail/oss/api-reference/access-control/signature-header.html。客户端
// 的签名算法实现请参考OSSUtils.sign(accessKey,screctKey,content)
return OSSUtils.sign(
accessKey,
secretKey,
content
)
}
}
val oss: OSS = OSSClient(application, endpoint, provider)
// 构造上传请求。
val put = PutObjectRequest(
bucketName, /*objectKey*/
uploadFile.name,/*uploadFilePath*/
uploadFile.path
)
val metaData = ObjectMetadata()
//设置为公共读资源
metaData.setHeader("x-oss-object-acl", "public-read")
put.metadata = metaData
// 异步上传时可以设置进度回调。
put.progressCallback = OSSProgressCallback { request, currentSize, totalSize ->
Log.d(
"PutObject",
"currentSize: $currentSize totalSize: $totalSize"
)
}
val task: OSSAsyncTask<*> = oss.asyncPutObject(
put,
object : OSSCompletedCallback<PutObjectRequest?, PutObjectResult> {
override fun onSuccess(request: PutObjectRequest?, result: PutObjectResult) {
Log.d("PutObject", "UploadSuccess")
Log.d("ETag", result.eTag)
Log.d("RequestId", result.requestId)
val publicUrl =
"http://${bucketName}.${endpoint}/${uploadFile.name}"
Log.d("PutObject", "publicUrl:$publicUrl")
Log.d("ZZZZ", "publicUrl:$publicUrl")
callBack?.invoke(publicUrl)
}
override fun onFailure(
request: PutObjectRequest?,
clientExcepion: ClientException?,
serviceException: ServiceException?
) {
// 请求异常。
clientExcepion?.printStackTrace()
if (serviceException != null) {
// 服务异常。
Log.e("ErrorCode", serviceException.getErrorCode())
Log.e("RequestId", serviceException.getRequestId())
Log.e("HostId", serviceException.getHostId())
Log.e("RawMessage", serviceException.getRawMessage())
}
callBack?.invoke(null)
}
})
// task.cancel(); // 可以取消任务。
// task.waitUntilFinished(); // 等待上传完成。
}
}
4.2 使用工具类
使用起来也很简单
val selectList = ArrayList<File>()
selectList.add(File(getExternalFilesDir("fodder"), "photo2.jpeg"))
selectList.add(File(getExternalFilesDir("fodder"), "GOPR0045.JPG"))
selectList.add(File(getExternalFilesDir("fodder"), "GX010114.MP4"))
AliOssUtils.uploadFiles(this, selectList) {
for (url in it) {
Log.i("ZZZZ", "url:$url")
}
}
5.其他
参考
阿里云OSS Android 接入文档
阿里云OSS Android Demo
【OSS】上传Object后如何获取访问URL?
Android 快速集成阿里云OSS服务2020
Android 阿里云oss sdk接入 优化并增加多文件上传