使用Android的OkHttp包实现基于HTTP协议的文件上传下载
OkHttp的HTTP连接基础
虽然在使用OkHttp发送HTTP请求时只需要提供URL即可,OkHttp在实现中需要综合考虑3种不同的要素来确定与HTTP服务器之间实际建立的HTTP连接。这样做的目的是为了达到最佳的性能。
首先第一个考虑的要素是URL本身。URL给出了要访问的资源的路径。比如URLhttp://www.baidu.com所对应的是百度首页的HTTP文档。在URL中比较重要的部分是访问时使用的模式,即HTTP还是HTTPS。这会确定OkHttp所建立的是明文的HTTP连接,还是加密的HTTPS连接。
第二个要素是HTTP服务器的地址,如baidu.com。每个地址都有对应的配置,包括端口号,HTTPS连接设置和网络传输协议。同一个地址上的URL可以共享同一个底层TCP套接字连接。通过共享连接可以有显著的性能提升。OkHttp提供了一个连接池来复用连接。
第三个要素是连接HTTP服务器时使用的路由。路由包括具体连接的IP地址(通过DNS查询来发现)和所使用的代理服务器。对于HTTPS连接还包括通讯协商时使用的TLS版本。对于同一个地址,可能有多个不同的路由。OkHttp在遇到访问错误时会自动尝试备选路由。
当通过OkHttp来请求某个URL时,OkHttp首先从URL中得到地址信息,再从连接池中根据地址来获取连接。如果在连接池中没有找到连接,则选择一个路由来尝试连接。尝试连接需要通过DNS查询来得到服务器的IP地址,也会用到代理服务器和TLS版本等信息。当实际的连接建立之后,OkHttp发送HTTP请求并获取响应。当连接出现问题时,OkHttp会自动选择另外的路由进行尝试。这使得OkHttp可以自动处理可能出现的网络问题。当成功获取到HTTP请求的响应之后,当前的连接会被放回到连接池中,提供给后续的请求来复用。连接池会定期把闲置的连接关闭以释放资源。
文件上传和下载实例:
1.不带参数上传文件
/**
*上传文件
*@paramactionUrl接口地址
*@paramfilePath本地文件地址
*/
public<T>voidupLoadFile(StringactionUrl,StringfilePath,finalReqCallBack<T>callBack){
//补全请求地址
StringrequestUrl=String.format("%s/%s",BASE_URL,actionUrl);
//创建File
Filefile=newFile(filePath);
//创建RequestBody
RequestBodybody=RequestBody.create(MEDIA_OBJECT_STREAM,file);
//创建Request
finalRequestrequest=newRequest.Builder().url(requestUrl).post(body).build();
finalCallcall=mOkHttpClient.newBuilder().writeTimeout(50,TimeUnit.SECONDS).build().newCall(request);
call.enqueue(newCallback(){
@Override
publicvoidonFailure(Callcall,IOExceptione){
Log.e(TAG,e.toString());
failedCallBack("上传失败",callBack);
}
@Override
publicvoidonResponse(Callcall,Responseresponse)throwsIOException{
if(response.isSuccessful()){
Stringstring=response.body().string();
Log.e(TAG,"response----->"+string);
successCallBack((T)string,callBack);
}else{
failedCallBack("上传失败",callBack);
}
}
});
}
2.带参数上传文件
/**
*上传文件
*@paramactionUrl接口地址
*@paramparamsMap参数
*@paramcallBack回调
*@param<T>
*/
public<T>voidupLoadFile(StringactionUrl,HashMap<String,Object>paramsMap,finalReqCallBack<T>callBack){
try{
//补全请求地址
StringrequestUrl=String.format("%s/%s",upload_head,actionUrl);
MultipartBody.Builderbuilder=newMultipartBody.Builder();
//设置类型
builder.setType(MultipartBody.FORM);
//追加参数
for(Stringkey:paramsMap.keySet()){
Objectobject=paramsMap.get(key);
if(!(objectinstanceofFile)){
builder.addFormDataPart(key,object.toString());
}else{
Filefile=(File)object;
builder.addFormDataPart(key,file.getName(),RequestBody.create(null,file));
}
}
//创建RequestBody
RequestBodybody=builder.build();
//创建Request
finalRequestrequest=newRequest.Builder().url(requestUrl).post(body).build();
//单独设置参数比如读取超时时间
finalCallcall=mOkHttpClient.newBuilder().writeTimeout(50,TimeUnit.SECONDS).build().newCall(request);
call.enqueue(newCallback(){
@Override
publicvoidonFailure(Callcall,IOExceptione){
Log.e(TAG,e.toString());
failedCallBack("上传失败",callBack);
}
@Override
publicvoidonResponse(Callcall,Responseresponse)throwsIOException{
if(response.isSuccessful()){
Stringstring=response.body().string();
Log.e(TAG,"response----->"+string);
successCallBack((T)string,callBack);
}else{
failedCallBack("上传失败",callBack);
}
}
});
}catch(Exceptione){
Log.e(TAG,e.toString());
}
}
3.带参数带进度上传文件
/**
*上传文件
*@paramactionUrl接口地址
*@paramparamsMap参数
*@paramcallBack回调
*@param<T>
*/
public<T>voidupLoadFile(StringactionUrl,HashMap<String,Object>paramsMap,finalReqProgressCallBack<T>callBack){
try{
//补全请求地址
StringrequestUrl=String.format("%s/%s",upload_head,actionUrl);
MultipartBody.Builderbuilder=newMultipartBody.Builder();
//设置类型
builder.setType(MultipartBody.FORM);
//追加参数
for(Stringkey:paramsMap.keySet()){
Objectobject=paramsMap.get(key);
if(!(objectinstanceofFile)){
builder.addFormDataPart(key,object.toString());
}else{
Filefile=(File)object;
builder.addFormDataPart(key,file.getName(),createProgressRequestBody(MEDIA_OBJECT_STREAM,file,callBack));
}
}
//创建RequestBody
RequestBodybody=builder.build();
//创建Request
finalRequestrequest=newRequest.Builder().url(requestUrl).post(body).build();
finalCallcall=mOkHttpClient.newBuilder().writeTimeout(50,TimeUnit.SECONDS).build().newCall(request);
call.enqueue(newCallback(){
@Override
publicvoidonFailure(Callcall,IOExceptione){
Log.e(TAG,e.toString());
failedCallBack("上传失败",callBack);
}
@Override
publicvoidonResponse(Callcall,Responseresponse)throwsIOException{
if(response.isSuccessful()){
Stringstring=response.body().string();
Log.e(TAG,"response----->"+string);
successCallBack((T)string,callBack);
}else{
failedCallBack("上传失败",callBack);
}
}
});
}catch(Exceptione){
Log.e(TAG,e.toString());
}
}
4.创建带进度RequestBody
/**
*创建带进度的RequestBody
*@paramcontentTypeMediaType
*@paramfile准备上传的文件
*@paramcallBack回调
*@param<T>
*@return
*/
public<T>RequestBodycreateProgressRequestBody(finalMediaTypecontentType,finalFilefile,finalReqProgressCallBack<T>callBack){
returnnewRequestBody(){
@Override
publicMediaTypecontentType(){
returncontentType;
}
@Override
publiclongcontentLength(){
returnfile.length();
}
@Override
publicvoidwriteTo(BufferedSinksink)throwsIOException{
Sourcesource;
try{
source=Okio.source(file);
Bufferbuf=newBuffer();
longremaining=contentLength();
longcurrent=0;
for(longreadCount;(readCount=source.read(buf,2048))!=-1;){
sink.write(buf,readCount);
current+=readCount;
Log.e(TAG,"current------>"+current);
progressCallBack(remaining,current,callBack);
}
}catch(Exceptione){
e.printStackTrace();
}
}
};
}
5.不带进度文件下载
/**
*下载文件
*@paramfileUrl文件url
*@paramdestFileDir存储目标目录
*/
public<T>voiddownLoadFile(StringfileUrl,finalStringdestFileDir,finalReqCallBack<T>callBack){
finalStringfileName=MD5.encode(fileUrl);
finalFilefile=newFile(destFileDir,fileName);
if(file.exists()){
successCallBack((T)file,callBack);
return;
}
finalRequestrequest=newRequest.Builder().url(fileUrl).build();
finalCallcall=mOkHttpClient.newCall(request);
call.enqueue(newCallback(){
@Override
publicvoidonFailure(Callcall,IOExceptione){
Log.e(TAG,e.toString());
failedCallBack("下载失败",callBack);
}
@Override
publicvoidonResponse(Callcall,Responseresponse)throwsIOException{
InputStreamis=null;
byte[]buf=newbyte[2048];
intlen=0;
FileOutputStreamfos=null;
try{
longtotal=response.body().contentLength();
Log.e(TAG,"total------>"+total);
longcurrent=0;
is=response.body().byteStream();
fos=newFileOutputStream(file);
while((len=is.read(buf))!=-1){
current+=len;
fos.write(buf,0,len);
Log.e(TAG,"current------>"+current);
}
fos.flush();
successCallBack((T)file,callBack);
}catch(IOExceptione){
Log.e(TAG,e.toString());
failedCallBack("下载失败",callBack);
}finally{
try{
if(is!=null){
is.close();
}
if(fos!=null){
fos.close();
}
}catch(IOExceptione){
Log.e(TAG,e.toString());
}
}
}
});
}
6.带进度文件下载
/**
*下载文件
*@paramfileUrl文件url
*@paramdestFileDir存储目标目录
*/
public<T>voiddownLoadFile(StringfileUrl,finalStringdestFileDir,finalReqProgressCallBack<T>callBack){
finalStringfileName=MD5.encode(fileUrl);
finalFilefile=newFile(destFileDir,fileName);
if(file.exists()){
successCallBack((T)file,callBack);
return;
}
finalRequestrequest=newRequest.Builder().url(fileUrl).build();
finalCallcall=mOkHttpClient.newCall(request);
call.enqueue(newCallback(){
@Override
publicvoidonFailure(Callcall,IOExceptione){
Log.e(TAG,e.toString());
failedCallBack("下载失败",callBack);
}
@Override
publicvoidonResponse(Callcall,Responseresponse)throwsIOException{
InputStreamis=null;
byte[]buf=newbyte[2048];
intlen=0;
FileOutputStreamfos=null;
try{
longtotal=response.body().contentLength();
Log.e(TAG,"total------>"+total);
longcurrent=0;
is=response.body().byteStream();
fos=newFileOutputStream(file);
while((len=is.read(buf))!=-1){
current+=len;
fos.write(buf,0,len);
Log.e(TAG,"current------>"+current);
progressCallBack(total,current,callBack);
}
fos.flush();
successCallBack((T)file,callBack);
}catch(IOExceptione){
Log.e(TAG,e.toString());
failedCallBack("下载失败",callBack);
}finally{
try{
if(is!=null){
is.close();
}
if(fos!=null){
fos.close();
}
}catch(IOExceptione){
Log.e(TAG,e.toString());
}
}
}
});
}
7.接口ReqProgressCallBack.java实现
publicinterfaceReqProgressCallBack<T>extendsReqCallBack<T>{
/**
*响应进度更新
*/
voidonProgress(longtotal,longcurrent);
}
8.进度回调实现
/**
*统一处理进度信息
*@paramtotal总计大小
*@paramcurrent当前进度
*@paramcallBack
*@param<T>
*/
private<T>voidprogressCallBack(finallongtotal,finallongcurrent,finalReqProgressCallBack<T>callBack){
okHttpHandler.post(newRunnable(){
@Override
publicvoidrun(){
if(callBack!=null){
callBack.onProgress(total,current);
}
}
});
}