我们在看一些书或者博客时总是会看到一句话“android更新UI操作都是在Main主线程中,子线程中不能进行UI更新操作”那么,在子线程中真的不能进行UI的更新操作吗?

//源码环境申明compileSdkVersion 24
buildToolsVersion "24.0.2"defaultConfig {minSdkVersion 14targetSdkVersion 24
}复制代码

首先我们来看一段代码:

在onCreate生命周期中添加子线程更新UI操作

@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);new Thread(new Runnable() {@Overridepublic void run() {text.setText("子线程刷新UI");}}).start();}复制代码

我们运行一下程序,发现APP没有崩溃,同时页面上显示了“子线程刷新UI”咦?不是说子线程中不能进行UI的更新操作嘛?为什么这个可以呢?

我们在onResume生命周期中添加子线程更新UI操作

  @Overrideprotected void onResume() {super.onResume();new Thread(new Runnable() {@Overridepublic void run() {text.setText("子线程刷新UI");}}).start();}复制代码

然后运行一下APP,发现这次APP崩溃了,打印一下Log日志

其中有一句经典的异常信息:

Only the original thread that created a view hierarchy can touch its views.复制代码

相信这句话大家应该基本都碰到过吧,这又是怎么回事呢?

为什么在onCreate中更新没事,而在onResume中更新就崩溃了?

带着这个问题,我们来跟踪一下源码,然后给出答案!

首先我们来看一下崩溃日志,一般情况下我们都可以通过崩溃日志来追踪问题。我们通过异常栈来一步一步追踪根源。

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.复制代码

第一句显示说明异常出现的原因:只有主线程才能进行更新UI操作。接下来一句:

at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:5288)复制代码

我们进入到ViewRootImpl.checkThread方法中

Tip: 对于AndroidStudioIDE我们可以通过双击shift键来查找相关的文件

void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}}复制代码

对于checkThread()方法,只有简单的几行代码,只是进行常规检查,如果当前线程不是主线程的话则会抛出异常。而mThread 这个变量是在构造函数中进行初始化的(记住,后面需要用到)。

   public ViewRootImpl(Context context, Display display) {//省略无关代码//...mThread = Thread.currentThread();//...//省略无关代码}复制代码

上面只是简单的进行判断当前线程是否是主线程,没什么价值,我们继续看第三行异常log信息

at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:974)复制代码

进入到该方法中,查看

  @Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) {checkThread();if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);if (dirty == null) {invalidate();return null;} else if (dirty.isEmpty() && !mIsAnimating) {return null;}//省略无关代码invalidateRectOnScreen(dirty);return null;}复制代码

好像并没有什么有用的提示,第一行也只是进行线程判断,我们进入到 invalidate()中看看,

void invalidate() {mDirty.set(0, 0, mWidth, mHeight);if (!mWillDrawSoon) {scheduleTraversals();}}复制代码

也没什么,继续进入scheduleTraversals()方法

  void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}复制代码

好像也没什么嘛,但是我们注意观察,可以发现

mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);复制代码

里面有一个参数 mTraversalRunnable,runnable不是线程的意思嘛,我们开启子线程经常使用到的,我们跟踪一下,看看究竟。

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}复制代码

咦,好像有点看头哦!对于线程操作,我们一般有两种方法

  1. 实现Runnable接口,实现run方法
  2. 继承java的原有线程Thread类,实现run方法

这两种方法的区别就是第二种继承Thread,Thread类也是通过实现Runnable方法来实现的,本质上没啥多大区别,只不过继承Thread类可以使用里面的一些方法而已。

而TraversalRunnable 类是直接实现Runnable接口,里面只有一个方法,我们进入到里面瞧瞧:

 void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}复制代码

好像也没什么多少有用信息,我们进入 performTraversals()方法中看看,,哇,太长了,还是不贴了。这个方法其实就是view的绘制了。通过以上代码我们可以知道每一次访问了UI,Android都会重新绘制View。

其实到了这里我们可以总结一点了,当访问UI时,ViewRootImpl会调用checkThread方法去检查当前访问UI的线程是哪个,如果不是UI线程则会抛出异常

但是为什么在OnCreate中可以操作子线程更新UI,而在OnResume中则不可以?而当前线程则是在ViewRootImpl的构造函数中进行初始化的,因此我们可以大胆的猜测一下:

ViewRootImpl类在onCreate中还没创建完成,而在onResume中已经创建完成了。

为了验证上面的猜想是不是正确的,我们需要解决的问题就是

ViewRootImpl在哪里进行创建的?

对于以上问题,我们需要用到这个类ActivityThread,这个类是干啥用的呢?看名字就知道是关于主线程的,我们浏览一下这个类,发现有个main方法,类似与java的main方法,这就是APP的全局入口处,

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");SamplingProfilerIntegration.start();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Set the reporter for event logging in libcoreEventLogger.setReporter(new EventLoggingReporter());// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}复制代码

我们在一开始的时候就注意到在onCreate中是可以进行子线程UI更新操作的,而在OnResume中是不可以的,我们当时猜测是因为在onResume中ViewRootImpl已经创建初始化完成了,所以能够进行checkThread检查,对于此我们需要了解,onResume是在哪里回调的,于是我们进入ActivityThread类中,里面有个方法:

    final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {//省略无关代码...// TODO Push resumeArgs into the activity for considerationr = performResumeActivity(token, clearHide, reason);if (r != null) {final Activity a = r.activity;//省略无关代码...r.activity.mVisibleFromServer = true;mNumVisibleActivities++;if (r.activity.mVisibleFromClient) {r.activity.makeVisible();}}//省略无关代码...复制代码

对于这一行代码

r = performResumeActivity(token, clearHide, reason);

我们可以大概知道这是进行resume回到的,到底是不是呢?我们进去看看,

public final ActivityClientRecord performResumeActivity(IBinder token,boolean clearHide, String reason) {if (r != null && !r.activity.mFinished) {//省略无关代码...r.activity.performResume();}复制代码

我们大概浏览一下方法,然后删除不必要的代码,里面有个方法,

r.activity.performResume();

继续进入,然后跟踪看看

final void performResume() {performRestart();//省略无关代码...mCalled = false;// mResumed is set by the instrumentationmInstrumentation.callActivityOnResume(this);}复制代码

继续进入方法

mInstrumentation.callActivityOnResume(this);

public void callActivityOnResume(Activity activity) {activity.mResumed = true;activity.onResume();if (mActivityMonitors != null) {synchronized (mSync) {final int N = mActivityMonitors.size();for (int i=0; i<N; i++) {final ActivityMonitor am = mActivityMonitors.get(i);am.match(activity, activity, activity.getIntent());}}}}复制代码

没错了,是在这里面进行onResume的回调的,对于此我们可以继续回到handleResumeActivity方法中去了,接下继续分析后面的代码

            if (r != null) {final Activity a = r.activity;//省略无关代码...r.activity.mVisibleFromServer = true;mNumVisibleActivities++;if (r.activity.mVisibleFromClient) {r.activity.makeVisible();}}复制代码

我们进入到makeVisible方法中去看看

 void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}复制代码

往WindowManager中添加DecorView,那现在应该关注的就是WindowManager的addView方法了。而WindowManager是一个接口来的,我们应该找到WindowManager的实现类才行,而WindowManager的实现类是WindowManagerImpl。

 @Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}复制代码

进入addview方法查看

   public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (display == null) {throw new IllegalArgumentException("display must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;if (parentWindow != null) {parentWindow.adjustLayoutParamsForSubWindow(wparams);} else {// If there's no parent, then hardware acceleration for this view is// set from the application's hardware acceleration setting.final Context context = view.getContext();if (context != null&& (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}}ViewRootImpl root;View panelParentView = null;synchronized (mLock) {// Start watching for system property changes.if (mSystemPropertyUpdater == null) {mSystemPropertyUpdater = new Runnable() {@Override public void run() {synchronized (mLock) {for (int i = mRoots.size() - 1; i >= 0; --i) {mRoots.get(i).loadSystemProperties();}}}};SystemProperties.addChangeCallback(mSystemPropertyUpdater);}int index = findViewLocked(view, false);if (index >= 0) {if (mDyingViews.contains(view)) {// Don't wait for MSG_DIE to make it's way through root's queue.mRoots.get(index).doDie();} else {throw new IllegalStateException("View " + view+ " has already been added to the window manager.");}// The previous removeView() had not completed executing. Now it has.}// If this is a panel window, then find the window it is being// attached to for future reference.if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {final int count = mViews.size();for (int i = 0; i < count; i++) {if (mRoots.get(i).mWindow.asBinder() == wparams.token) {panelParentView = mViews.get(i);}}}root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);}// do this last because it fires off messages to start doing thingstry {root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.synchronized (mLock) {final int index = findViewLocked(view, false);if (index >= 0) {removeViewLocked(index, true);}}throw e;}}复制代码

没错,就是这个

root = new ViewRootImpl(view.getContext(), display);

对ViewRootImpl进行初始化,然后进行UI线程检测操作,到了此处,我们代码分析基本就结束了,来总结一下源代码分析的过程。

首先,我们在onCreate中进行子线程操作UI,没有崩溃,而在onResume中进行子线程操作UI崩溃了,对于异常log信息,我们知道,检测当前线程是否是主线程的操作是在ViewRootImpl中,此我们猜想,ViewRootImpl的初始化是在onResume中进行的,对于此我们一步步进行跟踪源码进行查看。从而得出结论是正确的。


参考文章:http://blog.csdn.net/xyh269/article/details/52728861

android 关于关于子线程更新UI的一些事相关推荐

  1. Android子线程更新UI的方法总结

    消息机制,对于Android开发者来说,应该是非常熟悉.对于处理有着大量交互的场景,采用消息机制,是再好不过了.有些特殊的场景,比如我们都知道,在Android开发中,子线程不能更新UI,而主线程又不 ...

  2. Android中Activity、Window、ViewRootImpl与子线程更新UI

    三者层级关系 1.Window Window是一个抽象类,唯一的实现类是PhoneWindow Window分为三种类型应用Window.子Window.系统Window.子Window无法独立存在必 ...

  3. Android为什么不能在子线程更新UI

    Android为什么不能在子线程更新UI Android为什么不能在子线程更新UI? 如果不做这个校验,是不是我也可以正常在子线程更新UI 但是google为什么要这样去设计呢 ViewRootImp ...

  4. AndroidStudio子线程更新UI的几种方式

    在安卓开发中,大部分情况下是不能在子线程直接更新UI的,只能在UI线程更新UI,其根本原因在于加入在一个Activity中有多个线程去更新UI,且没有加锁机制,可能会产生界面混乱的情况,但是如果都加锁 ...

  5. 面试官问我:Andriod为什么不能在子线程更新UI?

    记得看文章三部曲,点赞,评论,转发. 微信搜索[程序员小安]关注还在移动开发领域苟活的大龄程序员,"面试系列"文章将在公众号同步发布. 1.前言 看完<你为什么在现在的公司不 ...

  6. C#利用Invoke和委托实现子线程更新UI(方式1)

    UI布局如下 委托定义如下: public delegate void SetMessageDelegate(string message); From1的代码如下: public partial c ...

  7. pyqt5 子线程更新ui

    from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * import time''' 信号 ...

  8. 子线程更新UI,牵扯activity的启动过程

    http://m.blog.csdn.net/article/details?id=43449123 点击打开链接

  9. 为什么我的子线程更新了 UI 没报错?借此,纠正一些Android 程序员的一个知识误区...

    开门见山: 这个误区是:子线程不能更新 UI ,其应该分类讨论,而不是绝对的. 半小时前,我的 XRecyclerView 群里面,一位群友私聊我,问题是: 为什么我的子线程更新了 UI 没报错? 我 ...

最新文章

  1. 如何关闭360自定义错误页面
  2. 计算机网络教室课程安排表,关于计算机教学计划汇总六篇
  3. SIGIR 2021 最佳学生论文:用于图像-文本检索的动态交互式建模
  4. myabatis oracle 调用存储过程返回list结果集
  5. 关于Java你不知道的10件事
  6. 如何将多个一维列表转化为二维列表_数据分析2_如何处理一维、二维数据
  7. Codeforces Round #460 (Div. 2)
  8. [Java] 1006. Sign In and Sign Out (25)-PAT甲级
  9. 【Hadoop】MapReduce
  10. 2021-10-21 pgRouting
  11. mysql的逻辑备份和恢复
  12. android学习心得之Activity
  13. linux软件包管理rpm
  14. 由一个照片,可以看出云是个物体
  15. 全国31省份实体经济发展水平数据 (2004-2017年)
  16. 加载glove-840B-300d.txt出现ValueError
  17. 资福医疗大圣磁控胶囊胃镜硬核出镜高交会
  18. python培训价目表-Python培训需要多少费用?
  19. Swift5.1 语言指南(三) 快速之旅
  20. java虚拟机学习笔记1

热门文章

  1. Redis 主从复制的几种方法
  2. ReportViewer中设置ServerReport.ReportServerCredentials属性的方法(WEB版)
  3. Python数据结构——序列总结
  4. mgy最新地址 mgyuser.com
  5. Sum of AP series——AP系列之和
  6. MariaDB:删除数据库报错:error: 'Error dropping database (can't rmdir './shiro', errno: 39)'
  7. java中的@override
  8. Commons net实现 FTP上传下载
  9. android Listview2 笔记
  10. 使用MEF方便的扩展