You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin中TimerTask的run函数无法正确访问变量且数据未解析的问题

Android Kotlin中TimerTask的run函数无法正确访问变量且数据未解析的问题

看起来你遇到的问题主要是Volley异步请求的执行时机导致的,这在Android开发中特别常见,很容易踩坑!咱们一步步拆解问题,然后给你解决办法:

问题根源拆解

  1. Volley请求是异步的,你却在同步时机操作变量

    • 你在onCreate里调用fetchdata()后立刻this.prev_html_data = html_data,这时候Volley的网络请求还在后台跑,html_data根本还没被赋值,所以prev_html_data一开始就是空字符串。
    • 同样,timerRun()里调用fetchdata()后马上就比较html_data和prev_html_data,这时候异步请求也还没完成,拿到的还是旧的html_data值,自然永远相等,prev_html_data也更新不对。
  2. html_parse()的执行时机不对,且你在异步回调外读取未更新的值

    • 你在display_html()里调用了html_parse(),而display_html()是在Volley请求成功的回调里执行的,所以html_data的解析是对的,但你在timerRun()里打印html_data的时候,回调还没触发,所以看到的是没被解析的旧值。

针对性解决方案

核心思路:所有依赖Volley请求结果的操作,都必须放到请求的成功回调里执行,不能在回调外面同步做。具体要改这几个地方:

1. 把prev_html_data的初始化和更新逻辑移到回调里

  • 移除onCreate里的this.prev_html_data = html_data,改成在第一次请求成功后初始化prev_html_data。
  • 在timerRun()里,不要直接比较变量,而是把比较、发通知、更新prev_html_data的逻辑放到Volley的成功回调中。

2. 加并发控制,避免重复请求

  • 新增一个标记位,防止上一次请求还没完成,Timer又触发了新的请求,导致数据混乱。

3. 确保通知操作在主线程执行

  • Timer的run()方法是在子线程运行的,Android通知必须在主线程创建,所以要包裹runOnUiThread。

修改后的完整代码

class MainActivity : AppCompatActivity() {
    var unique_id = 1000
    var html_data = ""
    var prev_html_data = ""
    lateinit var notificationManager: NotificationManager
    lateinit var notificationChannel: NotificationChannel
    lateinit var builder: Notification.Builder
    private val channelId = "hallomotto"
    private val description = "***** alert"
    // 新增标记:防止并发请求导致数据混乱
    private var isFetchingData = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        
        // 初始化通知相关逻辑
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notificationChannel = NotificationChannel(channelId, description, NotificationManager.IMPORTANCE_HIGH)
            notificationChannel.enableLights(true)
            notificationChannel.lightColor = Color.GREEN
            notificationChannel.enableVibration(true)
            notificationManager.createNotificationChannel(notificationChannel)

            builder = Notification.Builder(this, channelId)
                .setContentTitle("Floria alert!!")
                .setContentText("A new message has been posted (Floria is here!!!)")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setLargeIcon(BitmapFactory.decodeResource(this.resources, R.drawable.ic_launcher_background))
        } else {
            builder = Notification.Builder(this)
                .setContentTitle("Floria alert!!")
                .setContentText("A new message has been posted (Floria is here!!!)")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setLargeIcon(BitmapFactory.decodeResource(this.resources, R.drawable.ic_launcher_background))
        }

        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        // 第一次请求数据
        fetchdata()

        // 初始化Timer
        val timer = Timer(true)
        timer.schedule(object : TimerTask() {
            override fun run() {
                timerRun()
            }
        }, 60000, 60000)
    }

    private fun fetchdata() {
        // 如果正在请求,直接返回避免并发
        if (isFetchingData) return
        isFetchingData = true

        val volleyQueue = Volley.newRequestQueue(this)
        val url = "*****"

        val jsonRequest = JSONObject()
        jsonRequest.put("key", "*****")

        val jsonObjectRequest = JsonObjectRequest(
            Request.Method.POST,
            url,
            jsonRequest,
            { response ->
                isFetchingData = false
                val html_message = response.get("html_data")
                html_data = html_message.toString()
                // 所有依赖请求结果的操作都在这里执行
                display_html()
                checkAndSendNotification()
            },
            { error ->
                isFetchingData = false
                Toast.makeText(this, "Error has occurred when making the request", Toast.LENGTH_LONG).show()
                Log.e("MainActivity", "load_error: ${error.localizedMessage}")
            }
        )
        volleyQueue.add(jsonObjectRequest)
        Log.v("JSON request", jsonRequest.toString())
    }

    private fun html_parse() {
        html_data = """
            <html>
                <head>
                </head>
                <body>
        """.trimIndent() + html_data
        html_data += """
                </body>
            </html>
        """.trimIndent()
    }

    private fun display_html() {
        html_parse()
        Log.v("Output", html_data)
        val encoded_data = Base64.encodeToString(html_data.toByteArray(), Base64.NO_PADDING)
        val webview = findViewById<WebView>(R.id.webview)
        webview.loadData(encoded_data, "text/html", "base64")
    }

    // 新增方法:专门处理通知检查和历史数据更新
    private fun checkAndSendNotification() {
        if (html_data != prev_html_data) {
            // 通知操作必须在主线程执行
            runOnUiThread {
                notificationManager.notify(unique_id, builder.build())
                unique_id += 1
            }
            Log.e("Match 1", html_data)
            Log.e("Match 2", prev_html_data)
        }
        // 更新历史数据,确保下一次请求能正确对比
        prev_html_data = html_data
    }

    private fun timerRun(){
        // 现在只负责触发请求,所有逻辑交给异步回调处理
        fetchdata()
    }
}

关键改动说明

  1. 新增isFetchingData标记:防止Timer重复触发时,上一次请求还没完成就发起新请求,导致数据混乱。
  2. 把所有依赖请求结果的逻辑移到Volley成功回调里:包括解析HTML、检查通知、更新历史数据,确保操作的都是最新的html_data。
  3. Notification操作包裹runOnUiThread:Timer的run方法在子线程,Android通知必须在主线程创建,避免崩溃。
  4. 简化timerRun()逻辑:现在它只负责触发请求,所有后续处理都交给异步回调,彻底避免同步时机的错误。
  5. 移除了onCreate里过早的prev_html_data赋值:第一次请求成功后,会自动把prev_html_data更新为正确的初始值。

备注:内容来源于stack exchange,提问作者RadoslavL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 09:43:08