网上做网站网站代理,石家庄网站建设找汉狮,上海免费建站模板,自助小站点击蓝字关注我们为什么Android必须在主线程更新UI#xff1f;站在各位大牛的肩膀上#xff0c;谢谢#xff01;正常情况下#xff0c;Android需要在UI线程更新UI#xff0c;然鹅#xff0c;在特殊情况下#xff0c;子线程也能更新UI不在讨论之列#xff0c;这篇文章主… 点击蓝字关注我们为什么Android必须在主线程更新UI站在各位大牛的肩膀上谢谢正常情况下Android需要在UI线程更新UI然鹅在特殊情况下子线程也能更新UI不在讨论之列这篇文章主要讲一下个人理解的正常情况下为什么不能在非UI线程更新UI。先拿一句话来镇楼android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.然后晒出Android官方的一句话来说“The Android UI toolkit is not thread-safe and the view must always be manipulated on the UI thread.” 因为Android UI操作并不是线程安全的并且这些操作必须在UI线程执行。我们就主要分析一下这句话背后包含的含义。Android屏幕刷新机制。1 界面上任何一个 View 的刷新请求最终都会走到 ViewRootImpl 中的 scheduleTraversals() 里来安排一次遍历绘制 View 树的任务2 scheduleTraversals() 会先过滤掉同一帧内的重复调用在同一帧内只需要安排一次遍历绘制 View 树的任务即可这个任务会在下一个屏幕刷新信号到来时调用 performTraversals() 遍历View 树遍历过程中会将所有需要刷新的 View 进行重绘3接着 scheduleTraversals() 会往主线程的消息队列中发送一个同步屏障拦截这个时刻之后所有的同步消息的执行但不会拦截异步消息以此来尽可能的保证当接收到屏幕刷新信号时可以尽可能第一时间处理遍历绘制 View 树的工作4 发完同步屏障后 scheduleTraversals() 才会开始安排一个遍历绘制 View 树的操作作法是把 performTraversals() 封装到 Runnable 里面然后调用 Choreographer 的 postCallback() 方法5postCallback() 方法会先将这个 Runnable 任务以当前时间戳放进一个待执行的队列里然后如果当前是在主线程就会直接调用一个native 层方法如果不是在主线程会发一个最高优先级的 message 到主线程让主线程第一时间调用这个 native 层的方法6 native 层的这个方法是用来向底层注册监听下一个屏幕刷新信号当下一个屏幕刷新信号发出时底层就会回调 Choreographer 的onVsync() 方法来通知上层 app7onVsync() 方法被回调时会往主线程的消息队列中发送一个执行 doFrame() 方法的消息这个消息是异步消息所以不会被同步屏障拦截住8doFrame() 方法会去取出之前放进待执行队列里的任务来执行取出来的这个任务实际上是 ViewRootImpl 的 doTraversal() 操作9上述第4步到第8步涉及到的消息都手动设置成了异步消息所以不会受到同步屏障的拦截10doTraversal() 方法会先移除主线程的同步屏障然后调用 performTraversals() 开始根据当前状态判断是否需要执行performMeasure() 测量、perfromLayout() 布局、performDraw() 绘制流程在这几个流程中都会去遍历 View 树来刷新需要更新的ViewView刷新流程时序图详细信息可参考Android 屏幕刷新机制文章说的很详细简单来说 就是当View的刷新操作触发时会统一先注册到ViewRootImpl中 屏幕每隔16.6ms触发一次刷新这个信号会通知ViewRootImpl进行UI刷新 然后在ViewRootImpl中实际执行View的测量绘制的一系列操作。二UI线程到底是什么在上述4-8步中是在某个线程完成的这个线程就是实际上的UI线程。UI线程的名字的意义是遍历View树测量绘制View并将数据写入到buffer的线程。在一个APP启动的时候会建立一个Main Thread这时候仍要绘制页面因此这个Main Thread和UI Thread就是同一个线程。所以Main Thread和UI Thread相当于同一个概念。三为什么说必须UI线程更新UI对于开发来说的更新UI实际上是将View的变化通知到ViewRootImpl由ViewRootImpl实现后续操作。这个通知ViewRootImpl的操作包括 1invalidate(请求重绘) 2requestLayout(重新布局) 3requestFocus(请求焦点) 4startActivity(打开新界面) 5onRestart(重新打开界面) 6KeyEvent(遥控器事件本质上是焦点导致的刷新) 7Animation(各种动画本质上是请求重绘导致的刷新) 8RecyclerView滑动页面滑动本质上是动画导致的刷新 9setAdapter(各种adapter的更新) 10………… 这些操作所在的线程必须和UI线程在同一个线程。否则就会出现UI线程正在绘制页面而另外能操作UI的线程对View进行了操作当UI线程绘制完上方的View后那么这个被其他线程操作后的VIew的很有可能会覆盖到其他View之上这并不是我们想看到的结果。最后“Android UI操作并不是线程安全的”这句话个人理解是如果ViewRootImpl不强制检查线程那么任何都可以更改View的属性无法保证同一帧数据的完整性。 或许控制View绘制的线程和通知View更新的线程必须是同一线程比主线程更新UI更能表达出这层一次吧。*声明本文于网络整理版权归原作者所有如来源信息有误或侵犯权益请联系我们删除或授权事宜。戳“阅读原文”我们一起进步