x域名免费网站,网站开发培训学费,知识付费网站开发教程,3g手机网站时间#xff1a;2025年2月16日 地点#xff1a;深圳.前海湾 需求
我们都知道 webview 可加载 URI#xff0c;他有自己的协议 scheme#xff1a;
content:// 标识数据由 Content Provider 管理file:// 本地文件 http:// 网络资源
特别的#xff0c;如果你想直接… 时间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 需要以下配置
必须有网络权限 uses-permission android:nameandroid.permission.INTERNET /application 配置
android:networkSecurityConfigxml/network_security_configandroid:usesCleartextTraffictrue
network_security_config.xml
?xml version1.0 encodingUTF-8?network-security-configbase-config cleartextTrafficPermittedtruetrust-anchors certificates srcuser/ certificates srcsystem/ /trust-anchors /base-config
/network-security-confighttp-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);}}
}