如果从 activity 生命周期方法内部启动,线程能够更改 ui。为什么?

Thread is able to change ui if started from inside activity lifecycle methods. why?

我正在测试此代码以检查应用程序是否因从后台线程更改 ui 组件而崩溃。但它没有。

这里在下面添加了代码。我在 MainActivityonCreate() 方法中启动了一个新线程,它应该按照 android docs 说的

崩溃了

In the class, the Runnable.run() method contains the code that's executed. Usually, anything is allowable in a Runnable. Remember, though, that the Runnable won't be running on the UI thread, so it can't directly modify UI objects such as View objects.

所以我以为它会崩溃。它没有。见代码-

public class MainActivity extends AppCompatActivity {

  TextView txt;
  Thread thread;
  Runnable runnable = new Runnable() {
    @Override
    public void run() {
      txt.setText("bro");
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    txt = (TextView) findViewById(R.id.name_txt);
    thread = new Thread(runnable);
    thread.start();
  }
}

而如果我在从 onClicklistener() 启动线程时尝试更改 ui,如下所示,它确实会崩溃。这是预期的。

public class MainActivity extends AppCompatActivity {

  TextView txt;
  Thread thread;
  Runnable runnable = new Runnable() {
  @Override
  public void run() {
    txt.setText("bro");
  }
  };

  View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
     thread.start();
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    txt = (TextView) findViewById(R.id.name_txt);
    thread = new Thread(runnable);
    txt.setOnClickListener(listener);
  }
}

现在,第二个代码片段崩溃了,这是预料之中的,而第一个代码片段没有。

请解释为什么会这样,因为我每次都在不同的地方创建一个新的工作线程。官方文档参考将不胜感激。

UI不是thread-safe see processes-and-threads,所以你很幸运没有击中等待你的众多地雷中的一个。
如果你不喜欢靠运气那么:
你应该使用:

runOnUiThread(runnable);

而不是:

thread = new Thread(runnable);

AsyncTask 可以正确和轻松地使用 UI 线程。

正如@krish 在对我的问题的评论中指出的那样,我找到了这种行为背后的原因。原因是,线程只能在 TextView 对象中进行更改,直到它在 UI 屏幕上不可见,即不呈现。只有在视图渲染之后,除主线程外的任何线程都不能对任何 UI 组件进行更改。我尝试使用视图观察器来查看视图是否在更改之前呈现。这表明在视图渲染之前进行了更改。

这是我试过的代码。

public class MainActivity extends AppCompatActivity {

  TextView txt;
  Thread thread;
  Runnable runnable = new Runnable() {
    @Override
    public void run() {
      txt.setText("bro");
      Log.d("ThreadTest", "The Text was changed.");
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    txt = (TextView) findViewById(R.id.name_txt);
    thread = new Thread(runnable);

    txt.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        Log.d("ThreadTest", "The TextView was rendered");
      }
    });
  }

  @Override
  protected void onResume() {
    super.onResume();
    thread.start();
  }
}

使用上面的代码。您将在输出中看到:

The Text was changed.

The TextView was rendered

这意味着文本在视图呈现之前已更改。如果您尝试启动线程以更改 onGlobalLayout 方法。应用程序崩溃。