Android FTP服务器Demo开发实录:局域网文件共享从选型到排坑
简介Android平台上的FTP服务器Demo基于Apache FtpServer实现适合需要让手机或平板作为FTP服务端、进行无线文件传输或远程管理的开发者。工程依赖commons-net-ftp、ftpserver-core、mina-core等常用库可快速移植到自有项目中。压缩包共28个文件体积仅1.72MB包含源码、配置与资源有java源码和properties文件用于核心逻辑与参数设置多个xml如AndroidManifest、布局文件支撑应用界面与权限声明jar包为运行必需的第三方库png则提供不同分辨率的应用图标。整体结构清晰已有591人学习浏览适合有一定Android基础、想要实现局域网FTP共享或了解FtpServer集成方式的开发者。通过该Demo可以学会添加服务端依赖、配置端口与根目录、处理权限及启动服务的完整流程并可直接基于此扩展用户认证、匿名访问或目录管理功能。 直接跑题太远先回归正题。我最近往一台旧Android平板上装了个自写的FTP服务器Demo彻底解决了“手机和电脑之间来回传文件还得插数据线”的麻烦。这个项目说起来很简单在Android应用里内置一个FTP服务器手机/平板开热点或连同一个WiFi之后电脑上用FileZilla这类客户端直接连过去就能拉文件、传文件不需要root也不需要装第三方全家桶。这篇文章就来记录一下这个Android FTP服务器Demo从选型、写代码到联调排错的全过程适合刚接触Android网络编程、想做个局域网文件共享工具的人参考。1. 动手之前先把“FTP服务器”的选型想清楚1.1 三种常见的实现路径对比想在一台Android设备上提供FTP服务技术路径其实有三条我实际对比下来发现各自的差别非常大实现路径优点缺点适合场景直接用开源App改SwiFTP这类项目代码现成上手快代码老、结构乱适配新系统要改一堆只想快速出个工具不关心代码质量集成Apache FtpServer库协议完整、线程模型成熟、有TLS支持需要自己写接入层、做生命周期管理这就是我最终选的路正规且可控自己写Socket协议栈完全可控、可玩性高协议实现工作量巨大被动FTP数据连接很容易踩坑纯粹为了学习不适合做交付Demo如果你打开GitHub搜“Android FTP server”八成会先看到SwiFTP这个老项目。它确实能跑但看源码会发现很多逻辑写死在旧API上比如直接用Environment.getExternalStorageDirectory()去枚举根目录放到Android 11以上直接被分区存储搞死。我更推荐第二条路不要重复造轮子但也不要去改造一个轮子过于古老的方案而是直接引入Apache Mina的FtpServer库把它当作一个普通依赖接进自己的项目。1.2 为什么选Apache FtpServer而不是SwiFTP我自己的判断标准其实很简单一个“Demo”可以功能简化但底层选型一定要稳。SwiFTP的问题不止是UI过时而是它没有跟上Android权限模型的演进。相比之下Apache FtpServer是Java后端生态中很成熟的FTP协议实现从FTP命令解析、主动/被动模式数据连接到用户权限管理都是现成的而且它天然不依赖Android API你只需要在Android这边做好它的“宿主环境”就行。还有一点很关键Apache FtpServer直接支持FTP over TLSFTPS。虽然很多局域网场景用明文FTP也够但FileZilla客户端默认会尝试加密连接你没有一个带TLS的服务器客户端就会弹“不安全的服务器”提示。用Apache库至少可以加上TLS支持后面客户端连接时就不用被这个警告卡住心态。2. 工程准备Android Studio环境和项目骨架2.1 Android Studio版本与Gradle配置建议我用的是目前稳定版Android StudioGradle版本8.xJDK 17。新建一个空工程之后建议直接把minSdk设到26或更高因为Android 8.0以后后台服务的限制更加严格FTP服务器这种常驻型服务必须配合前台服务才能稳定运行与其在低版本上做兼容不如直接面向新版本开发。核心依赖是在build.gradle里加一行implementation org.apache.ftpserver:ftpserver-core:1.2.0注意这个库是纯Java的不依赖Android特有类所以不会有兼容性问题。另外还需要在AndroidManifest.xml里声明权限uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS /ACCESS_WIFI_STATE是为了拿到本机WiFi的IP地址POST_NOTIFICATIONS是给前台服务发通知用的。如果你确实想允许用户访问公共存储目录那还得加MANAGE_EXTERNAL_STORAGE权限但实测下来这个权限在Google Play政策里是非常敏感的Demo里我建议直接把FTP的根目录指向应用自己的files目录既够用又完全绕开存储权限争议。2.2 Android 11下的存储空间适配这部分是很多人在Demo阶段最容易踩的坑。Android 10开始强制执行分区存储App不能随意读其他应用的文件公共存储目录的访问也要申请额外权限。你要是直接把home目录设置成/storage/emulated/0/在新系统上非常容易遇到能连接、但列不出文件的情况。我建议的做法是File homeDir new File(context.getExternalFilesDir(null), ftp_share); if (!homeDir.exists()) { homeDir.mkdirs(); }应用专属外部存储目录不需要任何额外权限而且卸载应用时系统会自动清理。对于局域网传文件这种场景FTP共享根目录指向这里用户从电脑传过来的文件都落在getExternalFilesDir里面完全够用。3. 核心实现把FTP服务跑起来3.1 初始化FtpServerFactory连接监听器Apache FtpServer的入口是FtpServerFactory它做的事情分两块配置监听端口配置用户管理器。核心代码这里直接分享出来FtpServerFactory serverFactory new FtpServerFactory(); ListenerFactory listenerFactory new ListenerFactory(); listenerFactory.setPort(2121); // 避开21端口避免权限冲突 listenerFactory.setServerAddress(0.0.0.0); listenerFactory.setIdleTimeout(300); // 开启被动模式端口范围 DataConnectionConfigurationFactory dataConnFactory new DataConnectionConfigurationFactory(); dataConnFactory.setPassivePorts(2000-2100); listenerFactory.setDataConnectionConfiguration(dataConnFactory.createDataConnectionConfiguration()); serverFactory.addListener(default, listenerFactory.createListener());端口方面我特意选了2121而不是21。一是Linux体系下1024以下的端口往往需要特殊权限二是很多路由器后台或系统服务已经占用了21端口万一冲突客户端会一直提示“无法与服务器建立连接”排查起来很费劲。被动模式端口范围这个细节很容易被忽略FTP协议的文件传输默认走另一个动态端口。如果你不设置PassivePorts服务器会随机分配有些路由器或防火墙拦截了高端口之后就会出现“能登录但传不了文件”的诡异现象。设置一个明确的端口范围后面排查问题时思路会清晰很多。3.2 用户管理器和目录权限配置FTP服务器总得有账号密码Apache里用PropertiesUserManager最方便。我写了一个初始化用户的方法PropertiesUserManagerFactory userManagerFactory new PropertiesUserManagerFactory(); userManagerFactory.setFile(new File(context.getFilesDir(), users.properties)); UserManager userManager userManagerFactory.createUserManager(); BaseUser user new BaseUser(); user.setName(android); user.setPassword(123456); user.setHomeDirectory(homeDir.getAbsolutePath()); ListAuthority authorities new ArrayList(); authorities.add(new WritePermission()); authorities.add(new ReadPermission()); user.setAuthorities(authorities); userManager.save(user); serverFactory.setUserManager(userManager);注意这里给用户同时加了ReadPermission和WritePermission也就是说登录后既能下载也能上传、删除。如果只是给其他人临时取文件用建议只保留ReadPermission设为只读模式避免有人误删文件。3.3 用前台服务把FTP服务器包含进来Android 8.0以后后台应用在几分钟内就会被系统冻结如果你只是在一个Activity里启动了FtpServer锁屏之后服务大概率会被杀掉。所以正确姿势是启动一个前台服务Foreground Service并且在通知栏显示常驻通知。我的服务实现思路如下在Service的onStartCommand里调用serverFactory.createServer()然后调用server.start()。在onCreate里创建通知渠道并startForeground()这样应用被划掉后服务也能继续跑。在onDestroy里调用server.stop()并释放资源。前台服务通知栏里我会显示当前FTP的地址、端口和用户名密码这样用户直接看一眼通知栏就知道该连什么地址。3.4 动态获取WiFi IP地址电脑要连接手机上的FTP服务器总得知道服务器的IP。虽然可以在通知栏显示但动态获取IP的代码也一并分享出来public static String getWifiIpAddress() { try { EnumerationNetworkInterface interfaces NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface networkInterface interfaces.nextElement(); EnumerationInetAddress addresses networkInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress inetAddress addresses.nextElement(); if (!inetAddress.isLoopbackAddress() inetAddress instanceof Inet4Address) { return inetAddress.getHostAddress(); } } } } catch (Exception e) { e.printStackTrace(); } return 127.0.0.1; }这个遍历方式有一点需要注意它拿到的不一定是WiFi网卡的地址有可能是USB共享网络或者热点地址。如果设备同时开了热点和WiFi最好再结合ConnectivityManager判断当前活跃网络类型。不过对于Demo来说取到第一个非回环IPv4地址基本够用。4. 客户端联调与问题排查实录4.1 FileZilla连接后的告警与处理服务器跑起来之后我在电脑上用FileZilla实测第一次连接时确实弹出了“不安全的服务器”这类警告这是因为FileZilla默认倾向使用FTP over TLS。如果你像我一样暂时没配置FTPS可以在FileZilla站点管理器的加密选项里选择“仅使用普通FTP”警告就会消失。完整的连接参数如下主机手机热点或局域网IP比如192.168.1.5端口2121用户android密码123456实测下来局域网内传输速度可以达到20MB/s左右看视频、传安装包都没压力。4.2 常见问题速查手册我实际调试了两天整理了下面这张高频问题对照表基本覆盖了90%的坑现象可能原因解决办法FileZilla提示“无法与服务器建立连接”端口被占用、手机和电脑不在同一网段换端口确认连接的是同一WiFi关闭防火墙能登录但列不出目录用户home目录不存在或没有权限确保homeDir目录已创建检查读写权限能连接但上传大文件时中断被动模式端口被路由器拦截固定PassivePorts范围并在客户端切到被动模式Android 11以上列出的文件为空使用了公共存储根目录改用getExternalFilesDir或处理分区存储权限锁屏后服务自动停止没有使用前台服务用Foreground Service运行FtpServer并可见通知连接后传输速度很慢WiFi信号弱、路由器开启了AP隔离检查路由器是否隔离了局域网设备互通其中AP隔离是我踩过的一个比较隐蔽的坑手机能正常上网电脑也能上网但电脑就是连不上手机上的FTP。后来排查半天发现是路由器开启了访客网络隔离导致局域网内设备互相不可见。这种问题从代码层面解决不了只能换网络环境。4.3 关于FTP over TLS的配置建议如果你希望客户端默认使用加密连接而不是每次都得手动关掉TLS那就要在服务器端配置SSL上下文。Apache FtpServer支持这一功能但前提是你得先生成一个自签名证书。配置思路大致如下SslConfigurationFactory sslConfigFactory new SslConfigurationFactory(); sslConfigFactory.setKeystoreFile(new File(context.getFilesDir(), ftpserver.jks)); sslConfigFactory.setKeystorePassword(123456); listenerFactory.setSslConfiguration(sslConfigFactory.createSslConfiguration()); listenerFactory.setImplicitSsl(false);证书生成可以用keytool命令或者干脆在App首次启动时用代码动态生成一个自签名证书。注意客户端连接的时候会提示证书不受信任直接选择信任即可。我在Demo阶段其实纠结过要不要上TLS后来想想还是加了因为FileZilla默认嗅探加密能力如果服务器完全不支持TLS客户端每次连接都要额外多点一下这个体验比较差。加上之后至少“不安全的服务器”这类提示会消失。5. 从一个能跑的Demo到一个能用的工具5.1 可扩展的几个方向这个Demo跑通之后我马上想到了几个实用化的改进方向动态端口UI界面里加一个端口输入框避免固定端口被占用。用户管理把用户信息落到数据库支持多用户、不同权限而不是写死在properties里。只读模式单独加一个“允许写入”的开关默认只读需要时可切换。自动开关机检测到连接指定WiFi时自动启动服务断开时自动停止。文件浏览增强在App内直接展示FTP根目录的内容方便手机端确认文件是否传成功。这些都是对现有架构的低成本扩展Apache FtpServer的接口设计对这类需求支持得比较完善不需要大改。5.2 安全提醒不要在公网乱开FTPFTP协议本身是明文传输用户名密码都是裸奔的。我这个Demo里的密码很简单因为场景就是可信局域网。但如果你把它暴露到公网那基本等于把家门钥匙放地垫下面。强烈建议不要在内网穿透工具里直接映射21或2121端口。如果确实需要外网访问场景至少要做到三点一是起一个强密码账号二是限制允许登录的IP三是只开通只读权限。能做到这三点风险会大幅下降。不过我的态度是FTP这种老协议留在局域网内用就好公网传输还是交给专门的加密方案更稳妥。6. 最后再分享一点我个人的实测心得这个Android FTP服务器Demo整体不算复杂但从零开始写完、跑通、排完坑前后也用了两个晚上。最大收获倒不是“我会调FTP了”而是彻底理解了Android后台任务、存储权限、网络端口这三者之间是如何互相牵扯的。一个看起来很小的需求真正落地时绕不开系统限制只有把这些限制摸透写出来的App才不会是“一运行就崩溃、一锁屏就中断”的玩具。我自己的使用习惯是平时在家里或者公司临时要给手机传个几十MB的压缩包直接打开这个App电脑连上FTP拖拽过去比找数据线、开聊天工具上传下载都快不少。如果你也想做个类似的工具建议从我上面这套方案起步先用Apache FtpServer把服务跑通再慢慢加自己的交互和功能。遇到问题也别慌对照排查表逐步看大多数坑都能找到明确的原因。本文还有配套的精品资源点击获取
