跳到主要内容Android Studio 结合 Trae 使用 Kotlin 开发 WebView 应用 | 极客日志KotlinAI大前端
Android Studio 结合 Trae 使用 Kotlin 开发 WebView 应用
综述由AI生成记录了使用 Trae AI 辅助 Android Studio 进行 Kotlin 开发的流程。主要内容包括创建项目、配置虚拟机、利用 AI 生成按键捕获与 WebView 加载本地资源的代码。解决了 WebView 事件优先处理导致 APP 无法捕获按键的问题,以及异步初始化导致的 WebView 实例为空问题。最终实现了通过物理按键修改 WebView 内容及拦截请求加载本地资源的功能。
云间运维24 浏览 简介
使用 Trae 配合 Android Studio 开发一个内嵌 WebView 的安卓应用,在 WebView 中加载本地资源,在 APP 中捕获按键事件对 WebView 中的内容进行操作。
依赖
- Trae
- Android Studio (以下简称 AS)
- 占用内存较大(约 6GB)
- 下载项目依赖和安卓虚拟机(约 2GB)依赖网络
- 基础的编程知识
步骤
AS(Andriod Studio) 创建项目
- 在 AS 打开新窗口后,等待右下角进度条初始化完毕(下载依赖和编译)。
- 创建一个 Empty Activity。


AS 创建虚拟机
- 启动模板项目。


- 找到镜像,如果是灰色的,需要点击下载图标进入下载界面(大约 2G,非常依赖网络)。


- 在右上角创建虚拟机。

TRAE CN 修改项目
- 使用 Trae 打开该文件夹。
- 按下
Ctrl+U 开启右侧 AI 工具。
- 按下
Ctrl+P,在顶部打开的输入框中输入 Main,选择 MainActivity.kt。
新增按键捕获功能
- 输入提示 "我想要安卓应用可以捕获键盘的上下 2 个方向键并打印日志信息"。
- 鼠标点击模拟器中的应用的白色区域,后按下上下键,可看到日志输出。
- 切换到日志查看界面。
- 打开 AS,会自动刷新代码,点击右上角的绿色重启按钮或者启动按钮重新编译和启动。
新增 WebView
- 输入提示 "我想将 Greeting 中的 Text 控件替换为 Webview"。
- 回到 AS,点击右上角的绿色重启按钮或者启动按钮重新编译和启动。
- 回到 TRAE,输入提示信息
界面提示 "webpage not avalible"。
- 在点击 "全部接受" 前,我看了修复内容,它没添加网络权限还把地址改成 google 了,这个方案是不行的,所以点击 "全部拒绝"。
- 重新输入提示 "应该是没有配置网络权限,修复一下"。
- 它给 "AndroidManifest.xml" 的文件内容清空了,添加了一行权限代码,这个方案是不行的,所以点击 "全部拒绝"。
- 重新输入提示 "应该是没有配置网络权限,添加一下相关的权限,注意不要影响原有的配置信息"。
- 还是无法打开页面。
- 此时发现按键事件的捕获失效了。
- 回到 AS,编译报错了,把提示信息
MainActivity.kt:61:17 Modifier 'override' is not applicable to 'local function' 发给 TRAE。
- 新回复删除了
override 解决了报错,但是没有解决按键的问题。
- 重启项目,成功解决按键问题。
重新输入提示 "不行,按键事件还是没有被 APP 捕获到", 生成了新回复。
输入提示信息 "按键事件被 Webview 捕获了导致应用程序的无法捕获上下键了,修复一下" (这里我之前尝试过几次提问,最后发现是 Webview 优先处理了事件导致的)。
还是不行,换了百度网址也不行,打开自带的谷歌浏览器也访问不了网络,最后是在下拉的通知栏里重新开关 wifi 后解决 (上一步操作没有成功停止虚拟机)。
回到 TRAE 输入提示信息 "还是提示 "webpage not avalible", 是不是还缺少哪个网络权限", 给出新的修复。
回到 AS 重启项目,结果提示 xml 有问题,仔细一看,它代码加错位置了,手动调整一下,向下挪动一行。
这里有个报错信息,把鼠标移动到红色的文字上会给出提示,点击 import 修复一下。
WebView 加载本地资源
- 输入提示 "我想打印 webview 都请求了哪些链接"。
- 输入提示 "本地资源文件需要放在哪个目录,帮我生成一个示例"。
有 2 个导入报错,鼠标移动到红色文字上,给出了提示信息,点击 import 导入一下,重启项目。
还需要修改一下拦截的地址 (下面的日志打印又改回错误的了,手动修复一下)。
这里给出了 2 步,一个是手动创建本地文件夹和文件,一个是修改 MainActivity.kt(没有自动修改,可以点击应用按钮)。
输入提示信息 "我想在 shouldInterceptRequest 中拦截请求,然后加载本地的资源文件"。
新增了一个 shouldInterceptRequest 方法,这里的斜杠是多余的,导致没能正确打印日志,手动删除一下。
在按键回调中向 WebView 注入 JS 代码
- 输入提示信息 "我想在 onKeyDown 的回调中修改 WebView 中的显示内容"。
- 输入提示信息 "我想在 onKeyDown 的回调中修改 Greeting 中创建的 WebView 中的显示内容"。
- 这次他给
WebView 加了个全局变量 webViewInstance, 但是修改后的代码少了花括号,手动修复一下。
- 发现没效果,手动加了日志打印了一下日志发现
webViewInstance 是空的。
- 输入提示信息 "webViewInstance 是 null, 是不是哪里有问题,导致没赋值成功"。
- 输入提示信息 "Greeting 中返回 webView 的时候,webView 还没初始化吧?"。
- 输入提示信息 "AndroidView 中是不是异步执行的,导致返回的 webview 是 null"。
- 输入提示信息 "已打印日志确认 Greeting 中返回的 webView 是 null"。
- 输入提示信息 "通过日志发现 WebViewReturn 提示 webView 有值,WebViewRequests 处为 null, 且 WebViewRequests 先 WebViewReturn 输出,可以确定 AndroidView 异步执行导致的返回 null, 修复一下"。
- 在控制台直接粘贴 JS,发现 js 没有问题。
输入提示信息 "evaluateJavascript 没有效果", 给出新修复。
发现没效果,打开 Edge,访问 "edge://inspect/#devices", chrome 不行,会超时然后显示 404。
没注意看,重新输入提示 "不要使用 webViewInstance?.loadUrl, 使用注入 JS 的方式"。
输入提示信息 "不要跳转到 new-url, 而是执行 JS 代码"。
给了一个通过 findViewById 实现的方法,接受后发现不能用,退回一下。
最终关键代码
package com.example.dm2
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.dm2.ui.theme.DM2Theme
import android.util.Log
import android.view.KeyEvent
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import java.io.IOException
var webViewInstance: WebView? = null
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DM2Theme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android") { webView ->
webViewInstance = webView
}
}
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_DPAD_UP -> {
Log.d("KeyEvent", "上方向键被按下" + webViewInstance)
webViewInstance?.evaluateJavascript("document.body.innerText = new Date();", null)
return true
}
}
return super.onKeyDown(keyCode, event)
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier, onWebViewCreated: (WebView) -> Unit = {}) {
val context = LocalContext.current
AndroidView(
factory = { ctx ->
val webView = WebView(ctx).apply {
settings.javaScriptEnabled = true
Log.d("WebViewInit", "WebView 实例已创建:$this")
webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
val url = request.url.toString()
if (url.equals("https://www.example.com/")) {
try {
val inputStream = context.assets.open("example.html")
return WebResourceResponse("text/html", "UTF-8", inputStream)
} catch (e: IOException) {
e.printStackTrace()
}
}
Log.d("WebViewRequests", "请求链接:${request.url}")
return super.shouldInterceptRequest(view, request)
}
}
loadUrl("https://www.example.com")
isFocusable = false
isFocusableInTouchMode = false
fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return false
}
}
onWebViewCreated(webView)
return@AndroidView webView
},
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
DM2Theme {
Greeting("Android")
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DM2"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.DM2">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>示例页面</title>
</head>
<body>
<h1>这是一个本地资源文件示例</h1>
<p>该文件从 Android 的 assets 目录加载。</p>
</body>
</html>
吐槽
Java 稳重,JS 灵活,Kotlin 语法糖丰富但需适应。
相关免费在线工具
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online