android 关于关于子线程更新UI的一些事
我们在看一些书或者博客时总是会看到一句话“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();}}复制代码
咦,好像有点看头哦!对于线程操作,我们一般有两种方法
- 实现Runnable接口,实现run方法
- 继承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的一些事相关推荐
- Android子线程更新UI的方法总结
消息机制,对于Android开发者来说,应该是非常熟悉.对于处理有着大量交互的场景,采用消息机制,是再好不过了.有些特殊的场景,比如我们都知道,在Android开发中,子线程不能更新UI,而主线程又不 ...
- Android中Activity、Window、ViewRootImpl与子线程更新UI
三者层级关系 1.Window Window是一个抽象类,唯一的实现类是PhoneWindow Window分为三种类型应用Window.子Window.系统Window.子Window无法独立存在必 ...
- Android为什么不能在子线程更新UI
Android为什么不能在子线程更新UI Android为什么不能在子线程更新UI? 如果不做这个校验,是不是我也可以正常在子线程更新UI 但是google为什么要这样去设计呢 ViewRootImp ...
- AndroidStudio子线程更新UI的几种方式
在安卓开发中,大部分情况下是不能在子线程直接更新UI的,只能在UI线程更新UI,其根本原因在于加入在一个Activity中有多个线程去更新UI,且没有加锁机制,可能会产生界面混乱的情况,但是如果都加锁 ...
- 面试官问我:Andriod为什么不能在子线程更新UI?
记得看文章三部曲,点赞,评论,转发. 微信搜索[程序员小安]关注还在移动开发领域苟活的大龄程序员,"面试系列"文章将在公众号同步发布. 1.前言 看完<你为什么在现在的公司不 ...
- C#利用Invoke和委托实现子线程更新UI(方式1)
UI布局如下 委托定义如下: public delegate void SetMessageDelegate(string message); From1的代码如下: public partial c ...
- pyqt5 子线程更新ui
from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * import time''' 信号 ...
- 子线程更新UI,牵扯activity的启动过程
http://m.blog.csdn.net/article/details?id=43449123 点击打开链接
- 为什么我的子线程更新了 UI 没报错?借此,纠正一些Android 程序员的一个知识误区...
开门见山: 这个误区是:子线程不能更新 UI ,其应该分类讨论,而不是绝对的. 半小时前,我的 XRecyclerView 群里面,一位群友私聊我,问题是: 为什么我的子线程更新了 UI 没报错? 我 ...
最新文章
- 如何关闭360自定义错误页面
- 计算机网络教室课程安排表,关于计算机教学计划汇总六篇
- SIGIR 2021 最佳学生论文:用于图像-文本检索的动态交互式建模
- myabatis oracle 调用存储过程返回list结果集
- 关于Java你不知道的10件事
- 如何将多个一维列表转化为二维列表_数据分析2_如何处理一维、二维数据
- Codeforces Round #460 (Div. 2)
- [Java] 1006. Sign In and Sign Out (25)-PAT甲级
- 【Hadoop】MapReduce
- 2021-10-21 pgRouting
- mysql的逻辑备份和恢复
- android学习心得之Activity
- linux软件包管理rpm
- 由一个照片,可以看出云是个物体
- 全国31省份实体经济发展水平数据 (2004-2017年)
- 加载glove-840B-300d.txt出现ValueError
- 资福医疗大圣磁控胶囊胃镜硬核出镜高交会
- python培训价目表-Python培训需要多少费用?
- Swift5.1 语言指南(三) 快速之旅
- java虚拟机学习笔记1
热门文章
- Redis 主从复制的几种方法
- ReportViewer中设置ServerReport.ReportServerCredentials属性的方法(WEB版)
- Python数据结构——序列总结
- mgy最新地址 mgyuser.com
- Sum of AP series——AP系列之和
- MariaDB:删除数据库报错:error: 'Error dropping database (can't rmdir './shiro', errno: 39)'
- java中的@override
- Commons net实现 FTP上传下载
- android Listview2 笔记
- 使用MEF方便的扩展