用JavaFX做一个手机文件下载器

界面设想

  刚开始想借鉴windows的文件管理器,于是用工具提取了windows的部分图标。最后改成左侧显示文件夹树,右侧显示选中的文件夹的文件,下方显示下载和上传的进度。

手机文件下载器的界面

功能设想

  我的文件经常下载到"sdcard/download",所以就以它为根本路径。

  • 显示路径下文件夹和文件
  • 下载文件到当前路径
  • 上传文件
    • 当前路径
    • 指定路径
  • 显示上传下载进度

借助Spring

  很久以前,我想借助Spring的容器,查看了一些资料,发现大部分都是和SpringBoot搭配。于是这一次我也打算和SpringBoot搭配。通过寻找,找到了"springboot-javafx-support",但是它支持SpringBoot2,对Java和JavaFx的高版本支持也不太好。但我想尝试一下,看看能不能尽可能解决,因为我想用Java17、SpringBoot3、JavaFX17。最后,策略改变了,借鉴这个JavaFX17 整合 SpringBoot 3.1.0 (IDEA2023.1.2 ,jdk17.0.7 ) - 初步整合 - 知乎 (zhihu.com)

  后话:我怀疑创建了两个容器,因为我尝试了许多方法,只有一个类里的注入可以成功,其它地方都不能注入成功,容器里没有bean。所以最后主要放入了几个布局类。

准备环境

  JavaFX17、Java17、SpringBoot3.0.5、adb(改支持中文的1.0.31、原版1.0.41)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>17.0.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin><!-- 打包为exe的插件 -->
<groupId>io.github.fvarrui</groupId>
<artifactId>javapackager</artifactId>
<version>1.7.5</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
<configuration>
<!-- mandatory -->
<mainClass>cf.pengxiandyou.JavaFXApplication</mainClass>
<!-- optional -->
<bundleJre>true</bundleJre>
<generateInstaller>false</generateInstaller>
<administratorRequired>false</administratorRequired>
<platform>windows</platform>
<!-- <additionalResources>-->
<!-- <additionalResource>file path</additionalResource>-->
<!-- <additionalResource>folder path</additionalResource>-->
<!-- <additionalResource>...</additionalResource>-->
<!-- </additionalResources>-->
</configuration>
</execution>
</executions>
</plugin>

具体实现

  • 使用Font Awesome图标

    本来想像网页那样使用图标,但找到的一些方式都不太兼容。最后选择使用字体方式。

    1. Font Awesome下载字体等资源
    2. 借助文本编辑器和正则表达式,从文件里将图标名和Unicode 编码提取出来放到Map里
    3. 在Label里放入从Map里提取的Unicode
    4. 将字体文件读入,应该也可安装后通过名字读入
    5. 为Label设置字体
  • 执行adb命令

    借助ProcessBuilder执行命令,封装到工具类

    1. 参数为数组,每一个元素为命令的一部分,即将一条命令放入数组
    2. 通过BufferedReader读取结果的每一行,默认utf-8编码
    3. 将结果封装为List返回
  • 读取文件夹和文件信息(应该新建一个线程处理,我当时没有,不过还好不是传输大文件)

    1. 封装TreeItem,赋予文件夹属性
    2. 封装文件夹类,具有名称、路径、文件夹集合、文件集合、修改时间和大小的属性,添加增加大小的方法、文件夹集合和文件集合清除方法
    3. TreeItem作为根结点参数传入
    4. 借助工具类,执行adb shell ls -lt 手机path
    5. 使用正则分隔每一行信息提取需要的信息:drwxrws--- 2 u0_a185 media_rw 8192 2023-09-18 23:28 Browser,可以从权限处区分文件和文件夹,可以获得文件大小(文件夹大小不对,可以用内部文件累加),可以获得修改时间,可以获得名称
    6. 封装文件类,继承Label,具有名称、路径、大小、修改时间和后缀,定义一个初始化方法对Label进行一部分设置
    7. 根据每一行信息,创建TreeItem添加到根节点里,创建Label,添加点击下载事件,放到文件集合里
    8. 返回根节点TreeItem
  • 下载手机文件

    1. 封装下载功能到工具类
    2. 命令为adb pull "手机文件路径" .,路径的双引号还是保留好
    3. 从点击事件里获取手机文件路径
    4. 新开一个线程执行下载
  • 上传到手机

    由于拖拽的文件不一定只有一个,所以①命令拼接(不行)②一个文件一个命令

    1. 封装上传功能到工具类
    2. 传入存放路径和文件路径
    3. 命令为adb push "手机存放路径" 文件路径
    4. 遍历文件路径,新开线程执行命令
    • 当前文件夹
      1. FlowPane添加DragOver事件,修改接收模式
      2. FlowPane添加DragDropped事件,获取文件等信息,上传文件
    • 指定文件夹
      1. TreeView设置CellFactory
      2. 重写updateItem方法
      3. TreeCell添加DragOver事件,修改接收模式和给TreeView获得选择模型focus当前的TreeCell
      4. TreeCell添加DragDropped事件,获取文件和路径等信息,上传文件
  • 进度条

    1. HBox放入Label作为文件名和上传下载图标,放入ProgressBar作为进度条,放入Label作为进度条数值
    2. 在工具类封装进度条方法
    3. 创建内部类继承Task,实现call方法,在里面不停读取文件当前大小(封装)与文件总大小进行比较,更新进度
    4. 创建Service<Integer>,实现createTask,返回前面创建的Task
    5. 进度条绑定Task
    6. Task添加监听,修改进度LabelText
    7. 启动Service
  • 获取文件大小信息

    • 电脑文件
      1. (new File(path)).length();
    • 手机文件
      1. 命令为adb shell ls -lt "手机文件所在文件夹路径" | grep "文件名"
      2. 提取信息里的大小返回
  • 其它

    • 一些组件到处使用,所以放到容器,我通过配置类进行注入(笨办法是继承然后添加@Component

结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
D:.
│ JavaFXApplication.java

├───config
│ BeanConig.java

├───control
│ MyDirTreeItem.java
│ MyFileLabel.java

├───event
│ StageReadyEvent.java

├───factory
│ TreeViewCellFactory.java

├───listener
│ DragOverEventHander.java
│ PullFileFromAndroidEventHandler.java
│ PushFileToAndroidEventHandler.java
│ StageReadyListener.java
│ TreeViewItemSelectedListener.java

├───pojo
│ MyDir.java

├───utils
│ BeanUtils.java
│ PublicVariables.java
│ Utils.java

└───view
MainView.java

代码

gitee

git clone https://gitee.com/pengxiandyou/java-fx---mobile-file-download.git

注意