Android实现WebServer服务端和客户端(附带源码)
一、项目介绍
在移动互联网时代,Android 端不仅需要作为客户端发起请求,还能胜任轻量级的服务端角色,提供简单的数据或文件分发服务。本项目目标是在 Android 设备(或模拟器)上同时运行一个 HTTP WebServer(服务端)和一个 HTTP Client(客户端),实现设备本地局域网内的文件浏览、上传下载及简单 API 接口调用。
-
应用场景
-
局域网内播放本地媒体文件
-
简易分布式调试:多台设备互相请求数据
-
边缘设备数据采集与展示
-
核心功能
-
启动 WebServer,监听指定端口(如 8080)。
-
服务端响应 /files 列表当前设备指定目录下文件。
-
客户端发起 GET/POST 请求,支持文件下载与上传。
-
WebServer 支持简单的 REST 风格接口。
二、相关知识
在动手编码之前,需要读者对以下知识有一定掌握:
-
HTTP 协议基础
-
请求行、请求头、请求体
-
响应行、响应头、响应体
-
常见状态码(200、404、500 等)
(图片来源网络,侵删) -
Android 网络编程
-
HttpURLConnection 与 OkHttp 库
(图片来源网络,侵删) -
异步请求:AsyncTask(兼容老版本)、Thread + Handler、ExecutorService、RxJava
-
多线程与并发
(图片来源网络,侵删)-
Java Thread、Runnable
-
线程池管理
-
同步与线程安全
-
文件读写权限
-
Android 6.0+ 动态权限申请 (READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE)
-
Storage Access Framework(可选)
-
常用第三方库
-
NanoHTTPD:轻量级 Java HTTP 服务器
-
OkHttp:高效 HTTP 客户端
-
-
-
-
-
三、实现思路
-
整体架构
-
MainActivity:启动与停止服务器,展示客户端请求结果界面。
-
WebServer:继承自 NanoHTTPD,重写 serve() 方法处理请求。
-
HttpClientHelper:封装客户端 HTTP 请求逻辑,支持同步/异步接口调用及文件上传下载。
-
流程图
-
[MainActivity] │ 用户点击“启动服务器” │ ▼ [WebServer.start()] ──┬── 监听 8080 端口 │ └─ 进入请求处理循环 serve(session) ── 分发到对应 URI → 返回 Response
-
模块职责划分
模块 职责 MainActivity UI 控制,启动/停止服务,发起客户端请求 WebServer 接收并响应 HTTP 请求 HttpClientHelper HTTP 请求工具(GET/POST/下载/上传) PermissionManager 动态权限申请管理
四、实现代码
下面将所有源文件与布局文件整合到同一个代码块中,用注释标注文件边界,并附带详细注释。
// ---------------------- 文件: MainActivity.java ---------------------- package com.example.webserverapp; import android.Manifest; import android.os.Bundle; import android.os.Environment; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import java.io.IOException; // MainActivity:负责启动/停止服务器,发起客户端请求,并展示结果 public class MainActivity extends AppCompatActivity { private Button btnStartServer, btnStopServer, btnGetFiles; private TextView tvResult; private WebServer webServer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 加载布局文件 activity_main.xml // 初始化 UI 元素 btnStartServer = findViewById(R.id.btn_start_server); btnStopServer = findViewById(R.id.btn_stop_server); btnGetFiles = findViewById(R.id.btn_get_files); tvResult = findViewById(R.id.tv_result); // 点击“启动服务器” btnStartServer.setOnClickListener(v -> { // 确保已申请存储权限 if (PermissionManager.hasStoragePermission(this)) { startWebServer(); } else { PermissionManager.requestStoragePermission(this); } }); // 点击“停止服务器” btnStopServer.setOnClickListener(v -> stopWebServer()); // 点击“获取文件列表” btnGetFiles.setOnClickListener(v -> { // 异步请求 /files 接口 HttpClientHelper.get("http://127.0.0.1:8080/files", new HttpClientHelper.Callback() { @Override public void onSuccess(String response) { runOnUiThread(() -> tvResult.setText(response)); } @Override public void onFailure(Exception e) { runOnUiThread(() -> tvResult.setText("请求失败: " + e.getMessage())); } }); }); } /** 启动 WebServer */ private void startWebServer() { try { // 将根目录设为设备外部存储 Download 文件夹 String rootPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); webServer = new WebServer(8080, rootPath); webServer.start(); tvResult.setText("服务器已启动,监听端口 8080"); } catch (IOException e) { tvResult.setText("服务器启动失败: " + e.getMessage()); } } /** 停止 WebServer */ private void stopWebServer() { if (webServer != null) { webServer.stop(); tvResult.setText("服务器已停止"); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // 处理权限申请回调 super.onRequestPermissionsResult(requestCode, permissions, grantResults); PermissionManager.handlePermissionResult(this, requestCode, permissions, grantResults); } } // ---------------------- 文件: WebServer.java ---------------------- package com.example.webserverapp; import fi.iki.elonen.NanoHTTPD; import java.io.File; import java.io.FileInputStream; import java.util.Map; // WebServer:继承自 NanoHTTPD,重写 serve() 实现自定义路由与文件服务 public class WebServer extends NanoHTTPD { private String rootDir; public WebServer(int port, String rootDir) { super(port); this.rootDir = rootDir; } @Override public Response serve(IHTTPSession session) { String uri = session.getUri(); // 获取请求路径 Method method = session.getMethod(); // 请求方法:GET/POST Map params = session.getParms(); // 请求参数 // 路由分发 if (uri.equals("/files") && Method.GET.equals(method)) { return listFiles(); } else if (uri.equals("/upload") && Method.POST.equals(method)) { return handleUpload(session); } else { return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "404 Not Found"); } } /** 列举 rootDir 下所有文件名,并以 JSON 格式返回 */ private Response listFiles() { File dir = new File(rootDir); StringBuilder sb = new StringBuilder("["); File[] files = dir.listFiles(); if (files != null) { for (File f : files) { sb.append("\"").append(f.getName()).append("\","); } if (sb.charAt(sb.length() - 1) == ',') sb.deleteCharAt(sb.length() - 1); } sb.append("]"); return newFixedLengthResponse(Response.Status.OK, "application/json", sb.toString()); } /** 处理文件上传(POST /upload) */ private Response handleUpload(IHTTPSession session) { // TODO: 解析 multipart/form-data,保存文件到 rootDir return newFixedLengthResponse(Response.Status.OK, "text/plain", "上传成功"); } } // ---------------------- 文件: HttpClientHelper.java ---------------------- package com.example.webserverapp; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; // HttpClientHelper:封装 GET/POST 及文件下载上传 public class HttpClientHelper { public interface Callback { void onSuccess(String response); void onFailure(Exception e); } /** 发起 GET 请求 */ public static void get(String urlStr, Callback cb) { new Thread(() -> { try { URL url = new URL(urlStr); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); InputStream in = new BufferedInputStream(conn.getInputStream()); StringBuilder sb = new StringBuilder(); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) != -1) sb.append(new String(buf, 0, len)); in.close(); cb.onSuccess(sb.toString()); } catch (Exception e) { cb.onFailure(e); } }).start(); } /** 简单文件下载示例 */ public static void downloadFile(String urlStr, String destPath, Callback cb) { new Thread(() -> { try { URL url = new URL(urlStr); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); try (InputStream in = conn.getInputStream(); FileOutputStream fos = new FileOutputStream(destPath)) { byte[] buf = new byte[8192]; int len; while ((len = in.read(buf)) != -1) fos.write(buf, 0, len); } cb.onSuccess("下载完成: " + destPath); } catch (Exception e) { cb.onFailure(e); } }).start(); } // TODO: 实现文件上传 uploadFile() } // ---------------------- 文件: PermissionManager.java ---------------------- package com.example.webserverapp; import android.app.Activity; import android.content.pm.PackageManager; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; // PermissionManager:动态权限申请工具类 public class PermissionManager { private static final int REQ_STORAGE = 1001; public static boolean hasStoragePermission(Activity act) { return ContextCompat.checkSelfPermission(act, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } public static void requestStoragePermission(Activity act) { ActivityCompat.requestPermissions(act, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQ_STORAGE); } public static void handlePermissionResult(Activity act, int requestCode, String[] permissions, int[] grantResults) { if (requestCode == REQ_STORAGE) { // 简化:未授权则提示并退出 if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { // 提示用户开启权限 } } } } // ---------------------- 文件: res/layout/activity_main.xml ----------------------
五、代码解读
-
MainActivity.startWebServer():创建并启动 WebServer 实例,根目录设为 Download 文件夹。
-
MainActivity.stopWebServer():调用 webServer.stop() 关闭服务器监听。
-
WebServer.serve(IHTTPSession):核心路由分发,根据 URI 与 Method 调度到对应的处理方法。
-
WebServer.listFiles():扫描指定目录文件,返回 JSON 数组。
-
WebServer.handleUpload(...):(待完善)处理 multipart/form-data 上传。
-
HttpClientHelper.get(...):异步 GET 请求并回调结果。
-
HttpClientHelper.downloadFile(...):使用 HttpURLConnection 下载文件到指定路径。
-
PermissionManager 系列方法:检查和申请存储权限,确保读写外部存储。
六、项目总结
-
成果回顾
-
成功在 Android 端同时运行轻量级 HTTP 服务端和客户端。
-
支持基本 REST 接口与文件服务。
-
技术收获
-
深刻理解 HTTP 协议请求/响应流程。
-
掌握了 NanoHTTPD 在 Android 中的集成与使用。
-
强化了 Android 异步网络请求与多线程模型。
-
后续优化
-
完善文件上传逻辑,支持大文件断点续传。
-
增加界面友好度,如文件列表分页、下载进度显示。
-
引入 OkHttp 库替换原生 HttpURLConnection,提升性能与稳定性。
-
将业务逻辑拆分为 MVVM 模式,提高可维护性。
-
-
-
-
-
-