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

Android Http-server 本地 web 服务

时间:2025年2月16日

地点:深圳.前海湾

需求

我们都知道 webview 可加载 URI,他有自己的协议 scheme:

  • content://  标识数据由 Content Provider 管理
  • file://     本地文件 
  • http://     网络资源

特别的,如果你想直接加载 Android 应用内 assets 内的资源你需要使用`file:///android_asset`,例如:

file:///android_asset/demo/index.html

我们本次的需求是:有一个 H5 游戏,需要 http 请求 index.html 加载、运行游戏

通常我们编写的 H5 游戏直接拖动 index.html 到浏览器打开就能正常运行游戏,当本次的游戏就是需要 http 请求才能,项目设计就是这样子啦(省略一千字)

开始

如果你有一个 index.html 的 File 对象 ,可以使用`Uri.fromFile(file)` 转换获得 Uri 可以直接加载

mWebView.loadUrl(uri.toString());

这周染上甲流,很不舒服,少废话直接上代码

  • 复制 assets 里面游戏文件到 files 目录
  • 找到 file 目录下的 index.html
  • 启动 http-server 服务
  • webview 加载 index.html
import java.io.File;public class MainActivity extends AppCompatActivity {private final String TAG = "hello";private WebView mWebView;private Handler H = new Handler(Looper.getMainLooper());private final int LOCAL_HTTP_PORT = 8081;private final String SP_KEY_INDEX_PATH = "index_path";private LocalHttpGameServer mLocalHttpGameServer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});// 初始化 webviewmWebView = findViewById(R.id.game_webview);initWebview();testLocalHttpServer();}private void testLocalHttpServer(Context context) {final String assetsGameFilename = "H5Game";copyAssetsGameFileToFiles(context, assetsGameFilename, new FindIndexCallback() {@Overridepublic void onResult(File indexFile) {if (indexFile == null || !indexFile.exists()) {return;}// 大概测试了下 NanoHTTPD 似乎需要在主线程启动H.post(new Runnable() {@Overridepublic void run() {// 启动 http-serverif (mLocalHttpGameServer == null) {final String gameRootPath = indexFile.getParentFile().getAbsolutePath();mLocalHttpGameServer = new LocalHttpGameServer(LOCAL_HTTP_PORT, gameRootPath);}// 访问本地服务 localhost 再合适不过// 当然你也可以使用当前网络的 IP 地址,但是你得获取 IP 地址,指不定还有什么获取敏感数据的隐私String uri = "http://localhost:" + LOCAL_HTTP_PORT + "/index.html";mWebView.loadUrl(uri);}});}});}// 把 assets 目录下的文件拷贝到应用 files 目录private void copyAssetsGameFileToFiles(Context context, String filename, FindIndexCallback callback) {if (context == null) {return;}String gameFilename = findGameFilename(context.getAssets(), filename);// 文件拷贝毕竟是耗时操作,开启一个子线程吧new Thread(new Runnable() {@Overridepublic void run() {// 读取拷贝到 files 目录后 index.html 文件路径的缓存// 防止下载再次复制文件String indexPath = SPUtil.getString(SP_KEY_INDEX_PATH, "");if (!indexPath.isEmpty() && new File(indexPath).exists()) {if (callback != null) {callback.onResult(new File(indexPath));}return;}File absGameFileDir = copyAssetsToFiles(context, gameFilename);// 拷贝到 files 目录后,找到第一个 index.html 文件缓存路径File indexHtml = findIndexHtml(absGameFileDir);if (indexHtml != null && indexHtml.exists()) {SPUtil.setString(SP_KEY_INDEX_PATH, indexHtml.getAbsolutePath());}if (callback != null) {callback.onResult(indexHtml);}}}).start();}public File copyAssetsToFiles(Context context, String assetFileName) {File filesDir = context.getFilesDir();File outputFile = new File(filesDir, assetFileName);try {String fileNames[] = context.getAssets().list(assetFileName);if (fileNames == null) {return null;}// lenght == 0 可以认为当前读取的是文件,否则是目录if (fileNames.length > 0) {if (!outputFile.exists()) {outputFile.mkdirs();}// 目录,主要路径拼接,因为需要拷贝目录下的所有文件for (String fileName : fileNames) {// 递归哦copyAssetsToFiles(context, assetFileName + File.separator + fileName);}} else {// 文件InputStream is = context.getAssets().open(assetFileName);FileOutputStream fos = new FileOutputStream(outputFile);byte[] buffer = new byte[1024];int byteCount;while ((byteCount = is.read(buffer)) != -1) {fos.write(buffer, 0, byteCount);}fos.flush();is.close();fos.close();}} catch (Exception e) {return null;}return outputFile;}private interface FindIndexCallback {void onResult(File indexFile);}public static File findIndexHtml(File directory) {if (directory == null || !directory.exists() || !directory.isDirectory()) {return null;}File[] files = directory.listFiles();if (files == null) {return null;}for (File file : files) {if (file.isFile() && file.getName().equals("index.html")) {return file;} else if (file.isDirectory()) {File index = findIndexHtml(file);if (index != null) {return index;}}}return null;}private String findGameFilename(AssetManager assets, String filename) {try {// 这里传空字符串,读取返回 assets 目录下所有的名列表String[] firstFolder = assets.list("");if (firstFolder == null || firstFolder.length == 0) {return null;}for (String firstFilename : firstFolder) {if (firstFilename == null || firstFilename.isEmpty()) {continue;}if (firstFilename.equals(filename)) {return firstFilename;}}} catch (IOException e) {}return null;}private void initWebview() {mWebView.setBackgroundColor(Color.WHITE);WebSettings webSettings = mWebView.getSettings();webSettings.setJavaScriptEnabled(true);// 游戏基本都有 jswebSettings.setDomStorageEnabled(true);webSettings.setAllowUniversalAccessFromFileURLs(true);webSettings.setAllowContentAccess(true);// 文件是要访问的,毕竟要加载本地资源webSettings.setAllowFileAccess(true);webSettings.setAllowFileAccessFromFileURLs(true);webSettings.setUseWideViewPort(true);webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);webSettings.setJavaScriptCanOpenWindowsAutomatically(true);webSettings.setLoadWithOverviewMode(true);webSettings.setDisplayZoomControls(false);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);}if (Build.VERSION.SDK_INT >= 26) {webSettings.setSafeBrowsingEnabled(true);}}
}

差点忘了,高版本 Android 设备需要配置允许 http 明文传输,AndroidManifest 需要以下配置:

  1. 必须有网络权限 <uses-permission android:name="android.permission.INTERNET" />
  2. application 配置 ​​​​​​​​​​​​​​​​​​
  • android:networkSecurityConfig="@xml/network_security_config
  • ​​​​​​​​​​​​​​​​​​​​​android:usesCleartextTraffic="true"

network_security_config.xml

<?xml version="1.0" encoding="UTF-8"?><network-security-config><base-config cleartextTrafficPermitted="true"><trust-anchors>     <certificates src="user"/>      <certificates src="system"/>    </trust-anchors>   </base-config>
</network-security-config>

http-server 服务类很简单,感谢开源

今天的主角:NanoHttpd Java中的微小、易于嵌入的HTTP服务器

这里值得关注的是 gameRootPath,有了它才能正确找到本地资源所在位置

package com.example.selfdemo.http;import android.util.Log;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;import fi.iki.elonen.NanoHTTPD;public class LocalHttpGameServer extends NanoHTTPD {private String gameRootPath = "";private final String TAG = "hello";public GameHttp(int port, String gameRootPath) {super(port);this.gameRootPath = gameRootPath;init();}public GameHttp(String hostname, int port, String gameRootPath) {super(hostname, port);this.gameRootPath = gameRootPath;init();}private void init() {try {final int TIME_OUT = 1000 * 60;start(TIME_OUT, true);//start(NanoHTTPD.SOCKET_READ_TIMEOUT, true);Log.d(TAG, "http-server init: 启动");} catch (IOException e) {Log.d(TAG, "http-server start error = " + e);}}@Overridepublic Response serve(IHTTPSession session) {String uri = session.getUri();       String filePath = uri;//gameRootPath 游戏工作目录至关重要//有了游戏工作目录,http 请求 URL 可以更简洁、更方便if(gameRootPath != null && gameRootPath.lenght() !=0){filePath = gameRootPath + uri;}File file = new File(filePath);//web 服务请求的是资源,目录没有多大意义if (!file.exists() || !file.isFile()) {return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "404 Not Found");}//读取文件并返回try {FileInputStream fis = new FileInputStream(file);String mimeType = NanoHTTPD.getMimeTypeForFile(uri);return newFixedLengthResponse(Response.Status.OK, mimeType, fis, file.length());} catch (IOException e) {return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500 Internal Error");}}
}

相关文章:

Android Http-server 本地 web 服务

时间&#xff1a;2025年2月16日 地点&#xff1a;深圳.前海湾 需求 我们都知道 webview 可加载 URI&#xff0c;他有自己的协议 scheme&#xff1a; content:// 标识数据由 Content Provider 管理file:// 本地文件 http:// 网络资源 特别的&#xff0c;如果你想直接…...

用deepseek学大模型05逻辑回归

deepseek.com:逻辑回归的目标函数&#xff0c;损失函数&#xff0c;梯度下降 标量和矩阵形式的数学推导&#xff0c;pytorch真实能跑的代码案例以及模型,数据&#xff0c;预测结果的可视化展示&#xff0c; 模型应用场景和优缺点&#xff0c;及如何改进解决及改进方法数据推导。…...

python实践-实现实时语音转文字本地部署版(二)

一、技术栈 python 3.10.6 vosk 需下载对应模型&#xff08;vosk-model-cn-0.22&#xff09;模型下载慢的同学看最后的资源链接。 pyaudio keyboard 二、实现功能 本地化实现麦克风语音录入&#xff0c;实时生成文字&#xff0c;并保存至本地文档。 三、实现代码 fro…...

tortoiseSVN 如何克隆项目到本地

导入项目成功&#xff0c;如下图&#xff1a;...

解决“QString的split()函数分割中文“报错

在使用Qt平台的QString类里的split()函数&#xff0c;分割.txt文件里中文的字符串时&#xff0c;发现中文会乱码。     问题原因&#xff1a;中文使用UTF-16编码。     解决方法&#xff1a;将.txt文件保存为UTF-16编码&#xff0c;然后使用split()去分割对应的字符串即可。…...

云平台结合DeepSeek的AI模型优化实践:技术突破与应用革新

目录 前言 一、技术架构&#xff1a;算力与算法的协同基石 1. 蓝耘平台的核心优势 2. DeepSeek的模型创新 二、应用场景&#xff1a;垂直领域的智能化落地 1. 商业领域&#xff1a;智能推荐与客服 2. 工业领域&#xff1a;质检与流程优化 3. 智慧城市与医…...

蓝桥杯(B组)-每日一题(1093字符逆序)

c中函数&#xff1a; reverse(首位置&#xff0c;尾位置&#xff09; reverse(s.begin(),s.end()) 头文件&#xff1a;<algorithm> #include<iostream> #include<algorithm>//运用reverse函数的头文件 using namespace std; int main() {string s;//定义一…...

jsherp importItemExcel接口存在SQL注入

一、漏洞简介 很多人说管伊佳ERP&#xff08;原名&#xff1a;华夏ERP&#xff0c;英文名&#xff1a;jshERP&#xff09;是目前人气领先的国产ERP系统虽然目前只有进销存财务生产的功能&#xff0c;但后面将会推出ERP的全部功能&#xff0c;有兴趣请帮点一下 二、漏洞影响 …...

基于ffmpeg+openGL ES实现的视频编辑工具-字幕添加(六)

在视频编辑领域,字幕的添加是一项极为重要的功能,它能够极大地丰富视频内容,提升观众的观看体验。当我们深入探究如何实现这一功能时,FreeType 开源库成为了强大助力。本文将详细阐述借助 FreeType 库生成字幕数据的过程,以及如何实现字幕的缩放、移动、旋转、颜色修改、对…...

一文讲清 AIO BIO NIO的区别

引言 在 Java 编程中&#xff0c;BIO&#xff08;Blocking I/O&#xff09;、NIO&#xff08;Non-blocking I/O&#xff09;和 AIO&#xff08;Asynchronous I/O&#xff09;是三种不同的 I/O 模型&#xff0c;它们在处理输入输出操作时有着不同的机制和特点&#xff0c;但是市…...

Qt 中使用 ffmpeg 获取采集卡数据录制视频

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 之前做了一个功能&#xff0c;从采集卡获取数据然后录制成视频&#xff0c;结果发现录制的视频内存占用非常大&#xff0c;1分钟的…...

用HTML5+CSS+JavaScript实现新奇挂钟动画

用HTML5+CSS+JavaScript实现新奇挂钟动画 引言 在技术博客中,如何吸引粉丝并保持他们的关注?除了干货内容,独特的视觉效果也是关键。今天,我们将通过HTML5、CSS和JavaScript实现一个新奇挂钟动画,并将其嵌入到你的网站中。这个动画不仅能让你的网站脱颖而出,还能展示你的…...

一周学会Flask3 Python Web开发-redirect重定向

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 前面我们学过渲染到模板页面&#xff0c;这个其实是一种内部的转发&#xff0c;浏览器地址栏地址没有变化。如果我们想重定向…...

如何在 ConstraintLayout 中将 ViewPager 始终置于 ll_title 的下方

关于如何在 ConstraintLayout 中将 ViewPager 始终置于 ll_title标题栏 的下方。。 如何将 ViewPager 始终置于 ll_title 下方 在 ConstraintLayout 中&#xff0c;可以通过约束来实现 ViewPager 始终位于 ll_title 标题栏的下方。以下是修改后的布局代码&#xff1a; <?…...

文心一言大模型的“三级跳”:从收费到免费再到开源,一场AI生态的重构实验

2025年2月&#xff0c;百度文心大模型接连抛出两枚“重磅炸弹”&#xff1a;4月1日起全面免费&#xff0c;6月30日正式开源文心大模型4.5系列。这一系列动作不仅颠覆了李彦宏此前坚持的“闭源优势论”13&#xff0c;更标志着中国AI大模型竞争进入了一个全新的阶段——从技术壁垒…...

IPv6报头40字节具体怎么分配的?

目录 IPv6报头结构 字段详解 示例代码&#xff1a;IPv6报头的Python实现 输出示例 IPv6协议是为了解决IPv4地址耗尽问题而设计的下一代互联网协议。与IPv4相比&#xff0c;IPv6不仅提供了更大的地址空间&#xff0c;还简化了报头结构&#xff0c;提高了网络设备的处理效率。…...

使用 Spark NLP 实现中文实体抽取与关系提取

在自然语言处理(NLP)领域,实体抽取和关系提取是两个重要的任务。实体抽取用于从文本中识别出具有特定意义的实体(如人名、地名、组织名等),而关系提取则用于识别实体之间的关系。本文将通过一个基于 Apache Spark 和 Spark NLP 的示例,展示如何实现中文文本的实体抽取和…...

大数据治理之solr的体现

大数据治理之solr的体现 一&#xff0c;大数据治理下Solr的作用 在大数据治理的背景下&#xff0c;Solr作为一个高性能的搜索平台&#xff0c;发挥这重要的作用&#xff0c;下面是Solr在大数据治理中的几个关键作用和体现&#xff1a; 数据索引与检索&#xff1a; 高效检索&a…...

[笔记.AI]如何判断模型是否通过剪枝、量化、蒸馏生成?

以下摘自与DeepSeek-R1在线联网版的对话 一、基础判断维度 技术类型核心特征验证方法剪枝模型参数减少、结构稀疏化1. 检查模型参数量是否显著小于同类标准模型1 2. 分析权重矩阵稀疏性&#xff08;如非零参数占比<30%&#xff09;4量化权重/激活值精度降低、推理速度提升1…...

Uniapp 从入门到精通:基础篇 - 搭建开发环境

Uniapp 从入门到精通:基础篇 - 搭建开发环境 前言一、Uniapp 简介1.1 什么是 Uniapp1.2 Uniapp 的优势二、搭建开发环境前的准备2.1 安装 Node.js2.2 安装 HBuilderX三、创建第一个 Uniapp 项目3.1 打开 HBuilderX 并创建项目3.2 项目结构介绍3.3 运行项目四、配置项目4.1 配置…...

CSDN文章质量分查询系统【赠python爬虫、提分攻略】

CSDN文章质量分查询系统 https://www.csdn.net/qc 点击链接-----> CSDN文章质量分查询系统 <------点击链接 点击链接-----> https://www.csdn.net/qc <------点击链接 点击链接-----> CSDN文章质量分查询系统 <------点击链接 点击链…...

GPT-SoVITS更新V3 win整合包

GPT-SoVITS 是由社区开发者联合打造的开源语音生成框架&#xff0c;其创新性地融合了GPT语言模型与SoVITS&#xff08;Singing Voice Inference and Timbre Synthesis&#xff09;语音合成技术&#xff0c;实现了仅需5秒语音样本即可生成高保真目标音色的突破。该项目凭借其开箱…...

nginx ngx_http_module(8) 指令详解

nginx ngx_http_module(8) 指令详解 nginx 模块目录 nginx 全指令目录 一、目录 1.1 模块简介 ngx_http_ssi_module&#xff1a;服务器端包含&#xff08;SSI&#xff09;模块&#xff0c;允许在HTML页面中插入其他内容或动态生成的内容。通过特殊的SSI指令&#xff08;如 …...

CSS三大特性——继承、优先级与层叠

1. 层叠性 概念&#xff1a;如果发生了样式冲突&#xff0c;那就会根据一定的规则&#xff08;选择器优先级&#xff09;&#xff0c;进行样式的层叠&#xff08;覆 盖&#xff09;。 什么是样式冲突&#xff1f; ——— 元素的同一个样式名&#xff0c;被设置了不同的值&…...

Java 中的方法参数传递与值传递

文章目录 Java 中的方法参数传递与值传递代码示例代码运行结果分析原因1. Java 中的参数传递机制2. 代码执行过程 值传递的图示如何实现真正的交换&#xff1f;1. 使用数组2. 使用对象 总结 Java 中的方法参数传递与值传递 在 Java 编程中&#xff0c;理解方法参数传递的机制是…...

敏捷开发06:用户故事估算方法介绍

估算介绍 在以前开发 IT 软件时&#xff0c;使用较多的衡量软件开发工作量的单位是&#xff1a;小时、人天 或 人月。它是预估开发时间。比如&#xff1a;这个功能张三一个人开发需要 3 天时间完成。 这种 “人天” 估算只是 “理想人天” 的估算&#xff0c;有时与实际开发完…...

在原有基础上的Python正则表达式终极指南,新增高级用法、复杂案例和底层原理分析

以下是Python正则表达式终极指南,新增高级用法、复杂案例和底层原理分析: Python正则表达式终极指南 一、正则表达式引擎原理 1. 回溯机制解析 NFA(非确定性有限自动机)工作原理回溯的产生场景及性能影响灾难性回溯案例:# 危险模式示例 re.match(r(a+)+b, aaaaaaaaac) …...

进制转换及C语言中进制转换方法

进制转换是计算机科学和数学中的基础操作&#xff0c;主要用于不同数制之间的数值表示转换。以下是常见进制&#xff08;二进制、八进制、十进制、十六进制&#xff09;的转换方法及示例&#xff1a; 一、其他进制 → 十进制 方法&#xff1a;按权展开&#xff0c;逐位相加。 …...

【我要成为配环境高手】node卸载与nvm安装

node卸载与nvm安装 1. node卸载 参考了这篇文章&#xff1a; https://blog.csdn.net/weixin_43801036/article/details/141487791 2. nvm安装 参考了这两篇文章&#xff1a; https://www.cnblogs.com/rnny/p/17839190.html#tid-z7A3nR https://blog.csdn.net/weixin_45811…...

环境变量与本地变量

目录 本地变量的创建 环境变量VS本地变量 认识完了环境变量我们来认识一下本地变量。 本地变量的创建 我们如果直接env是看不到本地变量的&#xff0c;因为本地变量和环境变量都具有独立性&#xff0c;环境变量是系统提供的具有全局属性的变量&#xff0c;都存在bash进程的…...