前端android,上传与下载文件,使用OkHttp处理请求,后端使用spring boot+MVC,处理android发送来的上传与下载请求.这个其实不难,就是特别多奇奇怪怪的坑,所以,但愿看到的,不要像笔者这样踩的那么痛苦了...css
此次用一个全新的例子写博客,所以重新建工程开始:前端
加入java
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:usesCleartextTraffic="true">
网络权限,读写SD卡权限,固然还有容许http请求的权限.linux
加入android
compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 }
这个是支持JDK8的. 还有这两个okhttp与conscrypt,最新版本okhttp能够在这里查看,最新版本conscrypt在这里:git
implementation 'com.squareup.okhttp3:okhttp:4.3.1' implementation 'org.conscrypt:conscrypt-android:2.2.1'
手动上传一些文件到AVD设备,为下一步选择与上传文件作准备,先把这个窗口工具栏打开:github
打开后,打开在右侧栏中的Device File Explorer:web
而后选择sdcard文件夹上传文件便可,其余文件夹通常没有权限.spring
添加三个button(上传/下载/选择文件),一个EditText(上传文件名与下载文件名),一个ImageView(显示下载的图片).apache
直接拖放改一下id.
首先申请动态读写文件权限(其实选择文件只须要读权限,由于后面的下载须要写权限因此这里就一块儿申请了):
使用checkSelfPermission检查权限,参数为一个Context与String,String表示相应的权限,若是有了这个权限就会返回
PackageManager.PERMISSION_GRANTED
没有就会返回
PackageManager.PERMISSION_DENIED
没有就利用requestPermissions()申请,参数为Content,String[],int,String[]表示要申请的全部权限,int是一个requestCode.
新建一个Intent后,设置选择类型,而后就重写onActivityResult:
这是简化了的处理,由于选择的是图片,选择其余文件的话能够参照这里.
其中path是选择的文件的路径,可能你会问:
String path = dir.toString().substring(0,dir.toString().indexOf("0")+2) + DocumentsContract.getDocumentId(uri).split(":")[1];
这个是怎么来的,实际上是拼凑过来的,由于这是图片,是这个的简化版:
(博客在这里)
参数为文件路径与文件名,而后使用OkHttpClient,由于是文件,用的body是MultipartBody,增长一个叫file的FormDataPart与一个叫filename的FormDataPart.而后使用execute()发送请求,body()获取响应内容,这里假设了后端响应一个布尔,表示上传成功或失败,url的话使用了本地的路径,注意不能是localhost,使用内网ip,而后还要与后端对应.
参数为一个文件名,根据这个文件名返回对应的文件,返回一个File,这里请求体能够选择FormBody或MultipartBody,由于这是一个文件名参数,这里笔者为了统一就选择了MultipartBody,使用FormBody的话,只须要将RequestBody的那一行改成:
RequestBody body = new FormBody.Builder().add("filename",filename).build();
有了请求体后发送请求获取响应体,进而获取输入流,而后首先须要判断是否为空,但不能直接这样判断:
inputStream == null
由于后端是这样的:
从响应体获取的inputStream确定不为null,须要先进行一次读取(也就是判断里面的文件是否为null),若为null的话删除这个文件,不为null的话继续读取并写入文件.
用的是IDEA,其余IDE请自行搜索如何新建一个SpringBoot工程.
打包的话能够jar或war,不用部署的话jar便可,要部署的话后期也能够改为war.
两个,一个Spring Web,用于MVC等,一个模板引擎,用于显示视图,若是不须要显示能够不选.
做为一个示例demo,属性就直接在application.properties中配置了,实际状况请在相应的配置文件中配置相应属性.
须要配置上传文件的大小限制与上传文件夹的路径.
这里其实不须要干什么,只是若是下载依赖慢的话,能够这样设置settings.xml文件,在<mirrors>中加上:
<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror> <mirror> <id>uk</id> <mirrorOf>central</mirrorOf> <name>Human Readable Name for this Mirror.</name> <url>http://uk.maven.org/maven2/</url> </mirror> <mirror> <id>CN</id> <name>OSChina Central</name> <url>http://maven.oschina.net/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror> <mirror> <id>nexus</id> <name>internal nexus repository</name> <!-- <url>http://192.168.1.100:8081/nexus/content/groups/public/</url>--> <url>http://repo.maven.apache.org/maven2</url> <mirrorOf>central</mirrorOf> </mirror>
windows用户的话这个文件在
C:\Users\{username}\.m2\settings.xml
linux的话在
~/.m2/settings.xml
首先对应的post映射路径为/upload,与android端的路径对应,而后须要一个表示文件的MultipartFile与一个表示文件名的String,判断这两个是否为空后,若是上传的文件夹不存在则先建立,存在的话直接进行复制,而后根据复制成功或失败返回布尔值.复制使用了Files.copy(),第一个InputStream为上传文件的输入流,第二个Path为存储文件的路径,resolve(filename)至关于在上传目录下的filename文件.输出的话建议使用日志代替.
下载的话能够选择使用get或post请求,这里选择了post请求,由于android端是post请求,须要对应.get请求的话能够从浏览器发起.
首先根据文件名获取对应文件,判断文件是否存在后返回一个ResponseEntity,须要设定content-type与body,content-type根据须要设置便可,这里是图片,默认.jpg或.png,body的话使用FileSystemResource,直接new一个放进body便可.
若是不存在相应的文件则返回null,这里须要注意一下前端的判断,不能直接判断ResponseBody是否为null.
postman只能测试与后端的链接,上传等是否有问题,能够用来定位后端的问题.
再Headers中设置了Content-Type为multipart/form-data后:
在body添加一个叫file的文件与一个叫filename的字符串表示文件名:
发送,返回true.
服务器端有输出提示:
查看文件夹:
把file参数关掉,保留filename,修改路径.
而后发送,postman能够直接显示图片:
后端提示:
查看文件夹:
输入文件名后直接下载:
默认的话是放在这里,按须要更改位置便可,注意加上写权限:
若看不到文件选择synchronize便可.
服务器用的是tomcat,须要修改一些Spring Boot的部分.
pom.xml中jar改为war.
pom.xml加入:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
修改Main类,让其继承SpringBootServletInitializer,重载configure(),同时main()保持不变.
修改前:
修改后:
这个按须要修改便可,在这里不须要,注意就是@PostMapping,@GetMapping等都是相对于
tomcat/webapps/项目/
目录下的.
build加上<finalName>.
打包后的文件放在target下,使用scp上传便可,这里是本地的tomcat,就这接移动war了.
开启tomcat,双击startup.bat便可.
linux的话:
cd xxxx/tomcat/bin ./startup.sh
在测试前须要确保没有占用相应端口,默认8080,也就是说,若是不改端口的话,须要关闭IDEA运行中的SpringBoot应用.
上传测试,注意须要改路径,加上打包项目名,ip的话可使用localhost或者内网ip.
服务器这边收到了,由于上传路径只是直接写名字,所以会与startup.bat同一路径.
下载测试:
服务器的输出:
android端须要修改路径便可,加上war打包的名字.
这里打包的名字是kr,直接加上便可.
上传那里也是要加上,而后:
服务器的输出:
查看文件:
android须要读权限才能读取文件并上传,须要写权限才能保存从服务器返回的文件,在AndroidManifest.xml中加入:
<manifest>... <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application>...</application>
这是外部设备的读写权限.固然,加入这个还不能访问,由于,android6.0之后还须要动态申请权限,因此:
String [] permission = new String[]{ "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE" }; if( ActivityCompat.checkSelfPermission(this,permission[0]) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this,permission[1]) != PackageManager.PERMISSION_DENIED ) { ActivityCompat.requestPermissions(this,permission,1); }
须要保证下面几个路径正确,还有可读,可写等:
若前端是这样写的,在工具类中返回了以后Response已经关闭,所以须要读取输入流之类的须要先读取再返回,而不是返回一个ResponseBody或InputStream进行读取,不然会提示"closed".
Android P开始默认禁用http,所以可使用https或者在AndroidManifest.xml中容许http链接:
<application android:usesCleartextTraffic="true">
网络请求不能在主线程中,新开一个线程便可.
若检查过了服务器与android端没问题,那么有多是AVD的问题,解决方法很简单,卸载,重启AVD,注意必定要卸载再重启.
在本地测试的话后端能够直接localhost,在android端不能直接localhost,可使用ipconfig或ifconfig查看内网ip,输入内网ip便可.
若在服务器上测试直接使用服务器ip.
对于前端,应该判断存储路径是否为空,是否为null等,再传给后端,对于后端,要判断文件是否存在等,不存在就返回null,这时又须要前端进行判断返回的null,在下载文件时,虽然对不存在的文件后端返回null,可是,前端收到的是一个InputStream,不能直接判断是否为null,须要先读取一次,再进行剩下的读取: