看到这个问题,相信很多小伙伴认为,android开启子线程更新UI,是不对的,为什么呢?
因为只有主线程(UI线程)才可以进行UI的修改,如果在子线程进行UI更新,会抛出异常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views
是的,上面的表述是对的,但是,为什么说子线程又可以更新UI呢?这种说法可靠吗?
现在,我们用事实(代码)说话,来一段,
场景1:在MainActivity中开启子线程,设置TextView的内容,布局文件中有个TextView,比较简单,这里忽略,如下:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.v("MainActivity", "onCreate执行...");
initView();
new Thread(new Runnable() {
@Override
public void run() {
Log.v("MainActivity", "准备UI更新...");
tv.setText("测试子线程更新UI");
Log.v("MainActivity", "完成UI更新...");
}
}).start();
}
private void initView() {
tv = (TextView) this.findViewById(R.id.tv);
}
@Override
protected void onResume() {
super.onResume();
Log.v("MainActivity", "onResume执行...");
}
}
可以在日志看到:
onCreate执行...
onResume执行...
准备UI更新...
完成UI更新...
I/ActivityManager:Displayed com.rockykou.debug/.MainActivity: +868ms
运行结果:正常,界面显示“测试子线程更新UI”
场景2:在子线程的更新UI代码前,加上Thread.sleep(),其他与上面场景一致,如下:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.v("MainActivity", "onCreate执行...");
initView();
new Thread(new Runnable() {
@Override
public void run() {
//添加部分
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.v("MainActivity", "准备UI更新...");
tv.setText("测试子线程更新UI");
Log.v("MainActivity", "完成UI更新...");
}
}).start();
}
private void initView() {
tv = (TextView) this.findViewById(R.id.tv);
}
@Override
protected void onResume() {
super.onResume();
Log.v("MainActivity", "onResume执行...");
}
}
可以在日志看到:
onCreate执行...
onResume执行...
I/ActivityManager: Displayed com.rockykou.debug/.MainActivity: +1s33ms
准备UI更新...
AndroidRuntime: FATAL EXCEPTION: Thread-161
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4607)
...
运行结果:异常,app崩溃
比较下,场景1情况,子线程更新UI正常,场景2情况,加上Thread.sleep(5000),子线程更新UI异常,而且注意场景2中有个ViewRootImpl.checkThread方法,
怎么解释上述问题呢?
首先,这是两个线程,
场景1,主线程执行onCreate,开启子线程,主线程继续执行onResume,接下来,子线程执行,这里明确一点,ViewRootImpl类l对象没有被创建,checkThread没有被执行,直到子线程执行完,UI更新正常完成
场景2,主线程执行onCreate, 开启子线程,主线程继续执行onResume,子线程处于睡眠5秒状态,当继续执行子线程代码时,ViewRootImpl对象已经被创建,checkThead方法被调用,检查出当前线程(也就是子线程)与原来的线程(主线程)不一致,抛出异常
实际上,子线程执行更新UI代码时,如果ViewRootImpl对象如果已经创建,UI更新异常,未被创建,UI更新正常,
最终,根本在cpu对时间片的分配上,
当主线程有足够的时间执行时,ViewRootImpl对象被创建,子线程更新UI异常
当主线程没有足够时间(此时子线程在占有时间片)执行时,ViewRootImpl对象未创建,子线程更新UI正常