如何在子线程中更新Android TextView的文本内容?
子线程中更新TextView文本的正确方式
问题描述
我希望每次余额变化时更新余额TextView的文本。在通过点击按钮启动的子线程调用的方法中更新余额值时,调用balanceView.setText(balanceString)导致应用崩溃,请问正确的处理方式是什么?
原代码
private boolean working = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_work); CreateWorkButtonListener(); } private void CreateWorkButtonListener() { Button workButton = findViewById(R.id.workActivityWorkButton); workButton.setOnClickListener( v -> StartStopWorking(workButton) ); } private void StartStopWorking(Button workButton) { if (working) { working = false; workButton.setText(R.string.start_working); } else { working = true; workButton.setText(R.string.stop_working); new Thread(this::StartMakingMoney).start(); } } private void StartMakingMoney() { while(working) { try { Thread.sleep(1000); UserInfo.balance = UserInfo.balance + 1 * UserInfo.workMultiplier; UserInfo.totalWorkTime++; UserInfo.CalculateWorkMultiplier(); UserInfo.SaveUserInfoToFile(); String balanceString = "$" + String.format(Locale.getDefault(), "%,.2f", UserInfo.balance); TextView balanceView = findViewById(R.id.workActivityBalanceField); balanceView.setText(balanceString); // 此行导致崩溃 } catch (InterruptedException e) { System.err.println("Error with sleeping for work"); } catch (IllegalArgumentException e) { // 未捕获到异常 System.err.println("Error with setting the balanceView text"); } } }
崩溃栈跟踪
FATAL EXCEPTION: Thread-2 Process: com.ggz.cowsino, PID: 20926 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. Expected: main Calling: Thread-2 at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:9994) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:2082) at android.view.View.requestLayout(View.java:27043) at android.view.View.requestLayout(View.java:27043) at android.view.View.requestLayout(View.java:27043) at android.view.View.requestLayout(View.java:27043) at android.view.View.requestLayout(View.java:27043) at android.view.View.requestLayout(View.java:27043) at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3605) at android.view.View.requestLayout(View.java:27043) at android.widget.TextView.checkForRelayout(TextView.java:11296) at android.widget.TextView.setText(TextView.java:7179) at android.widget.TextView.setText(TextView.java:6994) at android.widget.TextView.setText(TextView.java:6946) at com.ggz.cowsino.activities.WorkActivity.StartMakingMoney(WorkActivity.java:76) at com.ggz.cowsino.activities.WorkActivity.$r8$lambda$yk-GWYweqGH21rtYG7QGFga2zhA(Unknown Source:0) at com.ggz.cowsino.activities.WorkActivity$$ExternalSyntheticLambda0.run(Unknown Source:2)
原因分析
从栈跟踪的CalledFromWrongThreadException可以看出,Android规定只有创建View层级的主线程(UI线程)才能操作View,子线程直接调用setText会触发这个异常,必须切换到主线程才能更新UI。
解决方案
方法一:使用runOnUiThread
直接在子线程中通过Activity的runOnUiThread方法切换到主线程执行UI更新操作,修改StartMakingMoney方法的相关代码:
private void StartMakingMoney() { while(working) { try { Thread.sleep(1000); UserInfo.balance = UserInfo.balance + 1 * UserInfo.workMultiplier; UserInfo.totalWorkTime++; UserInfo.CalculateWorkMultiplier(); UserInfo.SaveUserInfoToFile(); String balanceString = "$" + String.format(Locale.getDefault(), "%,.2f", UserInfo.balance); // 切换到主线程更新UI runOnUiThread(() -> { TextView balanceView = findViewById(R.id.workActivityBalanceField); balanceView.setText(balanceString); }); } catch (InterruptedException e) { System.err.println("Error with sleeping for work"); } } }
方法二:使用Handler
在Activity中创建一个绑定主线程的Handler,子线程通过发送消息通知主线程更新UI:
private Handler mainHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_work); // 初始化主线程Handler mainHandler = new Handler(Looper.getMainLooper()); CreateWorkButtonListener(); } private void StartMakingMoney() { while(working) { try { Thread.sleep(1000); UserInfo.balance = UserInfo.balance + 1 * UserInfo.workMultiplier; UserInfo.totalWorkTime++; UserInfo.CalculateWorkMultiplier(); UserInfo.SaveUserInfoToFile(); String balanceString = "$" + String.format(Locale.getDefault(), "%,.2f", UserInfo.balance); // 通过Handler发送消息到主线程 mainHandler.post(() -> { TextView balanceView = findViewById(R.id.workActivityBalanceField); balanceView.setText(balanceString); }); } catch (InterruptedException e) { System.err.println("Error with sleeping for work"); } } }
方法三:使用LiveData(推荐,符合现代Android架构)
如果使用Jetpack组件,用LiveData来托管余额数据,它会自动在主线程通知UI更新:
// 在Activity中定义LiveData private MutableLiveData<String> balanceLiveData = new MutableLiveData<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_work); CreateWorkButtonListener(); TextView balanceView = findViewById(R.id.workActivityBalanceField); // 观察LiveData变化,自动在主线程更新UI balanceLiveData.observe(this, balanceString -> { balanceView.setText(balanceString); }); } private void StartMakingMoney() { while(working) { try { Thread.sleep(1000); UserInfo.balance = UserInfo.balance + 1 * UserInfo.workMultiplier; UserInfo.totalWorkTime++; UserInfo.CalculateWorkMultiplier(); UserInfo.SaveUserInfoToFile(); String balanceString = "$" + String.format(Locale.getDefault(), "%,.2f", UserInfo.balance); // 更新LiveData的值,自动触发UI更新 balanceLiveData.postValue(balanceString); } catch (InterruptedException e) { System.err.println("Error with sleeping for work"); } } }
内容的提问来源于stack exchange,提问作者griffin zavadil
相关产品推荐
相关产品推荐

