这是之前朋友被问的一个面试问题,一般情况下我们都知道Android是不允许子线程更新UI的(通过单线程避免多线程的同步更新问题),但是为什么在onCreate方法里面却可以呢,自己试了一下,在onCreate方法直接new一个子线程并执行类似TextView.setText("Joern")这样的指令,发现的确可以没有报错,其实这个我们分析一下setText的代码就可以发现问题所在。
发现setText里面调用了 checkForRelayout();,而在checkForRelayout方法里面调用了invalidate()这个方法再追踪下去,是invalidate方法调用了ViewGroup.invalidateChild,最终调用ViewRootImpl.checkThread()。ViewRootImpl是一个隐藏类,我们只能去看framework的源码,源码如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
mThread是UI线程,这里会检查当前线程是不是UI线程。那么为什么onCreate里面没有进行这个检查呢。这个问题原因出现在Activity的生命周期中,在onCreate方法中,UI处于创建过程,对用户来说界面还不可视,直到onStart方法后界面可视了,再到onResume方法后界面可以交互,。
从某种程度来讲,在onCreate方法中进行setText不能算是更新UI,只能说是配置UI,或者是设置UI的属性。这个时候并不会调用invalidate,也就是不会调用到ViewRootImpl.checkThread()。而在onResume方法后,ViewRootImpl才被创建。这个时候去交互界面以及算是更新UI了,这个时候ViewRootImpl.checkThread()就会报错了。
网上还有一个试的方法,就上把上面onCreate的更新线程延迟200ms,这样onResume执行完成了,也的确会报错了
Thread thread = new Thread("Thread1") {
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
tv.append( System.currentTimeMillis() + "\n");
tv.append("thread name:" + Thread.currentThread().getName());
iv.setImageResource(R.drawable.ic_launcher);
};
};
thread.start();