使用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); } } }); }