欢迎光临
我们一直在努力

【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,做好一个主页面,修改后如下图

图1

在页面逻辑下,写好初始化代码,如下:

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,内容如下

<?xml version="1.0" encoding="utf-8"?>
<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/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 …,内容如下

<?xml version="1.0" encoding="utf-8"?>
<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" />
<!– 读取外部存储权限(如果需要获取SD卡信息) –>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!– 对于Android 10+,需要添加这个权限来访问公共目录 –>
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />

<!– 对于Android 11+,需要添加这个来管理所有文件 –>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />

<application
…
android:requestLegacyExternalStorage="true">

<!– … –>
</application>

</manifest>

好了,就写到这里,文件管理器的实现大致的思路已阐述完毕,内容告一段落。

运行测试

编译顺利的话,就安装到手机上,运行时效果如下 图2

图中二维码作者已打码,这里不便展示。

看操作步骤,首次运行需要设置好登陆密码,不设置那就用默认密码(已注释在代码里),

这服务器不用做多余的设置,管理的文件都是你的安卓设备下内置存储下的一些文件,

当用手机扫码访问,或打开浏览器输入局域网内的IP地址访问时,效果如下:

图片描述

笔者这手机是安卓Android 13系统,处理加载资源响应还是很快的,

知道为什么手机显示有128G容量吗,如下图 图4

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

是为什么呢?

  • 当然是计算方式不一样了,网购过硬盘的应该知道原因吧,

看起来是变小了,这也难怪,现在手机装了一些APP后,录了好多视频,装多了生活照以后,那128GB的容量就变得越来越不够用了😶,有种想换新手机的想法(当然这应该是厂家希望看到的)。

能看图片,生活照片可以放在里面,在家方便随时查看,重温生活的点点滴滴, 图3

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

以上是12MB大小的短视频,它会瞬间加载好了,响应很快吧

能听音乐,随时随地听,引起共鸣的歌可以下载到,设置到手机闹钟上,或者来电铃声, 图7

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

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

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

文件操作菜单整合在悬浮按钮中,如下图 图片描述

至于PC浏览器上的文件查看或播放的效果,这里就不晒图了,就是和手机上看的效果一样的,有区别的话就是比较宽,在宽屏上看视频,看电子书更好一些。

看浏览的文件像网盘一样能管理,能上传文件,文件能直接打开查看,可在线播放音乐视频,文件下载速度很快,便捷高效的操作体验,是不是有点心动了呢。

就写到这里,感谢你的耐心阅读!😊

项目源码

  • 相关文件管理APP项目源码 点此查看,
  • 更多的源码 点此查看,
  • 更多的资源 点此查看,

项目上传至今,发现了一个bug(无法在线阅读PDF电子书文件),是修改后导致的,作者未及时发现,现修改如下图 图片描述

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

图片

赞(0)
未经允许不得转载:171主机测评 » 【Android】HTTP文件管理服务器APP项目开发流程详解
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址