之前我是这么认为的因为View的方法都是不安全的,没有Synchronous修饰。所以UI线程和子线程如果通过操作就会造成同步问题。
可是Java中,就算多线程操作同一块不安全没有加锁的内存,只是会造成数据错乱的问题,但是仍然可以正常运行。为什么Android中就会报异常,强制退出呢?
所以还是没有找到问题的关键,虽然说google设计的初衷肯定是因为多线程造成的同步问题
参考
2.【Android开发经验】来来来,同学,咱们讨论一下“只能在UI主线程更新View”这件小事
代码
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="StartAlarm"/>
</RelativeLayout>
MainActivity中
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new Thread() {
@Override
public void run() {
/*try {
Thread.sleep(2000);
start.setText("thread update");
} catch (InterruptedException e) {
e.printStackTrace();
}*/
start.setText("thread update");
}
}.start();
}
运行结果:
如果直接start.setText("thread update");那么不报异常,更新成功。
如果休眠了两秒,报异常,强制退出
E/AndroidRuntime: Process: lbb.demo.test, PID: 1799
E/AndroidRuntime: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime: at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)
E/AndroidRuntime: at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:907)
E/AndroidRuntime: at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime: at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime: at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime: at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime: at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime: at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime: at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:360)
E/AndroidRuntime: at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime: at android.widget.TextView.checkForRelayout(TextView.java:7172)
E/AndroidRuntime: at android.widget.TextView.setText(TextView.java:4342)
E/AndroidRuntime: at android.widget.TextView.setText(TextView.java:4199)
E/AndroidRuntime: at android.widget.TextView.setText(TextView.java:4174)
E/AndroidRuntime: at lbb.demo.test.MainActivity$1.run(MainActivity.java:31)
沿着异常栈打印消息,我们可以发现
调用了TextView的setText方法,接着调用TextView.checkForRelayout(因为是设置了textview的内容,所以textview的大小很可能发生变化,所以是requestLayout,而不是invalidate),checkForRelayout中调用基类View.requestLayout,requestLayout方法中有mParent.requestLayout();调用了父布局的方法,这里的父布局是RelativeLayout。因为在RelativeLayout中requestLayout只是调用了基类View的requestLayout方法,所以一直沿着视图层次往上,最后是到了DecorView这个根View上,
我们知道DecorView中其实很多处理都是放在ViewRootImpl中的
ViewRootImpl中的requestLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
checkThread检查当前线程是否是main线程,如果不是那么抛出异常throw new CalledFromWrongThreadException,原来如此
但为什么如果没有休眠2s,不会报异常呢? 因为此时ViewRootImpl都还没来得及去创建,setText只是为这个view更新了一下属性,自然不会抛异常。2s后创建ViewRootImpl对象,所以会抛异常。
ViewRootImpl在什么时候创建呢?简单的讲其实是在activity的onResume调用之后的。。。
可以参考前面的博客 Android View视图层次
更详细的分析可以参考 为什么我们可以在非UI线程中更新UI
另外一种情况,如果是自定义的View直接继承自View,在子线程中刷新会出现什么情况呢?实际上也会抛出CalledFromWrongThreadException异常
因为,我们知道
myview.invalidate();
实际上,view视图层次结构中对任何一个view,调用invalidate,最终都会调用到最顶层View,也就是DecorView的invalidateChildInParent方法,,,然而实际上是有ViewRootImpl来处理的,可以看到
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
...........
}
这里同样的调用了checkThread方法,来检查当前线程是否是main线程。虽然
ViewRootImpl的invalidate方法没有调用checkThead
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}