Android Kotlin中TimerTask的run函数无法正确访问变量且数据未解析的问题
Android Kotlin中TimerTask的run函数无法正确访问变量且数据未解析的问题
看起来你遇到的问题主要是Volley异步请求的执行时机导致的,这在Android开发中特别常见,很容易踩坑!咱们一步步拆解问题,然后给你解决办法:
问题根源拆解
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也更新不对。
- 你在
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() } }
关键改动说明
- 新增
isFetchingData标记:防止Timer重复触发时,上一次请求还没完成就发起新请求,导致数据混乱。 - 把所有依赖请求结果的逻辑移到Volley成功回调里:包括解析HTML、检查通知、更新历史数据,确保操作的都是最新的
html_data。 - Notification操作包裹
runOnUiThread:Timer的run方法在子线程,Android通知必须在主线程创建,避免崩溃。 - 简化
timerRun()逻辑:现在它只负责触发请求,所有后续处理都交给异步回调,彻底避免同步时机的错误。 - 移除了
onCreate里过早的prev_html_data赋值:第一次请求成功后,会自动把prev_html_data更新为正确的初始值。
备注:内容来源于stack exchange,提问作者RadoslavL
相关产品推荐
相关产品推荐

