AndroidHttp-server本地web服务
- 手机
- 2025-08-25 02:42:02

时间: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; @Override protected 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; }); // 初始化 webview mWebView = findViewById(R.id.game_webview); initWebview(); testLocalHttpServer(); } private void testLocalHttpServer(Context context) { final String assetsGameFilename = "H5Game"; copyAssetsGameFileToFiles(context, assetsGameFilename, new FindIndexCallback() { @Override public void onResult(File indexFile) { if (indexFile == null || !indexFile.exists()) { return; } // 大概测试了下 NanoHTTPD 似乎需要在主线程启动 H.post(new Runnable() { @Override public void run() { // 启动 http-server if (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() { @Override public 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);// 游戏基本都有 js webSettings.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 需要以下配置:
必须有网络权限 <uses-permission android:name="android.permission.INTERNET" />application 配置 android:networkSecurityConfig="@xml/network_security_configandroid: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); } } @Override public 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"); } } }AndroidHttp-server本地web服务由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“AndroidHttp-server本地web服务”