Android+Spring Boot 选择+上传+下载文件

Stella981
• 阅读 750

2021.02.03更新

1 概述

前端Android,上传与下载文件,使用OkHttp处理请求,后端使用Spring Boot,处理Android发送来的上传与下载请求。这个其实不难,就是特别多奇奇怪怪的坑,因此,就一句话, 希望各位读者能少走弯路。

2 环境

  • Win10
  • Spring Boot 2.2.2
  • IDEA 2019.3.1
  • Android Studio 3.6
  • Tomcat 9.0.30

3 Android

3.1 准备工作

3.1.1 新建工程

这次用一个全新的例子写博客,因此从新建工程开始:

Android+Spring Boot 选择+上传+下载文件

Android+Spring Boot 选择+上传+下载文件

3.1.2 AndroidManifest.xml

加入

<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请求的权限

3.1.3 build.gradle

加入

compileOptions {
    sourceCompatibility = 1.8
    targetCompatibility = 1.8
}

这个是支持JDK8的。

还有这两个OkHttpConscrypt依赖,最新版本戳这里这里查看。

implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'org.conscrypt:conscrypt-android:2.5.1'

3.1.4 上传文件

手动上传一些文件到AVD中,为下一步选择与上传文件做准备,先把这个窗口工具栏打开:

Android+Spring Boot 选择+上传+下载文件

打开后,点击在右侧栏中的Device File Explorer

Android+Spring Boot 选择+上传+下载文件

然后选择sdcard文件夹上传文件即可,其他文件夹一般没有权限:

Android+Spring Boot 选择+上传+下载文件

3.1.5 布局

组件如下:

  • 三个button:上传/下载/选择文件
  • 一个EditText:上传文件名与下载文件名
  • 一个ImageView:显示下载的图片

Android+Spring Boot 选择+上传+下载文件

3.2 选择文件

3.2.1 申请权限

首先申请动态读写文件权限(其实选择文件只需要读权限,因为后面的下载需要写权限所以这里就一起申请了):

Android+Spring Boot 选择+上传+下载文件

使用checkSelfPermission检查权限,参数为一个Context+StringString表示相应的权限:

  • 如果有权限就会返回PackageManager.PERMISSION_GRANTED
  • 没有就返回PackageManager.PERMISSION_DENIED

没有就利用requestPermissions()申请,参数为Content+String[]+intString[]表示要申请的所有权限,int是一个requestCode

3.2.2 Intent选择文件

Android+Spring Boot 选择+上传+下载文件

新建一个Intent后,设置选择类型,然后就重写onActivityResult

Android+Spring Boot 选择+上传+下载文件

这是简化了的处理,因为选择的是图片,选择其他文件的话可以参照这里

其中path是选择的文件的路径,读者可能有疑问下面的路径是怎么拼接的:

String path = dir.toString().substring(0,dir.toString().indexOf("0")+2) +
    DocumentsContract.getDocumentId(uri).split(":")[1];

其实是拼凑过来的,因为这是图片,是下面版本的简化版:

Android+Spring Boot 选择+上传+下载文件

3.3 上传文件

Android+Spring Boot 选择+上传+下载文件

参数为文件路径与文件名,然后使用OkHttpClient,因为是文件,用的请求体是MultipartBody,增加一个叫fileFormDataPart与一个叫filenameFormDataPart,然后使用execute()发送请求,body()获取响应内容。

这里假设了后端响应一个布尔,表示上传成功或失败,**url的话使用了本地的路径,注意不能是localhost,使用内网ip,然后还要与后端对应**。

3.4 下载文件

Android+Spring Boot 选择+上传+下载文件

参数为一个文件名,根据这个文件名返回对应的文件,返回一个File。这里请求体可以选择FormBodyMultipartBody,因为这是一个文件名参数,这里笔者为了统一就选择了MultipartBody,使用FormBody的话,只需要将RequestBody的那一行改为:

RequestBody body = new FormBody.Builder().add("filename",filename).build();

有了请求体后发送请求获取响应体,进而获取输入流,然后首先需要判断是否为空,但不能直接这样判断:

inputStream == null

因为后端是这样的:

Android+Spring Boot 选择+上传+下载文件

从响应体获取的inputStream肯定不为null,需要先进行一次读取(也就是判断里面的文件是否为null),若为null的话删除这个文件,不为null的话继续读取并写入文件。

4 Spring Boot

4.1 准备工作

4.1.1 新建工程

Android+Spring Boot 选择+上传+下载文件

打包方式JAR/WAR均可:

Android+Spring Boot 选择+上传+下载文件

两个,一个Spring Web+一个模板引擎,用于显示视图,如果不需要显示可以不选。

Android+Spring Boot 选择+上传+下载文件

4.1.2 application.properties

Android+Spring Boot 选择+上传+下载文件

配置了三项:

  • 上传文件总大小限制
  • 单个文件大小限制
  • 上传路径

4.1.3 pom.xml

这里其实不需要干什么,只是如果下载依赖慢的话,可以这样设置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

4.2 处理上传文件

Android+Spring Boot 选择+上传+下载文件

首先对应的POST映射路径为/upload,与Android端的路径对应,然后需要一个表示文件的MultipartFile与一个表示文件名的String,判断这两个是否为空。

接着如果上传的文件夹不存在则先创建,存在的话直接进行复制,然后根据复制成功或失败返回布尔值。复制使用了Files.copy(),第一个InputStream为上传文件的输入流,第二个Path为存储文件的路径,resolve(filename)相当于在上传目录下的filename文件。

4.3 处理下载文件

下载的话可以选择使用GETPOST请求,这里选择了POST请求,因为Android端是POST请求,需要对应。

Android+Spring Boot 选择+上传+下载文件

首先根据文件名获取对应文件,判断文件是否存在后返回一个ResponseEntity,需要设定content-typebody,content-type,根据需要设置即可。这里是图片,默认.jpg.pngbody的话使用FileSystemResource,直接new一个放进body即可。

如果不存在相应的文件则返回null,这里需要注意一下前端的判断,不能直接判断ResponseBody是否为null

5 测试

5.1 Postman测试

5.1.1 上传测试

Headers中设置了Content-Typemultipart/form-data后:

Android+Spring Boot 选择+上传+下载文件

body添加一个叫file的文件与一个叫filename的字符串表示文件名:

Android+Spring Boot 选择+上传+下载文件

发送,返回true

Android+Spring Boot 选择+上传+下载文件

服务器端有输出提示:

Android+Spring Boot 选择+上传+下载文件

查看文件夹:

Android+Spring Boot 选择+上传+下载文件

5.1.2 下载测试

file参数关掉,保留filename,修改路径:

Android+Spring Boot 选择+上传+下载文件

然后发送,postman可以直接显示图片:

Android+Spring Boot 选择+上传+下载文件

5.2 Android端测试

5.2.1 上传测试

Android+Spring Boot 选择+上传+下载文件

后端提示:

Android+Spring Boot 选择+上传+下载文件

查看文件夹:

Android+Spring Boot 选择+上传+下载文件

5.2.2 下载测试

输入文件名后直接下载:

Android+Spring Boot 选择+上传+下载文件

默认的话是放在这里,按需要更改位置即可,注意加上写权限:

Android+Spring Boot 选择+上传+下载文件

若看不到文件选择synchronize即可。

Android+Spring Boot 选择+上传+下载文件

6 部署到服务器

服务器用的是Tomcat,需要修改一些Spring Boot的部分。

6.1 部署

6.1.1 改变打包方式

pom.xmljar改成war

Android+Spring Boot 选择+上传+下载文件

6.1.2 去除Tomcat依赖

pom.xml加入:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

6.1.3 修改Main

修改Main类,让其继承SpringBootServletInitializer,重写configure(),同时main()保持不变。

修改前:

Android+Spring Boot 选择+上传+下载文件

修改后:

Android+Spring Boot 选择+上传+下载文件

6.1.4 修改路径

这个按需要修改即可,在这里不需要,注意就是@PostMapping@GetMapping等都是相对于

tomcat/webapps/项目/

目录下的。

6.1.5 设置打包名字

<build>加上<finalName>

Android+Spring Boot 选择+上传+下载文件

6.1.6 打包

Android+Spring Boot 选择+上传+下载文件

6.1.7 上传到服务器

打包后的文件放在target下,使用scp上传即可。这里是本地的Tomcat,就这接移动war了。

6.1.8 运行

开启Tomcat,双击startup.bat即可:

Android+Spring Boot 选择+上传+下载文件

Linux的话:

cd xxxx/tomcat/bin
./startup.sh

6.2 测试

在测试前需要确保没有占用相应端口。默认8080,也就是说,如果不改端口的话,需要关闭IDEA运行中的SpringBoot应用。

6.2.1 Postman测试

上传测试,注意需要改路径,加上打包项目名,ip的话可以使用localhost或者内网ip

Android+Spring Boot 选择+上传+下载文件

服务器这边收到了,因为上传路径只是直接写名字,因此会与startup.bat同一路径:

Android+Spring Boot 选择+上传+下载文件

下载测试:

Android+Spring Boot 选择+上传+下载文件

服务器的输出:

Android+Spring Boot 选择+上传+下载文件

6.2.2 Android端测试

Android端需要修改路径即可,加上war打包的名字。

Android+Spring Boot 选择+上传+下载文件

这里打包的名字是kr,直接加上即可:

Android+Spring Boot 选择+上传+下载文件

上传那里也是要加上,然后:

Android+Spring Boot 选择+上传+下载文件

服务器的输出:

Android+Spring Boot 选择+上传+下载文件

查看文件:

Android+Spring Boot 选择+上传+下载文件

7 一些坑

7.1 权限

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

7.2 路径

需要保证下面几个路径正确,还有可读,可写等:

  • URL路径不能错
  • 前端上传文件的路径
  • 后端接收前端上传文件的路径
  • 后端发送前端需要下载的文件的路径
  • 前端接收下载文件的路径

7.3 有关HTTP的问题

7.3.1 OkHttpstream关闭

Android+Spring Boot 选择+上传+下载文件

若前端是这样写的,在工具类中返回了之后Response已经关闭,因此需要读取输入流之类的需要先读取再返回,而不是返回一个ResponseBodyInputStream进行读取,否则会提示"closed"

7.3.2 HTTP

Android P开始默认禁用HTTP,因此可以使用HTTPS或者在AndroidManifest.xml中允许HTTP连接:

<application android:usesCleartextTraffic="true">

7.3.3 线程

网络请求不能在主线程中,新开一个线程即可。

7.3.4 AVD

若检查过了服务器与Android端没问题,那么有可能是AVD的问题,解决方法很简单,卸载,重启AVD,注意一定要卸载再重启。

7.4 ip

在本地测试的话后端可以直接localhost,在Android端不能直接localhost,可以使用ipconfigifconfig查看内网ip,输入内网ip即可。

Android+Spring Boot 选择+上传+下载文件

若在服务器上测试直接使用服务器ip

7.5 判空处理

对于前端,应该判断存储路径是否为空,是否为null等,再传给后端。对于后端,要判断文件是否存在等,不存在就返回null,这时又需要前端进行判断返回的null,在下载文件时,虽然对不存在的文件后端返回null,但是,前端收到的是一个InputStream,不能直接判断是否为null,需要先读取一次,再进行剩下的读取:

Android+Spring Boot 选择+上传+下载文件

8 源码

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这