
简介本资源是一个基于 Apache FtpServer 实现的 Android 端轻量级 FTP 服务器 Demo面向 Android 应用开发初学者与嵌入式网络功能实践者解决在移动设备上快速搭建本地 FTP 服务、实现文件共享或调试传输的需求。压缩包为 ZIP 格式共 28 个文件涵盖 11 个 XML 配置与布局文件含 AndroidManifest.xml 和多套 values 资源、7 个核心 JAR 包如 ftpserver-core-1.0.6.jar、mina-core-2.0.4.jar 及 SLF4J/Log4J 日志依赖、5 张 PNG 图标资源以及 Java 源码、ProGuard 混淆配置、Eclipse 项目元数据等结构完整可直接导入 IDE 编译运行。目前已有 592 人学习下载。读者可获得一个开箱即用的 Android FTP 服务工程包含完整的目录组织、权限配置、服务启停逻辑及依赖管理方案特别适合理解 Android 后台服务与 Java 网络框架集成的关键细节。1. Android FTP服务器Demo别急着写代码先搞清这三件事——为什么90%的人在AndroidManifest.xml里就翻车了你是不是也试过网上搜“Android FTP服务器 demo”下载一个GitHub项目git clone完兴冲冲./gradlew build结果卡在Execution failed for task :app:processDebugManifest或者App一启动就闪退Logcat里只有一行java.lang.SecurityException: Permission denied这不是你手残而是绝大多数人根本没意识到Android 10 的存储权限模型、后台服务限制、以及FTP协议在移动设备上的天然适配断层已经让“开箱即用”的FTP服务器Demo成了玄学现场。这个Demo不是教你怎么写Socket而是帮你绕过系统级封印——它基于ftpserver-core轻量封装专为Android 12/13/14适配不依赖root不调用废弃API所有权限申请、前台服务声明、存储路径切换都已预埋。适合正在做局域网文件共享调试、嵌入式设备固件更新中转、或需要临时暴露手机SD卡给PC端工具比如Wireshark抓包分析FTP流量的开发者。如果你还在用SimpleFTP或自己手撸ServerSocket建议先停一停——那不是练手是给自己埋雷。2. 环境准备与依赖确认Gradle版本、SDK约束与JDK陷阱2.1 检查你的Android Studio是否“够老又够新”这个Demo对构建环境有明确代际要求必须使用Android Studio Giraffe2022.3.1或更高版本但不能用Hedgehog2023.1.1的默认Gradle插件。原因很现实——Hedgehog默认启用android.useNewApkCreatortrue而本项目中ftpserver-core的jar资源打包逻辑会与之冲突导致APK里缺失org/apache/ftpserver/类路径。我一般会强制降级到Giraffe Gradle Plugin8.0.2# 在项目根目录的gradle/wrapper/gradle-wrapper.properties中修改 distributionUrlhttps\://services.gradle.org/distributions/gradle-8.0-bin.zip提示不要手动下载Gradle ZIP包后放本地缓存目录——AS会校验SHA256放错版本会导致Could not find gradle-8.0-bin.zip。直接改URL重启AS让它自动拉取。2.2 SDK版本必须卡死在API 33Android 12L或34Android 14项目build.gradleModule: app中明确声明android { compileSdk 34 defaultConfig { targetSdk 34 minSdk 21 // 注意不支持低于21的设备因使用了java.util.concurrent.CompletableFuture } }为什么不能用API 35因为Android 15预览版中android.net.wifi.WifiManager.getConnectionInfo()返回结构变更而Demo中getLocalIpAddress()方法依赖该API获取热点IP未适配会导致空指针。我一般会在Utils.java里加一层判空保护但原始Demo未包含——这点后面避坑章节会补。2.3 JDK必须用17且禁止使用Android Studio内置Embedded JDKAS自带的Embedded JDK通常是17.0.x在Windows上存在java.nio.file.Files.createTempDirectory()权限异常表现为FTP服务启动时抛AccessDeniedException。解决方案是显式指定JDK路径# WindowsFile → Project Structure → SDK Location → JDK location # 填入C:\Program Files\Java\jdk-17.0.2 需提前从Adoptium下载 # macOSAS Preferences → Build → Build Tools → Gradle → Gradle JVM → 选中jdk-17.0.2验证方式终端执行./gradlew -version | grep JVM # 正确输出应为JVM: 17.0.2 (Eclipse Adoptium 17.0.28)2.4 核心依赖项ftpserver-corevsapache-ftpserver的血泪选择项目app/build.gradle中引入的是精简版依赖implementation org.apache.ftpserver:ftpserver-core:1.1.1 implementation org.apache.mina:mina-core:2.1.3注意绝不能替换成apache-ftpserver的2.x版本如2.0.0-M1。2.x彻底移除了FtpServerFactory构造器改用Builder模式而Demo中FtpServerManager.java仍用旧式API初始化。强行升级会导致编译失败error: cannot find symbol class FtpServerFactory更隐蔽的坑是mina-core版本——若误用2.2.0IoAcceptor接口签名变更FtpServer启动时会报NoSuchMethodError。我一般会锁死mina-core:2.1.3并用./gradlew app:dependencies | grep mina二次确认。3. 权限配置与AndroidManifest.xml深度修正3.1 存储权限从WRITE_EXTERNAL_STORAGE到MANAGE_EXTERNAL_STORAGE的平滑过渡Android 11API 30起WRITE_EXTERNAL_STORAGE被降级为“仅限媒体文件”而FTP服务器需读写任意目录如/sdcard/Download/ftp_root。Demo采用兼容方案!-- AndroidManifest.xml -- uses-permission android:nameandroid.permission.READ_MEDIA_IMAGES / uses-permission android:nameandroid.permission.READ_MEDIA_VIDEO / uses-permission android:nameandroid.permission.READ_MEDIA_AUDIO / uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE android:maxSdkVersion28 / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE android:maxSdkVersion28 / !-- Android 11 使用MANAGE_EXTERNAL_STORAGE但需声明理由 -- uses-permission android:nameandroid.permission.MANAGE_EXTERNAL_STORAGE tools:ignoreScopedStorage /注意tools:ignoreScopedStorage不是绕过审核而是告诉AGP“我知道Scoped Storage规则但此处为调试用途”。发布前必须移除该行并改用Storage Access FrameworkSAF选择目录——Demo中FtpRootSelectorActivity.java已预留SAF入口但默认注释掉。3.2 网络与后台服务权限FOREGROUND_SERVICE和INTERNET缺一不可FTP服务必须以前台服务运行否则Android 9会杀进程且需显式声明网络能力uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / !-- 前台服务类型必须声明为specialUse因FTP不属于常见类型 -- service android:name.service.FtpServerService android:enabledtrue android:exportedfalse android:foregroundServiceTypespecialUse /关键点foregroundServiceTypespecialUse是Android 12强制要求。若写成location或microphone系统会拒绝启动服务并在Logcat输出Foreground service started from background can not have type location/microphone3.3 Activity声明与Intent Filter避免被系统拦截Demo主Activity需声明LAUNCHER且禁用exported属性Android 12默认为falseactivity android:name.MainActivity android:exportedtrue android:launchModesingleTask intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity若exportedfalse桌面图标将消失若漏掉launchModesingleTask多次点击图标会创建多个Activity实例导致FTP服务重复启动端口被占用。4. 配置文件解析与自定义参数注入4.1ftpserver.xml不只是XML是运行时行为开关项目assets目录下的ftpserver.xml是核心配置文件。不要把它当成静态资源——它在运行时被FtpServerFactory解析每个节点都对应实际行为ftp-server xmlnshttp://mina.apache.org/ftpserver/spring/v1 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocation http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver/ftpserver-1.0.xsd !-- 关键绑定IP地址0.0.0.0表示监听所有网卡 -- server idmyServer max-logins10 listeners nio-listener namedefault port2121 ssl keystore filekeystore.jks passwordchangeit / /ssl /nio-listener /listeners /server !-- 用户管理明文密码不安全但Demo为简化用PlainTextPasswordEncryptor -- users user nameadmin password123456 enabledtrue authorities authority authorityROLE_USER / /authorities filesystem native home/sdcard/ftp_root/home upload-permissiontrue/upload-permission /native /filesystem /user /users /ftp-server重点参数说明port2121必须避开21端口。Android系统守护进程可能占用21且非root应用无法绑定特权端口1024。2121是安全选择。home/sdcard/ftp_root/home路径必须存在且可写。Demo在FtpServerService.onCreate()中会自动创建该目录但若/sdcard被加密或挂载为只读会静默失败。upload-permissiontrue/upload-permission控制用户能否上传。设为false则变成只读FTP适合固件分发场景。4.2res/values/strings.xml中的隐藏开关Demo通过字符串资源控制行为而非硬编码string nameftp_port2121/string string nameftp_useradmin/string string nameftp_password123456/string string nameftp_root_path/sdcard/ftp_root/string string nameftp_enable_uploadtrue/string这些值在FtpServerManager.java中被读取String portStr context.getString(R.string.ftp_port); int port Integer.parseInt(portStr); // 若portStr为空此处崩溃所以修改端口时必须同时改ftpserver.xml和strings.xml否则服务启动后FTP客户端连不上。4.3build.gradle中的动态配置注入为避免每次打包都手动改XMLDemo支持Gradle动态替换android { defaultConfig { resValue string, ftp_port, 2121 resValue string, ftp_user, demo_user resValue string, ftp_password, demo_pass } }这样R.string.ftp_port在编译时就被注入无需修改strings.xml。但注意resValue生成的字符串无法在ftpserver.xml中直接引用XML仍需手动同步——这是Android资源系统的硬性限制。5. 常见问题排查那些让你怀疑人生的5个瞬间5.1 现象App安装后无桌面图标原因AndroidManifest.xml中activity android:exportedfalse或漏掉intent-filter解决检查MainActivity声明确保exportedtrue且包含MAINLAUNCHERfilter。用aapt dump badging app-debug.apk | grep launchable-activity验证。5.2 现象点击“Start FTP”按钮无反应Logcat无日志原因FtpServerService未在AndroidManifest.xml中声明或startService()调用时Context错误解决确认service标签在application内startService()必须传getApplicationContext()而非thisActivity Context在销毁后失效。5.3 现象FTP客户端连接超时Connection refused原因端口被占用或ftpserver.xml中nio-listener的port与代码中读取的R.string.ftp_port不一致解决用adb shell netstat -tuln | grep 2121检查端口占用对比ftpserver.xml和strings.xml中端口号是否完全一致包括空格。5.4 现象登录成功但无法列出目录报550 Permission denied原因/sdcard/ftp_root目录不存在或MANAGE_EXTERNAL_STORAGE权限未授予解决在FtpServerService.onStartCommand()中添加日志File root new File(getString(R.string.ftp_root_path)); Log.d(FTP, Root exists: root.exists() , writable: root.canWrite());若为false手动创建adb shell mkdir -p /sdcard/ftp_root adb shell chmod 777 /sdcard/ftp_root5.5 现象Android 12设备上服务启动后立即停止Logcat显示Foreground service did not start in time原因startForeground()调用延迟超过5秒或NotificationChannel未创建解决在FtpServerService.onCreate()中立即创建通知渠道if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { NotificationChannel channel new NotificationChannel( ftp_service, FTP Server, NotificationManager.IMPORTANCE_LOW); getSystemService(NotificationManager.class).createNotificationChannel(channel); } // 紧接着调用startForeground() startForeground(1, buildNotification());6. 验证与真机调试三步确认FTP服务真正跑通6.1 第一步用ADB确认服务进程与端口状态在手机开启开发者选项、USB调试后执行# 查看FTP服务是否在运行 adb shell ps -A | grep com.example.ftpserver # 检查2121端口是否监听注意Android 10需root才能看到非本进程端口 adb shell su -c netstat -tuln | grep :2121 # 若无root用PC端telnet测试连通性手机和PC需在同一局域网 telnet 192.168.1.100 2121 # 替换为手机IP成功响应应为Trying 192.168.1.100... Connected to 192.168.1.100. Escape character is ^]. 220 Service ready for new user.若卡在Trying...说明防火墙或端口未开放若返回Connection refused服务未启动或端口错误。6.2 第二步用FileZilla进行全链路功能验证配置FileZilla连接参数参数值说明主机192.168.1.100手机Wi-Fi IP通过adb shell ip addr show wlan0 | grep inet获取端口2121必须与配置一致用户名adminftpserver.xml中定义密码123456明文密码仅限局域网调试验证动作清单✅ 连接后显示/ftp_root目录内容初始为空✅ 上传一个文本文件检查手机/sdcard/ftp_root/下是否生成✅ 下载该文件MD5校验确保无损adb shell md5sum /sdcard/ftp_root/test.txt✅ 创建子目录/ftp_root/upload_test验证upload-permissiontrue/upload-permission生效提示FileZilla的“被动模式”PASV在Android上常失败务必在站点管理器中勾选强制使用主动模式PORT。因为Android NAT穿透能力弱PASV需要客户端开放随机端口而PC防火墙通常拦截。6.3 第三步模拟真实场景压力测试用Python脚本发起并发连接验证服务稳定性# test_ftp_stress.py import ftplib import threading import time def ftp_login_and_list(host, port, user, pwd): try: ftp ftplib.FTP() ftp.connect(host, port, timeout5) ftp.login(user, pwd) ftp.retrlines(LIST) # 列出根目录 ftp.quit() print(f[OK] Thread {threading.current_thread().name}) except Exception as e: print(f[FAIL] Thread {threading.current_thread().name}: {e}) # 启动10个线程并发连接 threads [] for i in range(10): t threading.Thread(targetftp_login_and_list, args(192.168.1.100, 2121, admin, 123456), namefWorker-{i}) threads.append(t) t.start() for t in threads: t.join() print(Stress test completed.)运行后观察手机Logcat中是否有FtpServer: User admin logged in连续日志adb shell top -m 5 | grep com.example.ftpserver查看CPU占用是否突增若出现530 Too many users说明max-logins10生效需调高ftpserver.xml中该值从那以后我每次部署Android FTP服务都强制走一遍这三步ADB端口确认 → FileZilla主动模式连通 → Python并发压测。少走任何一步上线后遇到客户说“连不上”就得翻着Logcat从头猜——而真相往往藏在AndroidManifest.xml一行exportedfalse里。希望帮到你。本文还有配套的精品资源点击获取