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.sudoku
import 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.SudokuTheme
class 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 = true
webViewClient = WebViewClient()
// 关键:支持 alert/prompt
webChromeClient = 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也能构建成功了。但是我如果想进一步深入学习安卓编程,添加新功能需要新依赖,说不定又会遇到依赖不能成功下载的问题,所以玩完这一次,我可能就不会再玩安卓编程了。
老实说,国家整体软件编程水平也需要一定的人口基数支撑,如果让学程序设计的人把很多精力浪费在编译环境配置和构建上,多次失败的打击可能会劝退很多人,所以要么政府出资及时将谷歌的仓库里的软件包复制过来做个镜像供国内学习编程的人使用,要么不要限制对谷歌仓库的访问,否则想在软件上超过欧美恐怕是空话。