最近做的项目需要上传音视频以及图片,刚开始时候是用retrofit上传,说实话,那叫一个简单和爽啊,必须得赞一个。只需要将File往里面一放就ok了。哦哦,貌似有点跑题了。我应该说七牛云上传文件的。哈哈哈(这也足以说明retrofit上传是多么的便利了)。既然retrofit这么的方便简单,为啥还是要用七牛云呢?因为服务端害怕将来用户量大了,读写文件多了,服务器压力太大,万一搞瘫了肿么办?所以就选择了七牛云。
其实,这个集成也比较简单。因为客户端不需要申请任何的key,所以AndroidManifest里面也不需要任何配置了;只需要在module app的build.gradle里面添加 compile 'com.qiniu:qiniu-android-sdk:7.1.3'(当然这个版本可能更新,以七牛官网为准喽)就好了。客户端的上传流程取决于服务端的上传策略,刚开始我们采用的流程是 app申请上传凭证---上传文件至七牛----七牛回调到业务服务器---业务服务器处理完毕之后回调至七牛-------七牛再次返回给app结果。哇,是不是好麻烦,流程也是非常繁琐,万一里面某个环节出现了问题,都会导致上传失败。后来经过讨论采用了第二种流程方式:app申请上传凭证-----上传文件至七牛-----七牛文件存储完毕后返回给app----app再将结果上传给业务服务器;这样最大的就是简化了业务服务器和七牛的交互,并且我们的很多业务参数也不必通过七牛传递到我们自己的服务器了。可能有朋友会问,上面的上传凭证是什么,这个是服务端通过七牛生成的一个安全凭证,只有客户端拿着这个上传凭证上传文件才是有效的,否则七牛服务器是不接受的。也算是为了安全吧。
接下来看自己写的一个类
/**
* 向七牛上传文件,然后回调业务服务器
* Created by hexiappang_android on 16/11/28.
*/
public class UploadFileQiniuUtils1 {
private static int SUCCESSCOUNT = 0;//上传成功个数
private static int FAILCOUNT = 0;//失败个数
private static String stringKeys;//上传文件后的的key的拼接
private static UploadFileQiniuUtils1 uploadFileUtils;
private UploadManager uploadManager;//上传管理
private static String fileKey = null;//文件上传key;
//单例模式获取
public static UploadFileQiniuUtils1 getInstance(){
if(uploadFileUtils == null){
uploadFileUtils = new UploadFileQiniuUtils1();
}
//每次将原有变量清空
SUCCESSCOUNT = 0;
FAILCOUNT = 0;
stringKeys= "";
fileKey = null;
return uploadFileUtils;
}
/**
* 上传图片(可能会有多张。现在一次上传的的操作,只取一次上传凭证;然后会并发上传;)
*/
public void uploadMultiFile(final List<String> stringList,final OnResponse onResponse){
//获取上传凭证(这个是用retrofit从服务器获取的)
RetrofitNetHelper.getInstance().getUploadToken(new NetResponseSubscriber<String>(null) {
@Override
protected void onError(ApiException ex) {
onResponse.onFailure(ex.getDisplayMessage());
}
@Override
protected void onResponse(final String stringToken, String successMsg) {
//一个临时集合
final List<String> listTemp = new ArrayList<>();
//因为压缩图片也是一个耗时的过程,所以将其放入非ui线程中,压缩完成后,回调至ui线程处理(采用的是RxJava的线程切换)
Observable.create(new Observable.OnSubscribe<List<String>>() {
@Override
public void call(Subscriber<? super List<String>> subscriber) {
File fileTemp;
//循环传入文件路径list
for(int i = 0;i < stringList.size();i++){
fileTemp = new File(stringList.get(i));
//如果传入的文件存在
if(fileTemp.exists()){
//进行压缩(注意下面的文件名是用时间戳+当前的文件在集合中所在位置的拼接,而不是用了文件原有的名字,应的是服务端的要求)
fileTemp = ImageUtils.compressBitmap(stringList.get(i),800,480, System.currentTimeMillis()+i+".jpg");
//如果压缩过后的图片存在,那么添加入新的list
if(fileTemp != null && fileTemp.exists()){
listTemp.add(fileTemp.getAbsolutePath());
}
}
}
//将压缩结果发送至ui线程
subscriber.onNext(listTemp);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<String>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
//如果有错误,那么会回调给调用方
onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
}
@Override
public void onNext(final List<String> list) {
//如果上传凭证非空,那么执行上传操作
if(!TextUtils.isEmpty(stringToken)){
onResponse.onProgress(FormatUtils.resolveBuild("图片上传中","(1/",listTemp.size(),")"));
//因为网络请求是异步的,所以图片请求会是同时进行的。不能保证顺序操作
for(int j=0;j<list.size();j++){
//七牛云上传文件管理类
if (uploadManager == null) {
uploadManager = new UploadManager();
}
File uploadFile = new File(list.get(j));
//执行真正的上传操作
uploadManager.put(uploadFile, uploadFile.getName(), stringToken,
new UpCompletionHandler() {
@Override
public void complete(String key, ResponseInfo respInfo,
JSONObject jsonData) {
//各个文件之间的key用逗号分隔(这个是业务的需要,大家不一定这么写)
stringKeys = FormatUtils.resolveBuild(stringKeys,key,",");
//如果某个文件上传成功
if(respInfo.isOK()){
SUCCESSCOUNT++;//成功数量+1
}else{
FAILCOUNT++;//否则失败数量+1
}
//这个是将“上传进度”回调给调用方,其实也算是是假的,因为网络请求是几乎同时发出的,也是异步的,这里只是将上传完毕的记结果给回去了
onResponse.onProgress(FormatUtils.resolveBuild("图片上传中","(",SUCCESSCOUNT,"/",list.size(),")"));
//如果所有的图片都已经上传完毕了
if(SUCCESSCOUNT + FAILCOUNT == list.size()){
if(FAILCOUNT == 0){//如果失败的为0,则代表全部都成功,然后上传key
stringKeys = stringKeys.substring(0,stringKeys.length()-1);
}else{//只要有其中一张图片上传失败则认为上传失败
onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
}
}
}
}, null);
}
}else{
onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
}
}
});
}
});
}
/**
* 上传单一文件(主要是音视频)
*/
public void upLoadSingleFile(final String localPath, final OnResponse onResponse){
//在这里构造 上传文件的key,规则是前缀+时间戳+文件后缀名
if(type == Const.UPLOAD_AUDIO){
fileKey = System.currentTimeMillis()+".mp3";
}else if(type == Const.UPLOAD_VIDEO){
fileKey = System.currentTimeMillis()+".mp4";
}
final File file = new File(localPath);
if(file.exists()){
//这里有个文件最大字节的限制
if(file.length() > Const.MAX_FILE_BYTES){
onResponse.onFailure("上传文件太大,不能上传");
return;
}
//获取上传凭证
RetrofitNetHelper.getInstance().getUploadToken(new NetResponseSubscriber<String>(null) {
@Override
protected void onError(ApiException ex) {
onResponse.onFailure(ex.getDisplayMessage());
}
@Override
protected void onResponse(String s, String successMsg) {
if(!TextUtils.isEmpty(s)){
if (uploadManager == null) {
uploadManager = new UploadManager();
}
UploadOptions uploadOptions = new UploadOptions(null, null, false,
new UpProgressHandler() {
@Override
public void progress(String key, double percent) {
onResponse.onProgress(FormatUtils.resolveBuild("上传中(",FormatUtils.formatNumb(percent*100),"%)"));
}
}, null);
uploadManager.put(file, fileKey, s, new UpCompletionHandler() {
@Override
public void complete(String key, ResponseInfo respInfo,
JSONObject jsonData) {
if(respInfo.isOK()){
}else{
onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
}
}
}, uploadOptions);
}else{
onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
}
}
});
}else{
onResponse.onFailure("上传的视频不存在");
}
}
//对于文件上传结果的回调接口
public interface OnResponse{
void onSuccess();
void onFailure(String message);
/**
* 对于文件上传进度的提示(如果是图片则会有处理中。。。以及上传position/size的提示。对于音视频则会有x%的提示了)
*/
void onProgress(String message);
}
}
这个类主要是有两个方法了,一个是上传单一文件的,另外一个就是上传多张图片的,另外最后提供了将上传结果以及进度回调给调用方的接口(注释写的也比较详细了)。先说那个多张图片上传的,其实一次上传操作无论是单文件的还是多文件的,我们都是只获取一次上传凭证,因为服务器设置有失效时间,当然可以确定的是一次上传过程中,这个失效时间一定是不会到的啦。大家都知道网络请求是异步的,所以貌似很难保证顺序上传了,就是一张完了接着另外一张,暂时还没想到怎么来做。另外,即使可以做到一张一张传,那样岂不是很耗时呀,用户体验可能也不是很好了。所以最终就写成了上面的样子,多张图片请求几乎是同步的了。其实用了两个变量来判断是否上传完毕了,因为网络请求几乎同时发出。所以谁先回来都不知道。所以每次返回时候判断成功个数+失败个数是否等于要上传的文件数。如果相等了代表所有图片的上传都有一个返回结果了。另外如果失败个数不为0,则会认为上传失败,则会提示用户进行重新上传了。然后单一文件的上传其实和多文件上传几乎一样,只是我们为了上传的进度,多用了一个UploadOptions了,它能够将上传的进度以double值回调回来了。
最后还要说下上面的图片的命名,也就是我上传给七牛的key,就是uploadMagager的第二个参数了。由于当时服务端说他们指定的上传策略的缘故,我们客户端不能上传两个key一样的文件,要不然七牛会报错的,当时觉得不合理,但是服务端既然这么说了,说的那么真诚(哈哈,我好坏,我们服务端还是很给力的)然后我们就这样子做了。当时也没有验证,但是刚刚在写这篇文章的时候,我想我得试试,要不然看误导了看这篇文章的人就不好了。所以我就上传了好几张相同的图片,如下图
,然后我在七牛后台看到的是
同一个key(我用的是文件名)的文件就只有一个。所以得出的结论是,同一个文件上传多次,七牛只会存储一次了。我上面的代码暂时还没改。其实图片的名字被改的那么复杂一是满足业务需要,二是为了不重复,”不报错“。当然事实证明是没报错的。但是最终我的这个代码是否要改,还得周一去跟服务端商量。其实,这样用文件名作为key,至少能够减少一些存储空间了。
好了,这篇博客先暂时这个样子了。如有错误,欢迎指正。多谢。祝大家周末愉快。