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

使用Agora VideoUIKit-Android时App崩溃:CalledFromWrongThreadException

解决Agora VideoUIKit-Android中CalledFromWrongThreadException崩溃问题

问题描述

构建Agora VideoUIKit-Android示例项目时App崩溃,Logcat输出以下错误:

90-30676/io.agora.agora_android_uikit E/agora.io: jni_generator_helper.h: (line 131): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views

错误原因

这个异常的核心原因是在非UI线程中尝试修改UI组件。Android系统强制要求:所有View的创建、更新、交互操作必须在主线程(UI线程)中执行,否则就会抛出该异常。

代码问题定位

你的代码中,设置joinButton文本的逻辑存在线程调用错误:

runOnUiThread {
    Runnable { joinButton.text = "Allow Camera and Microphone, then click here" }
}

这里你在runOnUiThread的lambda内部又嵌套了一个Runnable,但这个内部的Runnable并没有被执行,相当于设置文本的代码根本没有在UI线程运行,导致后续触发非UI线程操作UI的情况,引发崩溃。

修复方案

直接在runOnUiThread的lambda中执行UI操作,不需要额外嵌套Runnable。另外,由于joinButton是在主线程创建的,甚至可以直接设置文本,无需额外调用runOnUiThread(保留也没问题,但写法要正确)。

修正后的完整代码如下:

package io.agora.agora_android_uikit

import android.Manifest
import android.os.Bundle
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import io.agora.agorauikit_android.AgoraConnectionData
import io.agora.agorauikit_android.AgoraVideoViewer
import io.agora.rtc2.Constants

private const val PERMISSION_REQ_ID = 22
private val REQUESTED_PERMISSIONS = arrayOf<String>(
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.CAMERA,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
)

@ExperimentalUnsignedTypes
class MainActivity : AppCompatActivity() {
    var agView: AgoraVideoViewer? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        try {
            agView = AgoraVideoViewer(
                this, AgoraConnectionData("*******"),
            )
        } catch (e: Exception) {
            println("Could not initialise AgoraVideoViewer. Check your App ID is valid. ${e.message}")
            return
        }
        val set = FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT
        )

        this.addContentView(agView, set)

        if (AgoraVideoViewer.requestPermission(this)) {
            agView!!.join("test", role = Constants.CLIENT_ROLE_BROADCASTER)
        } else {
            val joinButton = Button(this)
            // 修正:直接在runOnUiThread中执行文本设置,无需嵌套Runnable
            runOnUiThread {
                joinButton.text = "Allow Camera and Microphone, then click here"
            }
            // 或者直接设置(因为joinButton在主线程创建):
            // joinButton.text = "Allow Camera and Microphone, then click here"
            
            joinButton.setOnClickListener {
                if (AgoraVideoViewer.requestPermission(this)) {
                    (joinButton.parent as ViewGroup).removeView(joinButton)
                    agView!!.join("test", role = Constants.CLIENT_ROLE_BROADCASTER)
                }
            }
            this.addContentView(
                joinButton,
                FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 300)
            )
        }
    }
}

额外注意事项

  • 后续如果有其他UI操作(比如修改按钮样式、添加/移除View),务必确保代码在UI线程执行。如果是在子线程中触发UI更新,必须通过runOnUiThread或者Handler切换到主线程。
  • 若后续调用agView!!.join后仍出现UI异常,可以手动将该方法调用包裹在runOnUiThread中,确保UI相关操作在主线程执行。

内容的提问来源于stack exchange,提问作者Kamyab Ghassemi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 19:40:28