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

如何在子线程中更新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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 16:20:20