当前位置: 首页 > news >正文

Android 端启动 HTTP 服务:从基础实现到实战应用

在 Android 开发中,我们习惯了 App 作为 HTTP 客户端 调用后端接口,但在本地设备互联、跨模块通信等场景下,让 App 扮演 服务端 角色(启动 HTTP 服务)同样重要。比如手机与电脑同局域网传文件、原生模块与 WebView 交互、物联网设备本地控制等场景,本地 HTTP 服务能以低延迟、无依赖的优势解决问题。本文将详解两种主流实现方案,带您从 0 到 1 搭建 Android 端 HTTP 服务。

一、为什么要在 Android 端启动 HTTP 服务?

先明确核心应用场景,避免技术滥用:

  1. 本地文件共享:手机与电脑 / 平板连同一 WiFi,通过浏览器访问手机 HTTP 服务,直接下载 / 上传文件(无需安装微信、QQ 等工具);
  2. 跨模块通信:原生(Kotlin/Java)与 WebView、Flutter 等模块,通过本地 HTTP 接口传递数据(替代复杂的 JsBridge 或 Platform Channel);
  3. 物联网本地控制:智能设备(如蓝牙网关)通过 Android 端 HTTP 服务接收控制指令(不依赖云端,延迟更低);
  4. 后端模拟调试:开发阶段本地启动 HTTP 服务,模拟后端接口返回固定数据,无需等待服务器开发完成。

这些场景的共性是 “局域网内短距离通信”,无需暴露公网,安全性和性能可控。

二、基础准备:权限与环境配置

启动 HTTP 服务前,需先解决 Android 系统的权限和网络限制。

2.1 声明核心权限

在 AndroidManifest.xml 中添加必要权限,确保服务能正常通信和访问资源:

<!-- 1. 必需:网络访问权限(HTTP 服务依赖) -->
<uses-permission android:name="android.permission.INTERNET" /><!-- 2. 可选:若需读取本地文件(如文件服务器),需存储权限 -->
<!-- Android 13(API 33)以下:读写外部存储 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Android 13+:按类型申请媒体权限(以图片为例) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /><!-- 3. 可选:若需长期后台运行(如持续提供服务),需后台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

注意:Android 6.0(API 23)以上需动态申请危险权限(如存储权限),可使用 ActivityResultContracts 实现。

2.2 配置网络安全(Android 9+)

Android 9(API 28)默认禁止明文 HTTP 通信(仅允许 HTTPS),需配置网络安全规则放行本地 HTTP:

1.在 res/xml 目录下创建 network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config><!-- 允许本地局域网明文 HTTP 通信 --><domain-config cleartextTrafficPermitted="true"><!-- 匹配本地 IP 段(如 192.168.x.x、10.x.x.x) --><domain includeSubdomains="true">192.168.0.0/16</domain><domain includeSubdomains="true">10.0.0.0/8</domain><domain includeSubdomains="true">127.0.0.1</domain> <!-- 本地回环地址 --></domain-config>
</network-security-config>

2.在 AndroidManifest.xml 的 application 标签中引用配置:

<application...android:networkSecurityConfig="@xml/network_security_config">...
</application>

2.3 获取本地 IP 地址

其他设备(如电脑)需通过 Android 设备的 局域网 IP 访问 HTTP 服务,需先获取 IP 地址:

object NetworkUtils {/*** 获取 WiFi 下的本地 IP 地址(如 192.168.1.100)*/fun getLocalIpAddress(): String? {try {// 遍历所有网络接口val interfaces = NetworkInterface.getNetworkInterfaces()while (interfaces.hasMoreElements()) {val intf = interfaces.nextElement()// 遍历接口下的 IP 地址val addresses = intf.inetAddresseswhile (addresses.hasMoreElements()) {val addr = addresses.nextElement()// 排除回环地址(127.0.0.1)和 IPv6 地址if (!addr.isLoopbackAddress && addr is Inet4Address) {return addr.hostAddress ?: continue}}}} catch (e: Exception) {e.printStackTrace()}return null}
}

调用示例:

val localIp = NetworkUtils.getLocalIpAddress()
Log.d("HTTP_SERVER", "本地 IP:$localIp") // 输出如 "192.168.1.100"

三、方案一:轻量型服务(NanoHTTPD)

NanoHTTPD 是一个开源的轻量级 HTTP 服务器框架,仅一个 Java 文件,体积小(约 100KB),适合简单场景(如文件共享、基础接口)。

3.1 集成 NanoHTTPD

无需添加复杂依赖,直接将核心类 NanoHTTPD.java 复制到项目中(推荐使用官方最新版本):

  1. 下载地址:NanoHTTPD 官方 GitHub
  2. 复制 nanohttpd/src/main/java/fi/iki/elonen/NanoHTTPD.java 到项目 java 目录下(注意包名适配)。

3.2 实现基础 HTTP 服务

继承 NanoHTTPD 并重写 serve 方法,处理客户端请求(如 GET、POST):

class LocalHttpServer(port: Int) : NanoHTTPD(port) {// 存储服务启动状态var isRunning = false/*** 处理所有 HTTP 请求* @param session 请求会话(包含请求方法、参数、头部等)*/override fun serve(session: IHTTPSession): Response {// 1. 获取请求信息val method = session.method // 请求方法(GET/POST)val uri = session.uri // 请求路径(如 /api/hello、/file/download)val params = session.parms // GET 参数(POST 参数需额外解析)// 2. 路由分发:根据 URI 处理不同请求return when (uri) {"/api/hello" -> handleHelloRequest(params)"/file/list" -> handleFileListRequest()else -> handle404Response() // 未知路径返回 404}}/*** 处理 /api/hello 请求(示例:返回 JSON 数据)*/private fun handleHelloRequest(params: Map<String, String>): Response {// 获取 GET 参数(如 ?name=Android)val name = params["name"] ?: "Guest"// 构建 JSON 响应val responseJson = """{"code": 200,"message": "Hello, $name!","data": {"server": "NanoHTTPD","time": ${System.currentTimeMillis()}}}""".trimIndent()// 返回 JSON 响应(设置 Content-Type 为 application/json)return newFixedLengthResponse(Response.Status.OK,"application/json; charset=utf-8",responseJson)}/*** 处理 /file/list 请求(示例:返回本地图片列表)*/private fun handleFileListRequest(): Response {// 读取本地图片文件(需存储权限,此处简化处理)val imageFiles = mutableListOf<String>()// (实际项目中需通过 MediaStore 或 SAF 获取文件路径)imageFiles.add("photo1.jpg")imageFiles.add("photo2.jpg")val responseJson = """{"code": 200,"message": "success","data": {"files": ${Gson().toJson(imageFiles)}}}""".trimIndent()return newFixedLengthResponse(Response.Status.OK,"application/json; charset=utf-8",responseJson)}/*** 处理 404 未知路径*/private fun handle404Response(): Response {return newFixedLengthResponse(Response.Status.NOT_FOUND,"text/html; charset=utf-8","<h1>404 Not Found</h1>")}/*** 启动服务*/fun startServer(): Boolean {return try {start(NanoHTTPD.SOCKET_READ_TIMEOUT, false)isRunning = trueLog.d("HTTP_SERVER", "服务启动成功,端口:$listeningPort")true} catch (e: IOException) {e.printStackTrace()Log.e("HTTP_SERVER", "服务启动失败:${e.message}")false}}/*** 停止服务*/fun stopServer() {stop()isRunning = falseLog.d("HTTP_SERVER", "服务已停止")}
}

3.3 启动与测试服务

在 Activity 或 Service 中启动服务,注意 不能在 UI 主线程启动(需开子线程或用协程):

class MainActivity : AppCompatActivity() {private lateinit var httpServer: LocalHttpServerprivate val PORT = 8080 // 端口号(建议 1024-65535 之间,避免冲突)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 1. 初始化并启动服务(协程中执行,避免阻塞主线程)lifecycleScope.launch(Dispatchers.IO) {httpServer = LocalHttpServer(PORT)val startSuccess = httpServer.startServer()if (startSuccess) {val localIp = NetworkUtils.getLocalIpAddress()// 更新 UI 显示服务地址(需切回主线程)launch(Dispatchers.Main) {findViewById<TextView>(R.id.tv_server_info).text = "服务地址:http://$localIp:$PORT"}}}// 2. 停止服务(页面销毁时)lifecycleScope.launchWhenDestroyed {if (::httpServer.isInitialized && httpServer.isRunning) {httpServer.stopServer()}}}
}

测试方式

  1. 确保电脑与手机连同一 WiFi;
  2. 打开电脑浏览器,输入地址 http://[手机IP]:8080/api/hello?name=Android;
  3. 成功返回 JSON 数据,说明服务正常运行。

四、方案二:现代型服务(Ktor)

Ktor 是 JetBrains 推出的现代异步 HTTP 框架,支持 Kotlin 协程、路由、拦截器等特性,适合复杂场景(如多接口、异步处理、WebSocket)。

4.1 集成 Ktor

在 build.gradle(Module 级)中添加依赖:

dependencies {// 1. Ktor 核心依赖(HTTP 服务)implementation "io.ktor:ktor-server-core-jvm:2.3.3"implementation "io.ktor:ktor-server-netty-jvm:2.3.3" // Netty 引擎// 2. 可选:JSON 序列化(处理请求/响应)implementation "io.ktor:ktor-serialization-gson-jvm:2.3.3"implementation "io.ktor:ktor-server-content-negotiation-jvm:2.3.3"// 3. 可选:CORS 跨域(若需外部设备访问)implementation "io.ktor:ktor-server-cors-jvm:2.3.3"
}

4.2 实现 Ktor HTTP 服务

Ktor 采用 “插件化” 设计,通过配置路由、插件实现服务功能:

class KtorHttpServer {private var server: NettyApplicationEngine? = nullvar isRunning = false/*** 启动 Ktor 服务* @param port 端口号*/suspend fun startServer(port: Int) {try {// 启动服务(协程中执行,非阻塞)server = embeddedServer(Netty, port = port) {// 1. 安装插件:CORS(允许跨域访问,外部设备可请求)install(CORS) {anyHost() // 允许所有主机(仅局域网内使用,生产环境需限制)allowMethod(HttpMethod.Get)allowMethod(HttpMethod.Post)}// 2. 安装插件:ContentNegotiation(JSON 序列化)install(ContentNegotiation) {gson {setPrettyPrinting() // 格式化 JSON 输出}}// 3. 配置路由(处理不同请求)routing {// 路由 1:/api/hello(GET 请求,返回实体类)get("/api/hello") {// 获取请求参数val name = call.request.queryParameters["name"] ?: "Guest"// 返回 JSON 响应(自动序列化实体类)call.respond(ApiResponse(code = 200,message = "Hello, $name!",data = ServerInfo(server = "Ktor",time = System.currentTimeMillis(),port = port)))}// 路由 2:/api/file/download(GET 请求,下载本地文件)get("/api/file/download") {val fileName = call.request.queryParameters["fileName"] ?: run {call.respond(ApiResponse(code = 400, message = "文件名不能为空"))return@get}// (实际项目中需通过 MediaStore 获取文件路径和输入流)val file = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName)if (!file.exists()) {call.respond(ApiResponse(code = 404, message = "文件不存在"))return@get}// 设置响应头,触发浏览器下载call.response.headers.append(HttpHeaders.ContentDisposition,ContentDisposition.Attachment.withParameter(ContentDisposition.Parameters.FileName,fileName).toString())// 返回文件流call.respondFile(file)}// 路由 3:/api/data(POST 请求,接收 JSON 参数)post("/api/data") {// 解析 POST 请求体(自动反序列化为实体类)val request = call.receive<DataRequest>()// 处理业务逻辑(如存储数据)val result = "收到数据:${request.content},类型:${request.type}"// 返回响应call.respond(ApiResponse(code = 200, message = "success", data = result))}}}.start(wait = true) // wait = true:阻塞当前协程,直到服务停止isRunning = trueLog.d("KTOR_SERVER", "服务启动成功,端口:$port")} catch (e: Exception) {e.printStackTrace()Log.e("KTOR_SERVER", "服务启动失败:${e.message}")isRunning = false}}/*** 停止服务*/fun stopServer() {server?.stop(1000, 1000) // 1秒等待停止,1秒强制停止isRunning = falseLog.d("KTOR_SERVER", "服务已停止")}// 数据类:API 响应统一格式data class ApiResponse(val code: Int,val message: String,val data: Any? = null)// 数据类:服务信息data class ServerInfo(val server: String,val time: Long,val port: Int)// 数据类:POST 请求体格式data class DataRequest(val content: String,val type: String)
}

4.3 启动 Ktor 服务

Ktor 服务启动同样需在后台线程执行,推荐用 Kotlin 协程:

 
class KtorServerActivity : AppCompatActivity() {private lateinit var ktorServer: KtorHttpServerprivate val PORT = 8081 // 与 NanoHTTPD 端口区分,避免冲突override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_ktor_server)// 初始化服务并启动ktorServer = KtorHttpServer()lifecycleScope.launch(Dispatchers.IO) {ktorServer.startServer(PORT)// 显示服务地址val localIp = NetworkUtils.getLocalIpAddress()launch(Dispatchers.Main) {findViewById<TextView>(R.id.tv_ktor_info).text = "Ktor 服务:http://$localIp:$PORT"}}// 停止服务(页面销毁时)lifecycleScope.launchWhenDestroyed {if (ktorServer.isRunning) {ktorServer.stopServer()}}}
}

测试 POST 请求(用 Postman):

1.地址:http://[手机IP]:8081/api/data;

2.请求体(JSON):

{"content": "测试 Ktor POST","type": "text"
}

3.响应:返回 code=200 和处理结果,说明服务正常。

五、避坑指南:常见问题与解决方案

在 Android 端启动 HTTP 服务时,容易遇到权限、线程、兼容性问题,提前规避:

5.1 问题 1:服务启动失败(端口被占用)

  • 原因:选择的端口(如 80)被系统或其他 App 占用;
  • 解决方案
  1. 1.选择 1024-65535 之间的端口(如 8080、8888);

2.启动前检测端口是否可用(通过 ServerSocket 尝试绑定):

fun isPortAvailable(port: Int): Boolean {return try {ServerSocket(port).use { true } // 绑定成功,端口可用} catch (e: IOException) {false // 端口被占用}
}

5.2 问题 2:外部设备无法访问服务

  • 原因
  1. 手机与外部设备(如电脑)未连同一 WiFi;
  2. 手机开启了热点,但外部设备连热点后未获取正确 IP;
  3. 网络安全配置未放行本地 IP;
  • 解决方案
  1. 确认设备在同一局域网,用 ping [手机IP] 测试连通性;
  2. 热点场景下,手机 IP 通常为 192.168.43.1;
  3. 检查 network_security_config.xml 是否包含本地 IP 段。

5.3 问题 3:读取文件失败(存储权限)

  • 原因:Android 10+ 引入分区存储,限制 App 直接访问外部存储文件;
  • 解决方案
  1. Android 10-12:在 AndroidManifest.xml 中添加 android:requestLegacyExternalStorage="true"(兼容模式);
  2. Android 13+:使用 MediaStore 或 SAF(存储访问框架)获取文件,避免直接操作 File 路径。

5.4 问题 4:服务在后台被杀死

  • 原因:Android 后台进程限制,服务在后台长时间运行会被系统回收;
  • 解决方案
  1. 若需长期运行,将服务改为 前台服务(显示通知,避免被回收);
  2. 重写 Service 的 onStartCommand 返回 START_STICKY(服务被杀死后尝试重启)。

六、最佳实践:提升服务稳定性与安全性

  1. 按需启动服务:不用时及时停止服务,避免占用资源和耗电;
  2. 限制访问范围:仅允许局域网访问,不暴露公网(通过 CORS 配置或 IP 过滤);
  3. 处理并发请求:NanoHTTPD 默认单线程,高并发场景需自定义线程池;Ktor 基于协程,天生支持高并发;
  4. 添加请求验证:敏感接口(如文件上传)添加简单验证(如 Token),避免恶意访问;
  5. 适配后台限制:Android 12+ 对后台服务限制更严格,长期运行建议用 WorkManager 配合前台服务。

七、总结:两种方案如何选择?

方案

优点

缺点

适用场景

NanoHTTPD

体积小、无依赖、集成简单

功能基础、无协程支持、需手动处理并发

简单场景(文件共享、基础接口)

Ktor

支持协程、路由清晰、插件丰富

依赖较多、体积较大

复杂场景(多接口、异步处理)

如果仅需简单的本地通信,NanoHTTPD 足够轻量;如果需要多路由、异步处理或未来扩展 WebSocket,Ktor 是更现代的选择。

通过本文的两种方案,您可以根据需求在 Android 端快速搭建 HTTP 服务,实现本地设备互联、跨模块通信等场景。实际开发中,还需结合业务需求优化权限、线程和安全性,让服务既稳定又易用。


文章转载自:

http://NSRwKMx2.knryp.cn
http://JN3NGoJC.knryp.cn
http://L8rkZo9x.knryp.cn
http://6bPbhNjj.knryp.cn
http://9ZqksJ2f.knryp.cn
http://SzUoTat7.knryp.cn
http://Me7pYMbw.knryp.cn
http://RpvWLI4j.knryp.cn
http://eR5bNSmF.knryp.cn
http://ec2SKOBD.knryp.cn
http://M46cgLu9.knryp.cn
http://gOFQyPIQ.knryp.cn
http://fOVTVuaT.knryp.cn
http://CmIwuxC7.knryp.cn
http://kbHy9CBv.knryp.cn
http://0mwpzRws.knryp.cn
http://ML4xQMP9.knryp.cn
http://qVzqJBPS.knryp.cn
http://vxsysMkn.knryp.cn
http://bZ9M5TmK.knryp.cn
http://J6MbZIYd.knryp.cn
http://5jJEXBGc.knryp.cn
http://RySVkCoS.knryp.cn
http://mQ5Aea2H.knryp.cn
http://HjFJdoR3.knryp.cn
http://UtexyEO1.knryp.cn
http://FambKCp7.knryp.cn
http://TVgfymMD.knryp.cn
http://xVwJv9uR.knryp.cn
http://nCfbPh4c.knryp.cn
http://www.dtcms.com/a/387553.html

相关文章:

  • 《2D横版平台跳跃游戏中角色二段跳失效与碰撞体穿透的耦合性Bug解析》
  • 基于本机知识库 + 豆包(火山引擎)+ MCP的落地方案
  • OpenCV 风格迁移、DNN模块 案例解析及实现
  • php实现火山引擎 【双向流式websocket-V3-支持复刻2.0/混音mix】开箱即用,可用于各种PHP框架。
  • 【lua】Windows环境下cffi-lua使用指南:编译、安装与测试
  • 我优化了昨天的C++/Lua插件系统:添加了插件沙箱、Lua 状态池
  • 【数据库】SQLite安装部署与使用指南
  • Android Kotlin 请求方法代码
  • 【easy_tools】一个跨平台裸机工具库,包含任务/堆栈/消息/定时器/日志等实现
  • ARM(11) - LM75
  • FPGA实现SRIO数据回环传输,基于Serial Rapidlo Gen2架构,提供6套工程源码和技术支持
  • 第十九章 Arm C1-Premium TRBE技术解析
  • HTB writeup
  • 科学研究系统性思维的理论基础:数字化研究工具
  • 基于有限元-元胞自动机法(CAFE)的增材制造过程组织模拟
  • 电视行业复兴,数字化制造如何重塑“视界”新格局?
  • 从兼容到极致性能——qData数据中台商业版核心指标解读
  • MAC-枚举反射工具类
  • 搜索百科(1):Lucene —— 打开现代搜索世界的第一扇门
  • 学习日记-JS+DOM-day57-9.17
  • Java异常处理最佳实践指南
  • Ansible简介
  • pytest使用总结笔记
  • 在VSCode中设置Qt开发环境
  • 斜杠命令Slash Commands:Roo Code 的自动化利器
  • 大数据毕业设计选题推荐-基于大数据的慢性肾病数据可视化分析系统-Spark-Hadoop-Bigdata
  • 基于红尾鹰优化的LSTM深度学习网络模型(RTH-LSTM)的一维时间序列预测算法matlab仿真
  • TDengine IDMP 基本功能——数据可视化(2. 柱状图)
  • Python与Google Earth Engine (GEE) 实现地理空间数据自动化处理:高效分析与批量任务执行
  • Dify Agent + AntV 实战:从 0 到 1 打造数据可视化解决方案