github: github.com/crazyandcod…
博客: crazyandcoder.github.io
第一次写关于源代码方面的内容,如果错误欢迎指正!
正文
我们在看一些书或者博客时总是会看到一句话“android更新UI操作都是在Main主线程中,子线程中不能进行UI更新操作”那么,在子线程中真的不能进行UI的更新操作吗?
//源码环境申明
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
minSdkVersion 14
targetSdkVersion 24
}
首先我们来看一段代码:
在onCreate生命周期中添加子线程更新UI操作
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new Thread(new Runnable() {
@Override
public void run() {
text.setText("子线程刷新UI");
}
}).start();
}
我们运行一下程序,发现APP没有崩溃,同时页面上显示了“子线程刷新UI”咦?不是说子线程中不能进行UI的更新操作嘛?为什么这个可以呢?
我们在onResume生命周期中添加子线程更新UI操作
@Override
protected void onResume() {
super.onResume();
new Thread(new Runnable() {
@Override
public 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)
进入到该方法中,查看
@Override
public 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 {
@Override
public 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 libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(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 consideration
r = 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 instrumentation
mInstrumentation.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
没错了,是在这里面进行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。
@Override
public 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 things
try {
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中进行的,对于此我们一步步进行跟踪源码进行查看。从而得出结论是正确的。