在 Android 上使用 tio-boot 运行 HTTP 服务
本文档详细介绍了如何在 Android 设备上使用 tio-boot
框架运行轻量级 HTTP 服务。tio-boot
基于 TIO 通信协议,实现了高性能、易用的 HTTP 服务能力,非常适合嵌入式或移动端场景。以下各步将带你从环境准备,到代码编写,再到最终测试,一气呵成地完成部署。
Android平台启动tio-boot
一、前提准备
- Android Studio(或其他兼容的 IDE)
- 至少 Android 5.0(API 21)及以上设备或模拟器
- 网络权限及存储权限已在
AndroidManifest.xml
中声明 - 依赖库需要从 Maven 仓库拉取,请确保 Gradle 能正常访问外网或已配置内网镜像
二、添加依赖项
在模块级 build.gradle
中引入以下依赖:
dependencies {
// Android 端视图注入和工具类
implementation 'com.litongjava:android-view-inject:1.0'
implementation 'com.litongjava:litongjava-android-utils:1.0.2'
// 核心 HTTP 服务框架
implementation 'com.litongjava:tio-boot:2.0.1'
// 可选:AOP 支持(如不在 Controller 中使用,可移除以节省包体积)
implementation 'com.litongjava:jfinal-aop:1.3.6'
}
提示:若项目追求极致性能,并且不需要在 Android 代码中依赖注解式 Controller 扫描,可去掉
jfinal-aop
依赖。
三、编写 HTTP 处理器(Handler)
新建一个普通 Java 类 HelloHandler
,用于演示最基本的 GET 请求:
package com.litongjava.android.tio.boot.controller;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.Resps;
public class HelloHandler {
// 响应 /hello 返回纯文本 "hello"
public HttpResponse hello(HttpRequest httpRequest) {
return Resps.txt(httpRequest, "hello");
}
// 响应 /hi 返回纯文本 "hi"
public HttpResponse hi(HttpRequest httpRequest) {
return Resps.txt(httpRequest, "hi");
}
}
四、定义注解式 Controller
借助注解路由支持,将方法绑定到路由路径:
package com.litongjava.android.tio.boot.controller;
import com.litongjava.annotation.RequestPath;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
@RequestPath // 表示此类下的方法需被扫描并注册
public class IndexController {
@RequestPath // 绑定到根路径 “/”
public String index(HttpRequest request) {
// 通过 TioRequestContext 获取底层 HttpResponse 对象
System.out.println("访问根路径,Request = " + request);
return "index"; // 返回视图名称或纯文本
}
// 绑定到 /student,自动将请求参数解析为 StudentReqVo 对象
public StudentReqVo student(StudentReqVo reqVo) {
return reqVo;
}
}
五、配置服务器路由与启动类
- 创建配置类
TioBootServerConfig
:
package com.litongjava.android.tio.boot.controller;
import com.litongjava.context.BootConfiguration;
import com.litongjava.tio.boot.http.handler.controller.TioBootHttpControllerRouter;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.http.server.router.HttpRequestRouter;
import java.util.ArrayList;
import java.util.List;
public class TioBootServerConfig implements BootConfiguration {
@Override
public void config() throws Exception {
TioBootHttpControllerRouter controllerRouter = TioBootServer.me().getControllerRouter();
if (controllerRouter != null) {
List<Class<?>> scannedClasses = new ArrayList<>();
scannedClasses.add(IndexController.class);
controllerRouter.addControllers(scannedClasses);
}
HttpRequestRouter requestRouter = TioBootServer.me().getRequestRouter();
if (requestRouter != null) {
HelloHandler helloHandler = new HelloHandler();
requestRouter.add("/hi", helloHandler::hi);
requestRouter.add("/hello", helloHandler::hello);
}
}
}
- 启动服务的主类
TioBootServerApp
:
package com.litongjava.android.tio.boot.controller;
import com.blankj.utilcode.util.NetworkUtils;
import com.litongjava.tio.boot.TioApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TioBootServerApp {
private static Logger log = LoggerFactory.getLogger(TioBootServerApp.class);
public static void run() {
long start = System.currentTimeMillis();
TioBootServerConfig tioBootServerConfig = new TioBootServerConfig();
String[] args = new String[]{"--server.port=10051"};
TioApplication.run(TioBootServerApp.class, tioBootServerConfig, args);
String ipAddressByWifi = NetworkUtils.getIpAddressByWifi();
log.info("ipAddressByWifi:{}", ipAddressByWifi);
long end = System.currentTimeMillis();
System.out.println((end - start) + "(ms)");
}
}
六、配置 AndroidManifest.xml
在根 AndroidManifest.xml
中,声明网络及存储权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.litongjava.android.tio.boot">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Litongjavatiobootforandroidstudy">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
七、设计简单 UI(启动按钮)
res/layout/activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/startBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/purple_200"
android:text="启动"></Button>
</LinearLayout>
八、在 MainActivity 中获取权限并启动服务
package com.litongjava.android.tio.boot;
import android.Manifest;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import com.litongjava.android.tio.boot.controller.TioBootServerApp;
import com.litongjava.android.utils.acp.AcpUtils;
import com.litongjava.android.utils.toast.ToastUtils;
import com.litongjava.android.view.inject.annotation.FindViewByIdLayout;
import com.litongjava.android.view.inject.annotation.OnClick;
import com.litongjava.android.view.inject.utils.ViewInjectUtils;
import com.mylhyl.acp.AcpListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
@FindViewByIdLayout(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
ViewInjectUtils.injectActivity(this, this);
}
@OnClick(R.id.startBtn)
public void startBtnOnClick(View view) {
String[] permissions = {
//写入外部设备权限
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.INTERNET,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
//创建acpListener
AcpListener acpListener = new AcpListener() {
@Override
public void onGranted() {
TioBootServerApp.run();
}
@Override
public void onDenied(List<String> permissions) {
ToastUtils.defaultToast(getApplicationContext(), permissions.toString() + "权限拒绝,无法写入日志");
}
};
AcpUtils.requestPermissions(this, permissions, acpListener);
}
}
启动后,点击界面上的启动按钮,你将会在 Logcat 中看到类似输出:
2025-07-03 22:08:41.363 [main] WARN Prop.<init>:72 - failed to create fileapp.propertiesRead-only file system
2025-07-03 22:08:41.364 [main] INFO EnvUtils.load:285 - load:app.properties
2025-07-03 22:08:41.365 [main] INFO EnvUtils.load:307 - app.env:null app.name:null
2025-07-03 22:08:41.367 [main] INFO TioApplicationContext.run:110 - Scanned classes count: 0
2025-07-03 22:08:41.370 [main] INFO TioApplicationContext.configureHttp:502 - Server session enabled: false
2025-07-03 22:08:41.371 [main] INFO TioApplicationContext.run:147 - Using cache: class com.litongjava.tio.utils.cache.mapcache.ConcurrentMapCacheFactory
2025-07-03 22:08:41.375 [main] INFO TioApplicationContext.run:199 - Server heartbeat timeout: 0
2025-07-03 22:08:41.379 [main] INFO TioServer.start:95 - tio-boot workerThreads:32
Compat change id reported: 288912692; UID 10350; state: DISABLED
2025-07-03 22:08:41.387 [main] INFO Threads.getTioExecutor:93 - new worker thead pool:com.litongjava.tio.utils.thread.pool.SynThreadPoolExecutor@793e53c[Running, pool size = 1, active threads = 0, queued tasks = 0, completed tasks = 0]
2025-07-03 22:08:41.388 [main] INFO TioApplicationContext.run:333 - HTTP handler:
{
"/hi": "com.litongjava.android.tio.boot.controller.-$$Lambda$yj_UDBEIbM3h-OA3eprGlvm4tbY@23912c5",
"/hello": "com.litongjava.android.tio.boot.controller.-$$Lambda$H2BQOHvwVwZqr4LaO93a0lg1uWk@f63341a"
}
2025-07-03 22:08:41.403 [main] INFO TioBootHttpControllerRouter.printMapping:454 - controller method mapping
{
"": "com.litongjava.android.tio.boot.controller.IndexController.index()",
"/student": "com.litongjava.android.tio.boot.controller.IndexController.student()"
}
2025-07-03 22:08:41.403 [main] INFO TioApplicationContext.run:363 - Initialization times (ms): Total: 36, Scan Classes: 1, Init Server: 9, Config: 1, Server: 10, Route: 15
2025-07-03 22:08:41.404 [main] INFO TioApplicationContext.printUrl:380 - Server port: 10051
2025-07-03 22:08:41.404 [main] INFO TioApplicationContext.printUrl:391 - Access URL: http://localhost:10051
2025-07-03 22:08:41.407 [main] INFO TioBootServerApp.run:22 - ipAddressByWifi:192.168.50.7
51(ms)
九、功能验证
在同一局域网内,使用浏览器或 curl 访问:
http://192.168.50.7:10051/hi
→ 应返回hi
http://192.168.50.7:10051/hello
→ 应返回hello
http://192.168.50.7:10051/
→ 应返回index
(或相应视图名内容)
Android 平台下的 jfinal‑aop 自定义 ComponentScanner
Android 平台的 DexClassLoader 不会像标准 Java ClassLoader 那样,将 .dex
文件暴露为资源,因此无法通过 ClassLoader.getResources()
枚举出注解类。tio-boot
在 Android 上暂不支持自动扫描 APK 内的 Controller,只能手动在配置类中显式添加。
为了在 Android 平台上使用 jfinal‑aop 的自动扫描功能,需要自定义 ComponentScanner
—— 在 APK 中遍历所有类,并筛选出目标注解标记的组件。本文档将从背景原理、实现细节、配置流程和示例代码四个方面,介绍如何在 Android 环境中优雅地集成 jfinal‑aop。
一、背景与原理
jfinal‑aop 默认的 ComponentScanner
实现基于标准 Java ClassLoader,在桌面或服务器环境下通过文件系统或 Jar 包扫描类,但在 Android 平台中由于运行时环境不同,需要使用 DexFile
枚举 APK 中的 DEX 条目,从而获取所有已编译的类路径。通过结合 Android 全局 Context
和 DexFile
,即可动态加载并检测注解。
二、核心实现:AndroidComponentScanner
1. 引入依赖
// 在 Android 模块的 build.gradle 中添加:
implementation 'com.litongjava:jfinal-aop:1.3.7' // jfinal‑aop 本身
2. 自定义扫描器代码
package com.litongjava.android.tio.boot.controller;
import android.content.Context;
import com.blankj.utilcode.util.Utils;
import com.litongjava.jfinal.aop.process.ComponentAnnotation;
import com.litongjava.jfinal.aop.scanner.ComponentScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import dalvik.system.DexFile;
/**
* Android 平台下的 ComponentScanner 实现,利用 DexFile 枚举 APK 中的类并根据注解筛选。
*/
public class AndroidComponentScanner implements ComponentScanner {
private static final Logger log = LoggerFactory.getLogger(AndroidComponentScanner.class);
@Override
public List<Class<?>> scan(Class<?>[] primarySources, boolean printScannedClasses) throws Exception {
// 1. 获取全局 Application Context
Context context = Utils.getApp();
// 2. 读取 APK 路径并打开 DexFile
String apkPath = context.getPackageCodePath();
DexFile dexFile = new DexFile(apkPath);
Enumeration<String> entries = dexFile.entries();
// 3. 准备结果容器和注解列表
List<Class<?>> classes = new ArrayList<>();
List<Class<? extends Annotation>> annotations = ComponentAnnotation.getAnnotations();
// 4. 遍历所有类名项,并根据包名和注解进行过滤
while (entries.hasMoreElements()) {
String className = entries.nextElement();
// 仅扫描应用自身包下的类
if (!className.startsWith(context.getPackageName())) continue;
Class<?> clazz;
try {
clazz = Class.forName(className, false, context.getClassLoader());
} catch (Throwable t) {
// 无法加载的类直接跳过
continue;
}
// 如果该类被任意目标注解标记,则添加至结果
for (Class<? extends Annotation> annotation : annotations) {
if (clazz.isAnnotationPresent(annotation)) {
classes.add(clazz);
if (printScannedClasses) {
log.info("Scanned component: {}", className);
}
break;
}
}
}
return classes;
}
}
3. 实现说明
- Context 获取:借助 AndroidUtilCode 的
Utils.getApp()
获取全局Application
实例,避免手动传入。 - DexFile 枚举:通过
new DexFile(apkPath)
打开当前 APK,并用entries()
提取所有类名。 - Class 加载:仅对自身包名下的类调用
Class.forName
,并捕获任何加载失败的异常。 - 注解过滤:从
ComponentAnnotation.getAnnotations()
获取用户自定义的注解列表,一旦匹配即收集并可选性打印。
三、取消手动注册:TioBootServerConfig 示例
在桌面环境中,我们通常要手动将 Handler 注册到路由,但在 Android 平台下,可借助上述扫描器自动检测所有带有注解的组件,切换到纯注解驱动。以下为示例:
package com.litongjava.android.tio.boot.controller;
import com.litongjava.context.BootConfiguration;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.http.server.router.HttpRequestRouter;
/**
* 简易示例:演示手动注册逻辑,可根据业务删减。
*/
public class TioBootServerConfig implements BootConfiguration {
@Override
public void config() throws Exception {
HttpRequestRouter requestRouter = TioBootServer.me().getRequestRouter();
if (requestRouter != null) {
HelloHandler handler = new HelloHandler();
requestRouter.add("/hi", handler::hi);
requestRouter.add("/hello", handler::hello);
}
}
}
在引入注解驱动后,可将 HelloHandler
标记为注解组件,删除此手动添加逻辑,让框架完成自动装配。
四、启动流程:在 Application 中注入扫描器
最后一步,将自定义的 AndroidComponentScanner
设置到 AopContext
,并启动 TioApplication
:
package com.litongjava.android.tio.boot.controller;
import com.blankj.utilcode.util.NetworkUtils;
import com.litongjava.jfinal.aop.context.AopContext;
import com.litongjava.tio.boot.TioApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 启动入口:向 AopContext 注入 AndroidComponentScanner 并启动服务器。
*/
public class TioBootServerApp {
private static final Logger log = LoggerFactory.getLogger(TioBootServerApp.class);
public static void run() {
long start = System.currentTimeMillis();
// 1. 设置自定义 ComponentScanner
AopContext.me().setComponentScanner(new AndroidComponentScanner());
// 2. 创建并执行配置
TioBootServerConfig config = new TioBootServerConfig();
String[] args = new String[]{"--server.port=10051"};
TioApplication.run(TioBootServerApp.class, config, args);
// 3. 打印网络地址和启动耗时
String ip = NetworkUtils.getIpAddressByWifi();
log.info("Server running at: {}:10051", ip);
log.info("Startup completed in {} ms", System.currentTimeMillis() - start);
}
}