玩Android开发已经两年有余了,一直保持这一些错误思想,终于在前几天给打破了。
作为一个开发老者,你一定看过这样的报错:

1
2
android.view.ViewImple$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewImple.checkThread(ViewImpl.java:xxx)

毫无疑问,八成是你在别的线程尝试对UI进行更新。我们常说那是UI线程之外的线程。
但是,为什么报错不是UI thread,而是original呢?我觉得,大多数人都没有想过.
前几天看到一篇文章,把我的错误观念打爆了,所以今天记录一下.
先来看看checkThread()方法:

1
2
3
4
5
6
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}

它很简单,就真的只是一个if语句而已.但是,mThread却有玄机…
mThread赋值的时候是在ViewRootImpl创建的时候,之后不再修改,并且mThread已用final标识.

1
2
3
4
5
6
7
8
9
10
11
12
final Thread mThread;

//...

public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
//...
}

而我们界面的线程是系统帮我们创建好的,所以通常情况下,我们认为这就是UI线程没错.
但是我们可以在可以在系统创建的界面线程之外继续创建能渲染的线程.
所以,我们不能认为”界面更新只能在UI线程中进行”,其他的线程也可以,但是必须自己组织合理.
但是,可以肯定的是,只有这个线程本身才能更新自己的渲染状态,其他线程必须通过Looper和Handler来通知这个线程进行更新.
并不建议使其他的线程成为可以渲染界面的线程,因为这难以管理.而且,一旦调用Looper#loop(),那么这一句代码后面的代码就被阻塞了,你的操作也必须通过post()的方式完成.
尽管Looper可以退出,这会执行Looper#loop()后面的代码.
不过,确实不应该凭借经验去解答问题,要有证据.下次不敢了233333.