将HTML+JS+CSS数独游戏包装为安卓App

HTML+JS+CSS制作一个数独游戏-CSDN博客 中开发了一个数独游戏,这个数独游戏提供了一次性回退到指定步骤的辅助功能,在解决复杂数独问题时十分有帮助,可作为玩数独游戏的辅助工具,因此,考虑将它改装成安卓App安装在手机上,可以更方便使用。

将纯HTML程序包装成安卓App在编码方面根本没有什么难度,真正的难度在于开发环境的配置和程序的编译运行。花了一个多星期,才总算编译成功了。有图有真相,在Android Studio中连接手机成功运行的画面如下:

还是先给出代码。在Android Studio(我用的版本是2024.2.1 Patch 3,本来用2025.1.1 Patch 1玩了很多次,一直没有成功,后来用了别人说玩成功过的这个版本,实际上我估计按下面介绍的构建方法中的魔法5,2025.1.1也能成功,只是我没有去尝试了)中创建一个空Activity项目后,唯一需要修改的源代码文件是MainActivity.kt(下面提到的配置文件不算),其实下面的代码是copilot调用GPT-4.1生成的,我连读都没有读,因为我连Kotlin的语法都没了解过(所以说编码不是难点):

package com.yivifu.sudokuimport android.app.AlertDialog
import android.os.Bundle
import android.view.ViewGroup
import android.webkit.*
import android.widget.EditText
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.yivifu.sudoku.ui.theme.SudokuThemeclass MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {SudokuTheme {Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->AndroidView(factory = { context ->WebView(context).apply {settings.javaScriptEnabled = truewebViewClient = WebViewClient()// 关键:支持 alert/promptwebChromeClient = object : WebChromeClient() {override fun onJsAlert(view: WebView?,url: String?,message: String?,result: JsResult?): Boolean {AlertDialog.Builder(context).setMessage(message).setPositiveButton(android.R.string.ok) { _, _ ->result?.confirm()}.setCancelable(false).create().show()return true}override fun onJsPrompt(view: WebView?,url: String?,message: String?,defaultValue: String?,result: JsPromptResult?): Boolean {val input = EditText(context)input.setText(defaultValue)AlertDialog.Builder(context).setMessage(message).setView(input).setPositiveButton(android.R.string.ok) { _, _ ->result?.confirm(input.text.toString())}.setNegativeButton(android.R.string.cancel) { _, _ ->result?.cancel()}.create().show()return true}}loadUrl("file:///android_asset/sudoku.html")layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)}},modifier = Modifier.padding(innerPadding).fillMaxSize())}}}}
}

现在再说明配置编译过程中的难点。

1、在Android Studio构建工具Gradle的设置页面中,Gradle User Home这一项,要注意提供一个没有非ASCII字符的路径,默认会在C盘用户名路径下,如果用户名有中文字符,很容易构建失败;

2、最好为Gradle配置一个国内的源,下载的速度快一点,主要需要将gradle-wrapper.properties文件中的distributionUrl的值改为https\://mirrors.cloud.tencent.com/gradle/gradle-8.9-bin.zip,如果用其他版本的gradle,要确保下载源存在对应版本;

3、为插件和软件包依赖仓库设置国内下载源,主要修改settings.gradle.kts文件,我这里修改后的文件内容如下:

pluginManagement {repositories {maven { url=uri ("https://jitpack.io") }maven { url=uri ("https://maven.aliyun.com/repository/releases") }
//        maven { url 'https://maven.aliyun.com/repository/jcenter' }maven { url=uri ("https://maven.aliyun.com/repository/google") }maven { url=uri ("https://maven.aliyun.com/repository/central") }maven { url=uri ("https://maven.aliyun.com/repository/gradle-plugin") }maven { url=uri ("https://maven.aliyun.com/repository/public") }google {content {includeGroupByRegex("com\\.android.*")includeGroupByRegex("com\\.google.*")includeGroupByRegex("androidx.*")}}mavenCentral()gradlePluginPortal()}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {maven { url=uri ("https://jitpack.io") }maven { url=uri ("https://maven.aliyun.com/repository/releases") }
//        maven { url 'https://maven.aliyun.com/repository/jcenter' }maven { url=uri ("https://maven.aliyun.com/repository/google") }maven { url=uri ("https://maven.aliyun.com/repository/central") }maven { url=uri ("https://maven.aliyun.com/repository/gradle-plugin") }maven { url=uri ("https://maven.aliyun.com/repository/public") }google()mavenCentral()}
}rootProject.name = "Sudoku"
include(":app")

4、各插件及依赖包的版本如下(libs.versions.toml):

[versions]
agp = "8.7.3"
kotlin = "2.0.0"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0-alpha04"
composeBom = "2023.05.01"[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui", version = "1.4.0" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version = "1.4.0" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version = "1.4.0" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version = "1.4.0" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version = "1.4.0" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version = "1.4.0" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.0.1" }[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

其中特别要说明的是,Android Studio自动生成的activityCompose和composeBom版本号原来分别是1.8.2和2024.01.01,阿里云的镜像上没有对应的版本,我给改成了镜像上最新的版本号。为此,build.gradle.kts (:app)中的android结点下compileSdk那一句为了与1.8.0-alpha04兼容也改成了下面的配置:

compileSdkPreview = "UpsideDownCake"

但是,如果掌握了下面的魔法,也许第4步修改根本用不着做,只是我没有再尝试了。

5、最重要的魔法:必须找一台有VPN能够访问谷歌的下载源的电脑,但是VPN打开的时机要恰到好处。如果一直开着VPN,那么Gradle同步的时候,后台进程通信收到VPN的影响,同步会失败,如果一直不开VPN,有些国内镜像源上没有的依赖库和插件无法下载也会构建失败。所以,应该使用命令行编译程序,先关闭所有VPN和代理设置,在Android Studio开发环境中打开终端,输入命令:

 ./gradlew clean assembleRelease

然后时刻关注输出,等到输出开始下载依赖库和插件的信息时,立即打开VPN,此时就会顺利下载所需的依赖库和插件。我是通过--debug参数执行构建命令,看到在配置了国内镜像的情况下仍然输出了从谷歌源下载aapt2之类库并失败的信息,才去借用了外贸工作人士的有VPN的环境测试。但是一开始就开了VPN,因为gradle后台进程通信被VPN干扰(copilot说的),结果无法接收到后台进程的通信,导致测试仍然失败。折腾了一个星期,关VPN构建工具aapt2下载失败,开VPN则gradle进程通信失败,copilot、豆包、kimi全没有提出有效解决办法,后面我发现关掉VPN的时候gradle后台进程启动,会先从国内源下载一些资源,要过一会才开始从谷歌下载没下载下来的插件和依赖库,于是灵机一动,先关VPN启动构建,在从谷歌源下载依赖前打开VPN,居然构建成功!然后我将带VPN的测试环境里的gradle user home路径全部复制到我的电脑上,在我的电脑上没有VPN也能构建成功了。但是我如果想进一步深入学习安卓编程,添加新功能需要新依赖,说不定又会遇到依赖不能成功下载的问题,所以玩完这一次,我可能就不会再玩安卓编程了。

老实说,国家整体软件编程水平也需要一定的人口基数支撑,如果让学程序设计的人把很多精力浪费在编译环境配置和构建上,多次失败的打击可能会劝退很多人,所以要么政府出资及时将谷歌的仓库里的软件包复制过来做个镜像供国内学习编程的人使用,要么不要限制对谷歌仓库的访问,否则想在软件上超过欧美恐怕是空话。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92030.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92030.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

编程语言Java入门——核心技术篇(一)封装、继承和多态

同专栏基础知识篇写在这里,有兴趣的可以去看看: 编程语言Java入门——基础知识篇(一)-CSDN博客 编程语言Java入门——基础知识篇(二)-CSDN博客 编程语言Java入门——基础知识篇(三&#xff0…

【39】MFC入门到精通——C++ /MFC操作文件行(读取,删除,修改指定行)

文章目录1 通过关键词,读取某一行 (3种方法)2 删除 指定行3 修改 指定行1 通过关键词,读取某一行 (3种方法) 通过定位关键词,读取某一行信息,返回CString //通过定位关键词,读取某…

5 种可行的方法:如何将 Redmi 联系人备份到 Mac

将 Redmi 联系人备份到 Mac 是防止因手机损坏、丢失或更换设备而导致数据丢失的重要措施。虽然云服务提供了便利性,但拥有离线备份可以提供额外的安全性,而无需完全依赖互联网。如果您想知道如何将 Redmi 联系人备份到 Mac,本文将为您介绍 5 …

LeRobot 具身智能机械臂 SO-ARM100 从搭建到训练全流程

今天给大家分享一下 LeRobot 具身智能机械臂 SO-ARM100 的完整使用流程,包括设备组装、环境配置、远程控制、数据录制到模型训练的全过程。适合刚入门具身智能的小伙伴参考学习。 一、前期准备与资源获取 在开始之前,我们需要准备好相关的资源和工具&a…

LINUX720 SWAP扩容;新增逻辑卷;逻辑卷扩容;数据库迁移;gdisk

SWAP空间扩展 方法一 增加硬盘或分区扩展 swap -s mkswap /dev/sdd6 blkid /dev/sdd6 swapon /dev/sdd6 swapon -s vim /etc/fstab /dev/sdd6 swap swap defaults 0 0 开机自动扩容 swap -s [rootweb ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sd…

Python 进程间通信:TCP安全加密数据传输

最近在写安全方面的程序,有需求,就做了这些TCP加密数据传输类。 utils.safeUtils的内容详见: SafeObj:Python 高安全性加密数据容器类-CSDN博客SafeKey:Python 高安全性加密密码容器类-CSDN博客 如有任何问题或漏洞欢迎…

Windows批量修改文件属性方法

标题使用icacls命令(推荐批量操作)打开管理员权限的命令提示符(CMD)执行以下命令:cmd icacls "文件夹路径" /grant 用户名:(OI)(CI)F /T /C 参数说明:(OI):对象继承 - 适用于文件夹(C…

Entity Component System架构

ECS架构 1 简介 在当今快速发展的软件开发领域,游戏开发、实时模拟等场景对系统的性能、灵活性和可扩展性提出了极高的要求。传统的面向对象架构在面对复杂且动态变化的实体时,往往会出现代码耦合度高、扩展性差等问题。​ ECS(Entity - Com…

.vscode 扩展配置

一、vue快捷键配置 在项目.vscode下新建vue3.0.code-snippets 每当输入vue3.0后自动生成代码片段 {"Vue3.0快速生成模板": {"scope": "vue","prefix": "Vue3.0","body": ["<template>"," &…

一个基于阿里云的C端Java服务的整体项目架构

1.背景介绍 总结一下工作使用到的基于通常的公有云的项目整体架构&#xff0c;如何基于公有云建设安全可靠的服务&#xff0c;以阿里云为例的整体架构&#xff1b;1. 全局流量治理层&#xff08;用户请求入口&#xff09;1.1 域名与 DNS 解析域名注册与备案&#xff1a;通过阿里…

《剥开洋葱看中间件:Node.js请求处理效率与错误控制的深层逻辑》

在Node.js的运行时环境中&#xff0c;中间件如同一系列精密咬合的齿轮&#xff0c;驱动着请求从进入到响应的完整旅程&#xff0c;而洋葱模型则是这组齿轮的传动系统。它以一种看似矛盾的方式融合了顺序与逆序、分离与协作——让每个处理环节既能独立工作&#xff0c;又能感知全…

GaussDB union 的用法

1 union 的作用union 运算符用于组合两个或更多 select 语句的结果集。2 union 使用前提union 中的每个 select 语句必须具有相同的列数这些列也必须具有相似的数据类型每个 select 语句中的列也必须以相同的顺序排列3 union 语法select column_name(s) from table1 union sele…

构建足球实时比分APP:REST API与WebSocket接入方案详解

在开发足球实时比分应用时&#xff0c;数据接入方式的选择直接影响用户体验和系统性能。本文将客观分析REST API和WebSocket两种主流接入方案的技术特点、适用场景和实现策略&#xff0c;帮助开发者做出合理选择。一、REST API&#xff1a;灵活的数据获取方案核心优势标准化接口…

Linux文件系统三要素:块划分、分区管理与inode结构解析

理解文件系统 我们知道文件可以分为磁盘文件和内存文件&#xff0c;内存文件前面我们已经谈过了&#xff0c;下面我们来谈谈磁盘文件。 目录 一、引入"块"概念 解析 stat demo.c 命令输出 基本信息 设备信息 索引节点信息 权限信息 时间戳 二、引入"分区…

基于paddleDetect的半监督目标检测实战

基于paddleDetect的半监督目标检测实战前言相关介绍前提条件实验环境安装环境项目地址使用paddleDetect的半监督方法训练自己的数据集准备数据分割数据集配置参数文件PaddleDetection-2.7.0/configs/semi_det/denseteacher/denseteacher_ppyoloe_plus_crn_l_coco_semi010.ymlPa…

计算机网络:(十)虚拟专用网 VPN 和网络地址转换 NAT

计算机网络&#xff1a;&#xff08;十&#xff09;虚拟专用网 VPN 和网络地址转换 NAT前言一、虚拟专用网 VPN1. 基础概念与作用2. 工作原理3. 常见类型4. 协议对比二、NAT&#xff1a;网络地址转换1. 基础概念与作用2. 工作原理与类型3. 优缺点与问题4. 进阶类型三、VPN 与 N…

数位 dp

数位dp 特点 问题大多是指“在 [l,r][l,r][l,r] 的区间内&#xff0c;满足……的数字的个数、种类&#xff0c;等等。” 但是显然&#xff0c;出题人想要卡你&#xff0c;rrr 肯定是非常大的&#xff0c;暴力枚举一定超时。 于是就有了数位 dp。 基本思路 数位 dp 说白了…

Selector的用法

Selector的用法 Selector是基于lxml构建的支持XPath选择器、CSS选择器&#xff0c;以及正则表达式&#xff0c;功能全面&#xff0c;解析速度和准确度非常高 from scrapy import Selectorbody <html><head><title>HelloWorld</title></head>&…

Netty封装Websocket并实现动态路由

引言 关于Netty和Websocket的介绍我就不多讲了,网上一搜一大片。现如今AI的趋势发展很热门,长连接对话也是会经常接触到的,使用Websocket实现长连接,那么很多人为了快速开发快速集成就会使用spring-boot-starter-websocket依赖快速实现,但是注意该实现是基于tomcat的,有…

行为型设计模式:解释器模式

解释器模式 解释器模式介绍 解释器模式使用频率不算高&#xff0c;通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到&#xff0c;比如编译器、规则引擎、正则表达式、SQL 解析等。不过&#xff0c;了解它的实现原理同样很重要&#xff0c;能…