You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android项目嵌入可脚本化嵌入式Web服务器的实现问询

我之前在Android项目里用NanoHTTPD实现过完全一样的需求——静态文件服务加自定义JSON API,给你分享个完整的实现示例,应该能直接复用或者参考调整。

使用NanoHTTPD实现静态文件服务+自定义API

首先,你需要在项目里引入NanoHTTPD的依赖(比如在Gradle里添加implementation 'org.nanohttpd:nanohttpd:2.3.1')。然后创建一个自定义的服务器类,继承NanoHTTPD,重写serve方法来区分静态文件请求和API请求:

import org.nanohttpd.NanoHTTPD;
import org.nanohttpd.protocols.http.response.Response;
import org.nanohttpd.protocols.http.response.Status;
import org.nanohttpd.protocols.http.IHTTPSession;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class MyWebServer extends NanoHTTPD {
    private static final String API_PREFIX = "/actions/";
    private final File staticFilesDir;

    public MyWebServer(int port, File staticFilesDir) {
        super(port);
        this.staticFilesDir = staticFilesDir;
    }

    @Override
    public Response serve(IHTTPSession session) {
        String uri = session.getUri();
        Method method = session.getMethod();

        // 优先处理API请求
        if (uri.startsWith(API_PREFIX)) {
            return handleApiRequest(session, uri.substring(API_PREFIX.length()));
        }

        // 处理静态文件请求(比如/img/example.png、/assets/style.css)
        return handleStaticFileRequest(uri);
    }

    private Response handleApiRequest(IHTTPSession session, String action) {
        Method method = session.getMethod();
        
        // 处理POST请求到/actions/do_something
        if ("do_something".equals(action) && Method.POST.equals(method)) {
            try {
                // 解析POST请求体(如果是JSON格式,直接读取postData)
                Map<String, String> params = new HashMap<>();
                session.parseBody(params);
                String postJson = params.get("postData");

                // 调用你的自定义业务处理函数
                String responseJson = processDoSomething(postJson);

                // 返回JSON格式的响应
                return newFixedLengthResponse(Status.OK, "application/json", responseJson);
            } catch (IOException | ResponseException e) {
                return newFixedLengthResponse(Status.INTERNAL_ERROR, "text/plain", "请求处理失败");
            }
        }

        // 处理不存在的API端点
        return newFixedLengthResponse(Status.NOT_FOUND, "text/plain", "API端点不存在");
    }

    // 这里是你的自定义业务逻辑,根据传入的JSON做处理后返回结果
    private String processDoSomething(String postJson) {
        // 示例:解析传入的JSON,处理后返回成功响应
        // 实际开发中可以用Gson、Jackson等库来解析和生成JSON
        return "{\"status\":\"success\",\"message\":\"已处理请求\",\"received_data\":\"" + postJson + "\"}";
    }

    private Response handleStaticFileRequest(String uri) {
        // 根路径默认返回index.html
        if ("/".equals(uri)) {
            uri = "/index.html";
        }

        File targetFile = new File(staticFilesDir, uri);
        // 检查文件是否存在且是普通文件
        if (!targetFile.exists() || !targetFile.isFile()) {
            return newFixedLengthResponse(Status.NOT_FOUND, "text/plain", "文件不存在");
        }

        // 根据文件后缀设置正确的Content-Type
        String mimeType = getMimeType(targetFile.getName());
        try {
            // 返回文件流作为响应
            return newChunkedResponse(Status.OK, mimeType, new FileInputStream(targetFile));
        } catch (IOException e) {
            return newFixedLengthResponse(Status.INTERNAL_ERROR, "text/plain", "读取文件失败");
        }
    }

    // 简单的MIME类型映射,可根据需要扩展
    private String getMimeType(String fileName) {
        if (fileName.endsWith(".html") || fileName.endsWith(".htm")) {
            return "text/html";
        } else if (fileName.endsWith(".css")) {
            return "text/css";
        } else if (fileName.endsWith(".js")) {
            return "application/javascript";
        } else if (fileName.endsWith(".png")) {
            return "image/png";
        } else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
            return "image/jpeg";
        }
        // 默认二进制流
        return "application/octet-stream";
    }
}

在Android中启动服务器

因为Android的assets目录不能直接用File对象访问,所以需要先把静态文件(比如html、css、图片)复制到应用的内部存储,然后启动服务器:

import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "WebServerDemo";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 先把assets里的静态文件复制到内部存储
        copyAssetsToInternalStorage();
        // 启动服务器,端口设为8080
        startWebServer();
    }

    private void copyAssetsToInternalStorage() {
        try {
            String[] assetFiles = getAssets().list("");
            File staticDir = new File(getFilesDir(), "web_static");
            if (!staticDir.exists()) {
                boolean created = staticDir.mkdirs();
                if (!created) {
                    Log.e(TAG, "无法创建静态文件目录");
                    return;
                }
            }

            for (String fileName : assetFiles) {
                InputStream in = getAssets().open(fileName);
                File outFile = new File(staticDir, fileName);
                OutputStream out = new FileOutputStream(outFile);

                byte[] buffer = new byte[1024];
                int read;
                while ((read = in.read(buffer)) != -1) {
                    out.write(buffer, 0, read);
                }

                in.close();
                out.close();
            }
            Log.d(TAG, "静态文件复制完成");
        } catch (IOException e) {
            Log.e(TAG, "复制静态文件失败", e);
        }
    }

    private void startWebServer() {
        File staticDir = new File(getFilesDir(), "web_static");
        MyWebServer server = new MyWebServer(8080, staticDir);
        try {
            server.start();
            Log.d(TAG, "服务器启动成功,端口:8080");
            Log.d(TAG, "访问地址:http://" + getDeviceIp() + ":8080");
        } catch (IOException e) {
            Log.e(TAG, "服务器启动失败", e);
        }
    }

    // 辅助方法:获取设备的局域网IP
    private String getDeviceIp() {
        // 实现获取设备IP的逻辑,这里省略具体代码,你可以网上找现成的实现
        return "192.168.x.x";
    }
}

必要的权限配置

AndroidManifest.xml里添加网络权限,并且如果你的应用针对Android 9及以上,需要允许明文HTTP请求:

<uses-permission android:name="android.permission.INTERNET" />

<application
    ...
    android:usesCleartextTraffic="true">
    ...
</application>
其他可选的嵌入式Web服务器

如果NanoHTTPD不符合你的需求,还有这些不错的选择:

  • Ktor Server:JetBrains出品的轻量级服务器,支持Kotlin和Java,API设计现代化,自带JSON处理、路由管理等功能,适合复杂的API场景,在Android上运行稳定。
  • Spark Java:另一个轻量级Java服务器,路由配置非常简洁,支持静态文件服务和自定义API,学习成本低,适合快速开发小型服务。
  • Android SimpleWebServer:Android 10及以上系统自带的API,不需要引入第三方库,功能相对基础,适合只需要简单静态文件服务的场景。

内容的提问来源于stack exchange,提问作者David Refoua

火山引擎 最新活动