使用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
相关产品推荐
相关产品推荐

