【笔记】Trae+Andrioid Studio+Kotlin开发安卓WebView应用_android studio webview
文章目录
- 简介
- 依赖
- 步骤
-
- AS(Andriod Studio)创建项目
- AS创建虚拟机
- TRAE CN 修改项目
-
- 新增按键捕获功能
- 新增WebView
- WebView加载本地资源
-
- 在按键回调中向WebView注入JS代码
- 最终关键代码
- 吐槽
简介
使用Trae
配合Andriod Studio
开发一个内嵌WebView
的安卓应用, 在WebView中加载本地资源, 在APP中捕获按键事件对WebView中的内容进行操作;
依赖
- Trae CN (https://www.trae.com.cn/)
- Andriod Studio (https://developer.android.google.cn/studio?hl=zh-cn), 以下简称AS
- 吃内存, 占用了我大约6GB内存
- 下载项目依赖和安卓虚拟机(约2GB)依赖网络
- 基础的编程知识
步骤
AS(Andriod Studio)创建项目
- 打开AS, 创建一个Empty Activity
- 在AS打开新窗口后, 等待右下角进度条初始化完毕(下载依赖和编译)
AS创建虚拟机
- 在右上角创建虚拟机
- 找到镜像, 如果是灰色的, 需要点击下载图标进入下载界面(大约2G, 非常依赖网络)
- 启动模板项目
TRAE CN 修改项目
- 使用Trae打开该文件夹
- 按下
Ctrl+P
, 在顶部打开的输入框中输入Main
, 选择MainActivity.kt
- 按下
Ctrl+U
开启右侧AI工具
新增按键捕获功能
-
输入提示\"我想要安卓应用可以捕获键盘的上下2个方向键并打印日志信息\"
- 生成完毕后, 在提示框的上方选择\"全部接受\"
- 生成完毕后, 在提示框的上方选择\"全部接受\"
-
打开AS, 会自动刷新代码, 点击右上角的绿色重启按钮或者启动按钮重新编译和启动
-
切换到日志查看界面
-
鼠标点击模拟器中的应用的白色区域, 后按下上下键, 可看到日志输出
新增WebView
- 输入提示\"我想将Greeting中的Text控件替换为Webview\"
- 生成完毕后, 选择\"全部接受\"
- 这里有个报错信息, 把鼠标移动到红色的文字上会给出提示, 点击
import
修复一下
- 回到AS, 点击右上角的绿色重启按钮或者启动按钮重新编译和启动
- 出现了网络连接的错误
- 回到TRAE, 输入提示信息
界面提示\"webpage not avalible\"
- 在点击\"全部接受\"前, 我看了修复内容, 它没添加网络权限还把地址改成google了, 这个方案是不行的, 所以点击\"全部拒绝\"
- 重新输入提示\"应该是没有配置网络权限, 修复一下\"
- 它给\"AndroidManifest.xml\"的文件内容清空了, 添加了一行权限代码, 这个方案是不行的, 所以点击\"全部拒绝\"
- 重新输入提示\"应该是没有配置网络权限, 添加一下相关的权限, 注意不要影响原有的配置信息\"
- 这次正确的添加了权限, 选择接受
- 这次正确的添加了权限, 选择接受
- 回到AS重启项目, 结果提示xml有问题, 仔细一看, 它代码加错位置了, 手动调整一下, 向下挪动一行
- 还是无法打开页面
- 回到TRAE输入提示信息\"还是提示\"webpage not avalible\", 是不是还缺少哪个网络权限\", 给出新的修复
- 重启后还是不能打开页面, 重启一下虚拟机
- 还是不行, 换了百度网址也不行, 打开自带的谷歌浏览器也访问不了网络, 最后是在下拉的通知栏里重新开关wifi后解决(上一步操作没有成功停止虚拟机)
- 此时发现按键事件的捕获失效了
- 输入提示信息\"按键事件被Webview捕获了导致应用程序的无法捕获上下键了, 修复一下\" (这里我之前尝试过几次提问, 最后发现是Webview优先处理了事件导致的)
- 回到AS, 编译报错了, 把提示信息
MainActivity.kt:61:17 Modifier \'override\' is not applicable to \'local function\'
发给TRAE- 新回复删除了
override
解决了报错, 但是没有解决按键的问题
- 新回复删除了
- 重新输入提示\"不行, 按键事件还是没有被APP捕获到\", 生成了新回复
- 重启项目, 成功解决按键问题
WebView加载本地资源
- 输入提示\"我想打印webview都请求了哪些链接\"
- 新增了一个
shouldInterceptRequest
方法, 这里的斜杠是多余的, 导致没能正确打印日志, 手动删除一下
- 新增了一个
- 输入提示信息\"我想在shouldInterceptRequest中拦截请求, 然后加载本地的资源文件\"
- 输入提示\"本地资源文件需要放在哪个目录, 帮我生成一个示例\"
- 这里给出了2步, 一个是手动创建本地文件夹和文件, 一个是修改MainActivity.kt(没有自动修改, 可以点击应用按钮)
- 这里给出了2步, 一个是手动创建本地文件夹和文件, 一个是修改MainActivity.kt(没有自动修改, 可以点击应用按钮)
- 还需要修改一下拦截的地址(下面的日志打印又改回错误的了, 手动修复一下)
- 有2个导入报错, 鼠标移动到红色文字上, 给出了提示信息, 点击
import
导入一下, 重启项目
在按键回调中向WebView注入JS代码
- 输入提示信息\"我想在onKeyDown的回调中修改WebView中的显示内容\"
- 给了一个通过
findViewById
实现的方法, 接受后发现不能用, 退回一下
- 给了一个通过
- 输入提示信息\"我想在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, 修复一下\"
- 这次改成了回调的方式, 成功解决
- 输入提示信息\"不要跳转到new-url, 而是执行JS代码\"
- 手动修改一下
- 没注意看, 重新输入提示\"不要使用webViewInstance?.loadUrl, 使用注入JS的方式\"
- 发现没效果, 打开Edge, 访问\"edge://inspect/#devices\", chrome不行, 会超时然后显示404
- 在控制台直接粘贴JS, 发现js没有问题
- 输入提示信息\"evaluateJavascript 没有效果\", 给出新修复
- 重启, 按下上键, 成功解决
最终关键代码
- MainActivity.kt
package com.example.dm2import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.ui.Modifierimport androidx.compose.ui.tooling.preview.Previewimport com.example.dm2.ui.theme.DM2Themeimport android.util.Logimport android.view.KeyEventimport android.webkit.WebViewimport android.webkit.WebViewClientimport android.webkit.WebResourceRequestimport android.webkit.WebResourceResponseimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.viewinterop.AndroidViewimport java.io.IOExceptionvar webViewInstance: WebView? = nullclass 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) }}@Composablefun 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): android.webkit.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)@Composablefun GreetingPreview() { DM2Theme { Greeting(\"Android\") }}
- AndroidManifest.xml
<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>
- example.html
<!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简直是耍**, 那语法糖看的我抓耳挠腮