【问题标题】:Thread issues - Only the original thread that created a view hierarchy can touch its views线程问题 - 只有创建视图层次结构的原始线程才能触及其视图
【发布时间】:2016-10-13 18:50:29
【问题描述】:

我花了一段时间试图找出为什么我的模拟器一直卡住,原来是因为我引入了一个 while 循环。点击按钮后,输出一个随机数(LoadG1),4秒后消失。然后用户输入一个数字,如果它等于 loadg1,他们得到一个点,程序通过让另一个 loadg1 可用 4 秒然后消失等来循环......

除非它显然是它自己的线程,否则我不能有循环。我为它做了一个线程然后尝试了。按钮按下后整个应用程序冻结并没有发生,但是当 loadg1 启动 4 秒时,应用程序关闭。就好像循环的第一行有效,然后在 4 秒结束后失败。我收到以下错误:

android.view.ViewRootImpl$CalledFromWrongThreadException: 只有 创建视图层次结构的原始线程可以触及其视图。

查询后我发现我需要将它放在一个runOnUiThread中,所以我这样做了,只是遇到了同样的问题。这是我的代码:

import android.os.CountDownTimer;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.Random;


public class game1 extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_game1);


final Button loseStarter1;

    loseStarter1 = (Button) findViewById(R.id.Starter1);
    loseStarter1.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            loseStarter1.setVisibility(View.GONE);

            final int[] score = {0};
            Random generateG1 = new Random();
            final int loadG1 = generateG1.nextInt(1000000)+10000;
            final TextView number = (TextView) findViewById(R.id.number);
            number.setText(" "+loadG1);

            new CountDownTimer(18000, 1000) {
                @Override
                public void onTick (long millisUntilFinished) {
                }
                public void onFinish() {
                    TextView result = (TextView) findViewById(R.id.outcome);
                    result.setText("Score: "+ score[0]);
                    TextView prompt = (TextView) findViewById(R.id.prompt);
                    prompt.setVisibility(View.GONE);
                }
            }.start();

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Runnable myRunnable = new Runnable() {
                        @Override
                        public void run() {
                            while (true) {
                                SystemClock.sleep(4000);
                                number.setVisibility(View.GONE);
                                final TextView prompt = (TextView) findViewById(R.id.prompt);
                                prompt.setText(" Enter the number");
                                final EditText input = (EditText) findViewById(R.id.enterAnswer);
                                input.setVisibility(View.VISIBLE);
                                input.setOnKeyListener(new View.OnKeyListener() {
                                    @Override
                                    public boolean onKey(View v, int keyCode, KeyEvent event) {
                                        if (event.getAction() == KeyEvent.ACTION_DOWN) {
                                            switch (keyCode) {
                                                case KeyEvent.KEYCODE_ENTER:
                                                    Editable answer = input.getText();
                                                    int finalAnswer = Integer.parseInt(String.valueOf(answer));
                                                    int finalLoadG1 = Integer.parseInt(String.valueOf(loadG1));
                                                    input.setVisibility(View.GONE);
                                                    prompt.setVisibility(View.GONE);
                                                    if (finalAnswer == finalLoadG1) {
                                                        score[0]++;
                                                    }

                                                    return true;
                                                default:
                                            }
                                        }
                                        return false;
                                    }
                                });
                            }
                        }
                    };

                    Thread myThread = new Thread(myRunnable);
                    myThread.start();

                }
            });

            }
        });
    }

}

我觉得我已经很接近解决这个问题了,非常感谢提供的所有帮助。

编辑:我尝试了一个没有循环的替代解决方案,代码在这里:

runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Timer timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    SystemClock.sleep(4000);
                                    number.setVisibility(View.GONE);
                                    final TextView prompt = (TextView) findViewById(R.id.prompt);
                                    prompt.setText(" Enter the number");
                                    final EditText input = (EditText) findViewById(R.id.enterAnswer);
                                    input.setVisibility(View.VISIBLE);
                                    input.setOnKeyListener(new View.OnKeyListener() {
                                        @Override
                                        public boolean onKey(View v, int keyCode, KeyEvent event) {
                                            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                                                switch (keyCode) {
                                                    case KeyEvent.KEYCODE_ENTER:
                                                        Editable answer = input.getText();
                                                        int finalAnswer = Integer.parseInt(String.valueOf(answer));
                                                        int finalLoadG1 = Integer.parseInt(String.valueOf(loadG1));
                                                        input.setVisibility(View.GONE);
                                                        prompt.setVisibility(View.GONE);
                                                        if (finalAnswer == finalLoadG1) {
                                                            score[0]++;
                                                        }

                                                        return true;
                                                    default:
                                                }
                                            }
                                            return false;
                                        }
                                    });

                                }
                            });

                        }
                    }, 0, 4000);
                }
            });

现在随机数生成并显示 4 秒,但当用户输入可用时,应用程序冻结。显然跳过了 238 帧(我相信每四秒钟就会显示一次),并且“主线程”正在做太多的工作。注意:即使没有 RUNONUITHREAD 封装我的代码,也会出现相同的结果。两种方法我都试过了,没有发现差异。

真诚地感谢任何可以帮助我解决此问题的人。

【问题讨论】:

标签: java android


【解决方案1】:

不要在 UI 操作中使用 while(true),这就是你卡住的原因,更不要使用 while(true) - 这是不好的做法。

如果您需要运行一些重复操作,请为此使用计时器,您可以启动/停止计时器,您可以启动另一个计时器,甚至可以同时启动几个)

不要在线程中进行 UI 操作,只在 UI 线程中进行。

对于硬操作,请使用异步任务 - 当所有硬操作完成后,更新 UI。 (在异步任务中,您可以通过发布进度来更新 UI),在 stackoverflow 上大量用于异步任务的示例。

OnKeyListener - 在非 UI 线程上运行,但请阅读以上内容并避免使用 while(true)

更新回答:

好的,如果您想像这样运行并且不想出现滞后,请执行以下操作:

创建计时器 - 无需在 runOnUIThread 中启动它。所有带有计算的代码都会移动您将在计时器中启动的异步任务,在运行之前在异步任务中 - 停止计时器以避免再次运行,在异步任务中进行所有计算,在结束异步任务时 - 更新 ui 和(或者您可以在发布时更新进度),在async 任务结束时,您可以再次启动计时器。如果您在下次运行之前需要延迟,请使用 HandlerpostDelayed

【讨论】:

  • 你能告诉我我的代码需要改变什么吗?我创建了一个 int 变量 == 1 并将 while 循环设置为该变量小于 2 并尝试了它。似乎与 while true 相同,我得到了相同的输出。
  • 只要记住一次就可以了 - 如果您在单独的线程中触摸 UI 中的任何内容,那么您会被卡住,直到线程结束它的工作。如果您希望更新您的 UI - 使用计时器,这样您的线程中的代码将完成它的工作,并且您的 UI 将被更新。
  • 如前所述,即使计时器不在 UI 线程中,我仍然会收到延迟问题。请阅读我的最新编辑,谢谢。
  • 这似乎行不通,因为我收到一条错误消息,被告知 setOnKeyListener 必须在主 UI 线程中。如果您可以插入我的代码的更正版本,我相信它会帮助解决我的问题。
  • 在带有计时器的上一个版本中 - 删除此行 - SystemClock.sleep(4000); 将其替换为 timer.cancel(); 以及稍后当用户输入数字时 - 再次启动计时器。
猜你喜欢
  • 2012-12-26
  • 1970-01-01
  • 2018-04-12
  • 2018-05-30
  • 2016-01-17
  • 1970-01-01
  • 2015-05-22
相关资源
最近更新 更多