当前位置: 首页 > 科技观察

Android使用Retrofit2实现多文件上传实践

时间:2023-03-13 04:20:26 科技观察

前段时间翻译了FutureStudio的Retrofit2教程,从中学习了一些Retrofit2的使用方法。如果你打算最近开始学习,我的博客上有一个Retrofit教程。或许你可以参考以下内容:Retrofit教程。本文作为阶段性总结,将使用Python中的Flask框架实现Android端的多文件上传功能。读者没用过Python中的Flask也没关系,只能看Android客户端部分,毕竟客户端工程师只用API也是可以的。一、实验结果Android端操作截图服务器端接收到的图片二、服务器端实战服务器端负责接收并保存客户端上传的图片,并提供访问图片的能力。第三方库的语言,有很多web服务框架,如Flask、Django等,笔者使用的是Flask框架。Flask是一个微型框架。实现小功能非常方便。作者实现的多文件上传功能不超过30行。让我们详细看一下。2.1环境安装笔者使用的Python版本是3.4,大家可以去Python3.4下载,选择适合自己系统的版本下载。请搜索完整的Python教程。Python安装完成后,需要安装服务端程序依赖库。通过pip安装:pipinstallFlaskpipinstallwerkzeug2.2程序实现首先要引入依赖库:fromflaskimportFlask,request,send_from_directory,jsonifyimportosfromwerkzeugimportsecure_filename本实验需要上传文件,需要限制上传文件的文件类型和文件名,防止某些程序破坏server运行,以及一些非法文件名如:filename="../../../../home/username/.bashrc"如果黑客能够操作此类文件,将对服务器系统造成致命打击.所以werkzeug提供了secure_filename来验证上传文件文件名的合法性。判断文件后缀是否合法ALLOWED_EXTENSIONS=set(['png','jpg','jpeg','gif'])defallowed_file(filename):return'.'infilenameandfilename.rsplit('.',1)[1]在ALLOWED_EXTENSIONS接收上传文件的函数代码如下:@app.route('/upload',methods=['POST'])defupload_file():ifrequest.method=='POST':forkinrequest.files:file=request.files[k]image_urls=[]iffileandallowed_file(file.filename):filename=secure_filename(file.filename)file.save(os.path.join(app.config['IMAGE_FOLDER'],filename))image_urls.append("images/%s"%filename)returnjsonify({"code":1,"image_urls":image_urls})Flask支持GET、POST、PUT、DELETE等HTTP请求方式,并使用装饰器进行修饰,类似Java中的注解概念,/upload是客户端请求的相对地址,请求方式仅限于POST。根据request内置对象,可以访问客户端发送的文件,查看文件并保存到本地,其中image_urls是上传图片的相对地址数组。***将图片的地址以json格式返回给客户端。完整的服务端代码如下:fromflaskiimportFlask,request,send_from_directory,jsonifyimportosfromwerkzeugimportsecure_filenameapp=Flask(__name__)app.config['IMAGE_FOLDER']=os.path.abspath('.')+'\\images\\'ALLOWED_EXTENSIONS=set(['png','jpg','jpeg','gif'])defalowed_file(文件名):return'.'infilenameandfilename.rsplit('.',1)[1]inALLOWED_EXTENSIONS@app.route('/upload',methods=['POST'])defupload_file():ifrequest.method=='POST':forkinrequest.files:file=request.files[k]print(file)image_urls=[]iffileandallowed_file(file.filename):filename=secure_filename(file.filename)file.save(os.path.join(app.config['IMAGE_FOLDER'],filename))image_urls.append("images/%s"%filename)returnjsonify({"code":1,"image_urls":image_urls})#让文件映射访问,否则默认只能访问static文件夹下的文件@app.route("/images/",methods=['GET'])defimages(imgname):returnsend_from_directory(app.config['IMAGE_FOLDER'],imgname)if__name__=="__main__":#Check如果IMAGE_FOLDER存在的话notos.path.exists(app.config['IMAGE_FOLDER']):os.mkdir(app.config['IMAGE_FOLDER'])app.run("192.168.1.102",debug=True)这里有个小技巧,写完服务端代码后,就可以使用Postman进行测试了。测试成功后,即可开发客户端程序。需要选择图片,这里推荐一个仿微信的图片选择库ImagePicker。3.1添加依赖库图片加载库我喜欢用Glidecompile'c??om.squareup.retrofit2:retrofit:2.1.0'compile'c??om.squareup.retrofit2:converter-gson:2.1.0'compile'c??om.github.bumptech.glide:glide:3.7.0'compile'c??om.lzy.widget:imagepicker:0.4.1'3.2程序实现没有接触过Retrofit2的可以来我的BlogRetrofit教程了解。Retrofit2是一个支持RESTfulAPI的请求库。其实只是对API请求方法的封装。真正的网络请求是由OkHttp发送的。Retrofit2一般定义一个ServiceGenerator类,用于动态生成Retrofit对象。publicclassServiceGenerator{publicstaticfinalStringAPI_BASE_URL="http://192.168.1.102:5000/";privatestaticOkHttpClient.BuilderhttpClient=newOkHttpClient.Builder();privatestaticRetrofit.Builderbuilder=newRetrofit.Builder().baseUrl(API_BASEFterFteractory).add(ver;publicstaticScreateService(ClassserviceClass){Retrofitretrofit=builder.client(httpClient.build()).build();returnretrofit.create(serviceClass);}}具体的API操作由FlaskClient接口操作,publicinterfaceFlaskClient{//上传图片@Multipart@POST("/upload")CalluploadMultipleFiles(@PartMapMapfiles);}上传文件需要注解@Multipart关键字,@POST表示HTTP请求方式为POST,/upload是请求服务器的相对地址,uploadMultipleFiles是自定义的方法名,参数是Mapfiles是由多个文件组成的Map对象,@PartMap表示这是一个多文件上传,如果一个file可以使用@PartMultipartBody.Part文件,该方法的返回类型默认为Response。由于我们开发过服务器端,我们知道服务器端的返回数据格式是Json,所以我们为返回数据格式新建一个UploadResut类。publicclassUploadResult{publicintcode;//1publicListimage_urls;}界面布局如图:点击Upload按钮执行上传操作,核心方法:publicvoiduploadFiles(){if(imagesList.size()==0){吐司。makeText(MainActivity.this,"无法选择图片",Toast.LENGTH_SHORT).show();return;}Mapfiles=newHashMap<>();finalFlaskClientservice=ServiceGenerator.createService(FlaskClient.class);for(inti=0;icall=service.uploadMultipleFiles(files);call.enqueue(newCallback(){@OverridepublicvoidonResponse(Callcall,Responseresponse){if(response.isSuccessful()&&response.body().code==1){Toast.makeText(MainActivity.this,"上传成功",Toast.LENGTH_SHORT).show();Log.i("orzangleli","--------------------上传成功----------------------");Log.i("orzangleli","基址为:"+ServiceGenerator.API_BASE_URL);Log.i("orzangleli","图片的相对地址为:"+listToString(response.body().image_urls,','));Log.i("orzangleli","--------------------结束----------------------");}}@OverridepublicvoidonFailure(Callcall,Throwablet){Toast.makeText(MainActivity.this,"上传失败",Toast.LENGTH_SHORT).show();}});}其中,构建和上传多个文件的方法参数比较关键。MediaType.parse(imagesList.get(i).mimeType)获取图片的mimeType。如果规格有误,可能会上传失败Mapfiles=newHashMap<>();finalFlaskClientservice=ServiceGenerator.createService(FlaskClient.class);for(inti=0;i