【Android】HTTP文件管理服务器APP项目开发流程详解
过去某些网盘平台曾推出大容量赠送活动,包括1TB或2TB的免费空间,实际使用后确认这类福利真实有效,早期用户可能对相关网盘的活动留有印象,如下图,是否知道当时是哪个网盘的呢,阅读完此文章,你可能会理解,作者做出此项目的初衷。

笔者用过这一个网盘,不知道往里面存了多少备份的资料,不是很重要的资料,也就没有经常去光顾它,
直到后来,才发现,这网盘容量缩水了 😳,原来的1TB变成了10GB,说好的永久容量,信用呢 😏,

好在另一家网盘承诺容量还在,只是限速了,文件下载速度很慢的,需要的时候你可忍得了😓,不过好在,一些大文件都没有再往那里传了。
现在的很多网盘未来是否也会变成那么不可靠呢😐,不如自己搞一个本地的来存储数据放心一些,这样不担心泄露隐私😌。
说起现在可靠的存储设备,就是以前的老机械硬盘了,出了故障还能抢修一下恢复数据,
而那个叫固态硬盘的,好在读写速度快,但是,寿命是3到5年的样子,一旦使用寿命到了,就会突然无法读盘,丢失数据事大,用着实在不让人放心😒,
还有没有可靠的存储设备呢,笔者希望是能长期运行又省电,当然是,闲置的手机内部存储器了😀,
手机的内部存储器的质量是最好的,你想想,那些闲置的手机是5年多前买过的,到现在都没有坏过,还能开机呢,说明手机里保存文件是最可靠的,有没有见过哪些手机的存储器有坏过呢,在这里,笔者实现在手机上管理文件的APP,相比手机内置的FTP服务器管理文件操作要容易一些。
目录
- 页面布局
- 安装模块
- 服务器
- 二维码
- 实现功能
- 更改登陆密码
- 开启服务器
- 关闭服务器
- 打开浏览器
- 获取权限
- 运行测试
- 项目源码
接下来,让本作者讲解如何开发这个文件管理服务器,可以参考学习。
想试试却没有安装Android Studio开发工具?可参考以下文章开始安装
- 【Android】安装2025版AndroidStudio开发工具开发老安卓旧版App
此项目是以Web服务器项目开发的基础上调整而来,更多细节可参考以下文章
- 【Android】答题系统Web服务器APP应用开发流程详解
注意:
开发此项目编译的APP安装包适配在安卓手机系统Android 5.0以上
打开已安装好的Android Studio开发工具,新建一个项目,例如 FileServer,
在新建项目的窗口上,注意选择 No Activity,只有这一项,才能开发旧系统的App,
接下来,选择对应的
- Language 选择 Java,
- Minimum SDK 选择 API21,也就是最小适配 Android 5.0 以上的App,
- Build configuration language 选择 Groovy DSL(build.gradle)
最后,等开发工具Build 创建项目完成。
完成后,
要新建一个页面,点中项目文件夹,点鼠标右键,按以下步骤选择
New → Activity → Empty Views Activity
由于第一个页面是应用程序入口,要默认勾选Launcher Activity,
这样,一个页面就自动建好了,看看项目路径app/src/main下的文件,
会发现多了两个文件:
- 一个 MainActivity.java - 写页面逻辑
- 一个 main_layou.xml - 写页面布局
页面布局
打开项目下的布局文件main_layout.xml,做好一个主页面,修改后如下图

在页面逻辑下,写好初始化代码,如下:
public class MainActivity extends AppCompatActivity {
//...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//...
//获取按钮
tvServer = findViewById(R.id.textViewServer);
btnOpen = findViewById(R.id.buttonOpenBrow);
btnOn = findViewById(R.id.buttonOn);
btnOff = findViewById(R.id.buttonOff);
Button btnEditPwd = findViewById(R.id.buttonEditPwd);
//按钮点击事件
btnOpen.setOnClickListener(s->openBrowser());
btnOn.setOnClickListener(s->serverSwitchChanged(true));
btnOff.setOnClickListener(s->serverSwitchChanged(false));
btnEditPwd.setOnClickListener(s->editLoginPwd());
//...
// 初始化显示
updateUI();
// 检查权限
checkStorageAccess();
checkPermissions();
}
//...
}
接下来,主要是功能的实现,
💁功能模块就好比那些路上跑车的轮子,有好的也有差的,能不能让我们的程序顺利跑起来就靠它们了。
现在说一下两个轮子,就是那些很厉害的人,写好的两个模块,分享给我们的小白,或者懒人开发者用用,这样可少写代码,轻松地实现。
如果找来那些轮子试了还不满意的话,可以自己来造…

安装模块
就两个模块,分别是服务器和二维码,
服务器
这个模块名叫AndServer,安装这个模块还需要几个步骤,
打开app模块文件build.gradle,添加以下内容
plugins {
//...
id 'com.yanzhenjie.andserver'
}
dependencies {
//...
implementation 'com.yanzhenjie.andserver:api:2.1.12'
annotationProcessor 'com.yanzhenjie.andserver:processor:2.1.12'
}
还有一个文件build.gradle,跟上面的文件一样,但内容不一样,是放在项目的根目录下,
将其打开,添加以下内容
buildscript {
dependencies {
classpath 'com.yanzhenjie.andserver:plugin:2.1.12'
}
}
注意这个要在里面的内容
plugins { ... }前面的位置插入,否则编译会报错。
看过之前开发答题服务器文章的读者也许会问,为什么不用之前的模块插件NanoHTTPD做服务器?
作者用过,只是没有这个的好用吧,有时间自己试一下,觉得哪个好用就用哪个吧。
二维码
模块名叫zxing,安装这个模块只要一个步骤,
打开app模块文件build.gradle,添加以下内容
dependencies {
//...
implementation 'com.google.zxing:core:3.5.2'
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
}
实现功能
点击编辑器右上角的Sync Now,开发工具就会开始联网安装相关的模块,
等模块安装好了,接下来就实现功能
看看界面上按钮有什么,每个按钮都用到了几个功能,
需要一个个去实现:
文件管理系统是安全的,不可公开访问,因此有登陆页面,登陆密码可通过后台来设置和修改,
更改登陆密码
点击修改登陆密码按钮,就会打开一个输入密码对话框,密码忘了没关系,可直接修改登陆密码,
点击按钮会调用一个方法,代码如下
private void editLoginPwd() {
DialogUtils.showPrompt(this,"设置登陆密码","","password",(String value)->{
//回调方法里的实现...
});
}
其中方法showPrompt()会打开输入对话框,这个窗口就用作者自己写好的一个工具类DialogUtils,好处是方便集成,不用再单独做一个页面,
选择好文件夹后,方法会通过一个回调参数返回value,表示用户输入的密码,用法类似JavaScript的回调函数,函数里的处理逻辑代码如下
if (value.isEmpty()){
DialogUtils.showToast(this, "密码不能空");
return;
}
String pwd = SecurityUtils.getMD5(value);
PreferenceUtils.setString(this, SET_LOGIN_PWD, pwd);
showToast("密码设置成功");
从上面看出来,还有工具类SecurityUtils和PreferenceUtils也是作者自己写好的,设置到的密码需要通过getMD5()处理加密内容(下次如果你忘了密码再看数据就想不起来了),然后通过setString()方法设置存到本地。
开启服务器
点击开启的按钮,就会开启一个后台服务,假设有个服务类WebServerService,代码如下
private WebServerService webServerService;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
WebServerService.LocalBinder binder = (WebServerService.LocalBinder) service;
webServerService = binder.getService();
//...
}
@Override
public void onServiceDisconnected(ComponentName name) {
//...
}
};
在之后的服务器开启时,会触发它里面这个方法onServiceConnected(),通过getService()返回实例化webServerService类,
这个服务类WebServerService需要自己实现,继承了服务,代码如下
public class WebServerService extends Service {
private AndroidWebServer webServer;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
//...
}
然后,重写它的方法onStartCommand(),这里面去调用模块实现webServer的start()方法,开启服务,
try {
webServer = new AndroidWebServer(SERVER_PORT, customFolderPath, this);
webServer.start();
// 启动成功
String ipAddress = NetworkUtils.getLocalIpAddress(this);
String serverUrl = "http://" + ipAddress + ":" + SERVER_PORT;
sendStatusUpdate(true, serverUrl, null);
} catch (Exception e) {
stopSelf();
// 启动失败
sendStatusUpdate(false, null, "启动失败: " + e.getMessage());
}
其中方法
sendStatusUpdate就是发送通知的,告诉用户当前服务器的状态
最后,别忘了在文件AndroidManifest.xml里添加service,内容如下
<manifest>
<application>
<activity>
activity>
<service
android:name=".server.WebServerService"
android:enabled="true"
android:exported="false" />
application>
manifest>
当开启服务的按钮点击后,会调用一个方法,代码如下
private void startServer(){
//...
// 启动服务(会创建前台通知)
Intent serviceIntent = new Intent(this, WebServerService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
PreferenceUtils.setString(this, SET_SOURCE_PATH, selectedFolderPath);
showToast( "服务器开启");
}
可见,开启服务是这样的,UI进程与其后台服务进程之间通过
Intent传递通信,通知开始服务工作
关闭服务器
点击关闭服务器,就会把之前开启的后台服务给关闭了,
点击关闭的按钮会调用一个方法,代码如下
private void stopServer(){
if (isServiceBound && webServerService != null) {
// 停止服务
Intent serviceIntent = new Intent(this, WebServerService.class);
stopService(serviceIntent);
// 解绑
unbindService(serviceConnection);
isServiceBound = false;
showToast( "服务器关闭");
}
}
当解绑后,系统会调用实例webServerService的一个onDestroy()方法,代码如下
public void onDestroy() {
// 停止Web服务器
if (webServer != null) {
webServer.stop();
webServer = null;
sendStatusUpdate(false, null, null);
}
// 释放锁
releaseLocks();
super.onDestroy();
}
从上面看出,调用了实例
webServer的stop()方法,就停止了服务器,其余的操作就是发送通知,解除防休眠的
聪明的你也许留意到了,服务器是webServer,是AndroidWebServer类,还需要自己实现,
这个实现就用到了AndServer模块,代码如下
public class AndroidWebServer {
private final Server server;
public AndroidWebServer(int port, Context context){
CustomApplication app = CustomApplication.getInstance();
app.storageDiretory = FolderPickerUtils.getStorageDirectory();
//...
server = AndServer.webServer(context)
.port(port)
.timeout(10, TimeUnit.SECONDS)
.listener(new Server.ServerListener() {
@Override
public void onStarted() {
Log.v(TAG, "服务器启动成功");
}
@Override
public void onStopped() {
Log.v(TAG, "服务器已停止");
}
@Override
public void onException(Exception e) {
Log.e(TAG, "服务器异常停止: "+e.getMessage());
}
})
.build();
}
public void start(){
if(!server.isRunning()) server.startup();
}
public void stop(){
if(server.isRunning()) server.shutdown();
}
public boolean isAlive(){
return server.isRunning();
}
}
- 从上面代码可以看出,通过方法
getStorageDirectory()会返回要给内置存储区的目录位置,实现方法的工具类FolderPickerUtils也是作者自己写的; - 其中
CustomApplication类也是自己实现,它是继承了Application,主要是类似于设置或访问程序全局变量的方式,接下来会用到;
这个没有继承模块AndServer,而是在里面实例化了模块的对象Server,可控制它的开启和停止,
聪明的你也许会发现问题,服务器的请求控制器逻辑没有写,
它是没有写到一起的,这就写一个ApiController类,代码如下
@RestController
@RequestMapping("/api")
public class ApiController {
private final CustomApplication app;
public ApiController(){
this.app = CustomApplication.getInstance();
}
@RequestMapping(path = "/islogin")
public String isLogin(@NonNull HttpRequest request) {
boolean isLogin = isUserLogin(request);
if (isLogin){
return createResponseData(0, "ok");
}
// 已登录用户处理逻辑
return createResponseData(1, "no login");
}
@RequestMapping(path = "/login", method = RequestMethod.POST)
public String doLogin(@NonNull HttpRequest request, @RequestBody String jsonBody) {
// 从请求中获取信息并处理...
// 部分省略...
return createResponseData(code, errMsg, data);
}
@RequestMapping(path = "/logout")
public String doLogout(@NonNull HttpRequest request){
//部分省略...
}
//部分省略...
}
从代码中看出,这是处理前端页面发来的三个请求:
- /api/islogin - 判断用户是否已登陆;
- /api/login - 如果没有登陆,就要处理这个请求来判断传过来的登陆密码;
- /api/logout - 处理这个请求就会登出;
学会Java开发,创建过
RESTful风格的控制器的同学,应该会发现它使用类似SpringMVC的注解;
这就是处理页面GET请求和POST请求的后台逻辑,
聪明的你又发现了问题,没有看到哪个代码会调用这个类ApiController,这个不用管,开发工具会自动调用它,这就是AndServer模块专为懒人开发者设计的;
可以继续在ApiController类中实现更多的处理请求,也可以单独再写一个请求处理类,看如下代码
@RestController
@RequestMapping("/file-os")
public class AdminFileOriSysController {
private final CustomApplication app;
public AdminFileOriSysController(){
this.app = CustomApplication.getInstance();
}
@RequestMapping(path = "/space_info", produces = MediaType.APPLICATION_JSON_VALUE)
public String getStorageSpaceInfo(@NonNull HttpRequest request) {
if (!isLogin(request)){
return responseJsonData(1, "no login");
}
//部分省略...
}
//部分省略...
}
-
上面代码就是处理更多的文件管理请求,还有一些省略了,生下的请求处理如下
-
- /file-os/space_info - 返回设备内置存储区的大小以及剩余可用空间信息;
-
上面
AdminFileOriSysController类中还有一些POST请求处理,列举如下: -
- /file-os/list_files - 返回目录下的文件列表,包括文件夹;
-
- /file-os//download/{filename} - 返回下载文件,其中
filename是指包含相对路径的文件名;
- /file-os//download/{filename} - 返回下载文件,其中
-
- /file-os/create_folder - 处理请求,新建文件夹
-
- /file-os/rename_file - 处理请求,重命名文件(夹)
-
- /file-os/delete_files - 处理请求,批量删除文件(夹)
-
- /file-os/upload - 处理请求,上传文件
-
- /file-os/move_files - 处理请求,批量移动文件(夹)
所有的
POST请求,需要先判断用户是否登陆,如果未登陆下的请求是不允许访问和下载文件的,否则这管理下的文件那就是人人都能访问了,会泄露隐私的;
- 实现过程代码较多,这里就不详细说,自己能实现吧
对了,还有服务器配置要改,需要再写一个类WebServerConfig,代码如下:
public class WebServerConfig implements WebConfig {
@Override
public void onConfig(Context context, Delegate configurator) {
// 配置静态资源路径
configurator.addWebsite(new CustomAssetsWebsite(context, "/wwwroot/"));
}
}
可见,这又是一个懒人设计,没见到哪个代码在调用它,
其中wwwroot是一个文件夹,类似前端托管,这里存放uniapp项目生成的H5页面文件(单独放一个自己写的index.html文件也行),
这个文件夹就放在项目目录位置下app/src/main/assets,在编译出来就内置在里面
如果你要研究和修改作者开发的uniapp项目,可参考 文件管理系统-HTTP服务器-H5-uniapp-项目源码
打开浏览器
点击打开浏览器按钮,就会打开系统自带的浏览器,直接访问文件管理服务器的H5页面,
点击的按钮会调用一个方法,代码如下
private void openBrowser(){
String url = webServerService.getServerUrl();
if (url==null || url.isEmpty()) {
showToast( "无法获取网络IP地址");
} else {
// 打开浏览器访问Web服务器
LinkUtils.openUrl(this, url);
}
}
由于H5页面是用
uniapp项目做来编译出来的,旧手机(包括Android 5.0以下)的自带浏览器无法正常加载H5页面,用电脑或者最新的Android 8以上的系统浏览器就能正常打开访问
获取权限
上面用到的功能,获取权限是必不可少的:
- 需要访问网络状态,连接的WIFI;
- 需要通知,服务器工作的状态通知;
- 需要保持在后台运行的权限;
- 访问手机内部存储的权限,读取或者保存一些文件;
在第一个页面MainActivity代码中处理初始化的时候,调用了两个方法,代码如下
// 检查权限
checkStorageAccess();
checkPermissions();
就是检查存储和通知两个权限的,没有权限就授权,自己能实现吧,
除了写授权逻辑代码,还要
在配置文件AndroidManifest.xml下添加uses-permission ...,内容如下
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<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.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<application
...
android:requestLegacyExternalStorage="true">
application>
manifest>
好了,就写到这里,文件管理器的实现大致的思路已阐述完毕,内容告一段落。
运行测试
编译顺利的话,就安装到手机上,运行时效果如下

图中二维码作者已打码,这里不便展示。
看操作步骤,首次运行需要设置好登陆密码,不设置那就用默认密码(已注释在代码里),
这服务器不用做多余的设置,管理的文件都是你的安卓设备下内置存储下的一些文件,
当用手机扫码访问,或打开浏览器输入局域网内的IP地址访问时,效果如下:

笔者这手机是安卓Android 13系统,处理加载资源响应还是很快的,
知道为什么手机显示有128G容量吗,如下图

与上面的H5页面内置存储显示容量不对应呢,如下图

是为什么呢?
- 当然是计算方式不一样了,网购过硬盘的应该知道原因吧,
看起来是变小了,这也难怪,现在手机装了一些APP后,录了好多视频,装多了生活照以后,那128GB的容量就变得越来越不够用了😶,有种想换新手机的想法(当然这应该是厂家希望看到的)。
能看图片,生活照片可以放在里面,在家方便随时查看,重温生活的点点滴滴,

能看视频,若是视频文件比较大,加载的时间就会相对延长,

以上是12MB大小的短视频,它会瞬间加载好了,响应很快吧
能听音乐,随时随地听,引起共鸣的歌可以下载到,设置到手机闹钟上,或者来电铃声,

能看电子书,在家里方便随时随地查阅,学习知识,

若是用电脑PC浏览器访问的,那么效果如下:

这是作者在h5项目里做了响应布局页面,操作功能一个都不少,该支持的都支持,与手机页面不同的是,布局位置有调整,左侧多了一个文件树操作框口,操作比手机的有效率多了。
文件操作菜单整合在悬浮按钮中,如下图

至于PC浏览器上的文件查看或播放的效果,这里就不晒图了,就是和手机上看的效果一样的,有区别的话就是比较宽,在宽屏上看视频,看电子书更好一些。
看浏览的文件像网盘一样能管理,能上传文件,文件能直接打开查看,可在线播放音乐视频,文件下载速度很快,便捷高效的操作体验,是不是有点心动了呢。
就写到这里,感谢你的耐心阅读!😊
项目源码
- 相关文件管理APP项目源码 点此查看,
- 更多的源码 点此查看,
- 更多的资源 点此查看,
项目上传至今,发现了一个bug(无法在线阅读PDF电子书文件),是修改后导致的,作者未及时发现,现修改如下图

在项目下的路径
app/assets/wwwroot/hybrid/html/index.html这个文件里面,第54行位置添加一行代码url = m[1];就算解决了;










