View的绘制流程--> 简单的讲就是onMeasure、onLayout、onDraw.
虽然字面上看上去只是三个简单的方法,其实背后涉及到原理可真不少,不仅涉及到View的绘制还有Activity的启动流程里面的一部分知识点贯穿起来
1. 子线程真的不可以更新UI吗?
2. 让子线程更新的方法
知道这两个问题的可以在评论区写出你们的观点
上一文章中Activity的启动流程以源码的形式分析了startActivity到onCreate的过程,但是这个时候Activity还是处于用户看不到页面的状态,接下来有哪些操作才会让用户看到页面呢!!
- Activity负责UI的显示和处理,但是Activity并不会直接管理View
- Window负责承载View,并且还是由它的子类PhoneWindow去负责承载,它持有根View->DecorView,window是通过windowManager将自己和decorView关联起来的
- ViewRootIMPL管理DecorView,不仅是WindowManagerService(WMS)和DecorView的连接枢纽,还是管理View的绘制和事件的分发
1. 创建Window
Window的字面意思就是窗口,是一种抽象的概念,PhoneWindow是Window的具体实现类
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
....
final Activity a = performLaunchActivity(r, customIntent);
....
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
....
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
....
}
//Activity.Java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
...
}
可以得知PhoneWindow的创建是在Acitity实例化后通过attch函数完成了window的创建,所以每个Acitity都有一个对应的PhoneWindow,WindowManager负责管理所有Acitity和WMS的通信,而它是一个接口,WindowManagerIMPL是它的实现类
2.DecorView的创建
在上面handleLauncherAvtivity里面Activity实例化后 最终会执行Activity的oncreate方法
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
public void callActivityOnCreate(Activity activity, Bundle icicle,
PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
onCreate(icicle);
}
这样就开始我们的setConentView的流程了
这里我们可以看Activity的源码,也可以看AppCompatActivity
//AppCompatActivity
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
//Activity
@Override
public void setContentView(View view) {
getWindow().setContentView(view);
}
可以看得出版本不同,负责的流程不一样,新的AppCompatActivity也是为了添加新的特性和功能,但是原理都是换汤不换药,都是window和decorview,以及windowmanager,这里我们先看AppCompatActivity
getDelegate -->AppCompatDelegate 而它是一个抽象类,所以是它的子类AppCompatDelegateImpl负责内容的加载
@Override
public void setContentView(int resId) {
//一个类似的DecorView的创建
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//将用户的xml页面填充到conent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
private void ensureSubDecor() {
//mSubDecorInstalled默认为false
if (!mSubDecorInstalled) {
//创建新的容器页面 类似于DecorView
mSubDecor = createSubDecor();
applyFixedSizeWindow();
mSubDecorInstalled = true;
}
}
private ViewGroup createSubDecor() {
//最终该是交给window
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
}
最终还是交给PhoneWindow去处理了
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
//创建真正的decorView,titleview+content
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
//通过content容器将我们自己的Xml经过系统的再次包装的View添加进来
mContentParent.addView(view, params);
}
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//创建decorview
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//在decorview中取出contentview
mContentParent = generateLayout(mDecor);
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
} else {
}
}
protected DecorView generateDecor(int featureId) {
.....
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
....
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
经过以上流程,就把我们的布局加载到decorview中了
3.将DecorView与ViewRootImpl关联起来
还记不记得中ActivityStackSupervisor中 clientTransaction.addCallback一个LaunchActivityItem还通过setLifecycleStateRequest把ResumeActivityItem添加进来
boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc{
final ClientTransaction clientTransaction = ClientTransaction.obtain(
proc.getThread(), r.appToken);
final DisplayContent dc = r.getDisplay().mDisplayContent;
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(),
r.assistToken, r.createFixedRotationAdjustmentsIfNeeded()));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(dc.isNextTransitionForward());
} else {
lifecycleItem = PauseActivityItem.obtain();
}
clientTransaction.setLifecycleStateRequest(lifecycleItem);
mService.getLifecycleManager().scheduleTransaction(clientTransaction)
}
之后通过TransactionExecutor的execute去执行的,而executeCallbacks是LaunchActivityItem,executeLifecycleState执行的就是ResumeActivityItem 这里属于事务的形式
executeCallbacks(transaction);
executeLifecycleState(transaction);
//ResumeActivityItem.java
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward,
"RESUME_ACTIVITY");
}
onResume执行后会执行WindowManager.addView,当addView里面的View执行完成 Resume的整个流程才算执行完毕
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
//执行Resume
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r == null) {
// We didn't actually resume the activity, so skipping any follow-up actions.
return;
}
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
@VisibleForTesting
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,String reason) {
....
final ActivityClientRecord r = mActivities.get(token);
r.activity.performResume(r.startsNotResumed, reason);
return r;
}
包括在performResume里面先执行onrestart在执行onresume的生命流程也清楚了
//Acitvity.java
final void performResume(boolean followedByPause, String reason) {
performRestart(true /* start */, reason);
mInstrumentation.callActivityOnResume(this);
}
public void callActivityOnResume(Activity activity) {
activity.onResume();
}
接着开始WindowManager的addView
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
wm是一个ViewManager接口,是通过activity的getWindowManager()获取的
public WindowManager getWindowManager() {
return mWindowManager;
}
而这个mWindowManager在哪里被赋值的呢 ,还记不记得activity的启动流程里面 activity的attach里面
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
喔,原来wm的具体实现是WindowManagerImpl啊
//WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
看到这里,我们发现原来ViewRootImpl的实例化是在这里啊,并且通过ViewRootImpl的setView将DecorView关联起来
//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
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, userId);
} catch (RuntimeException e) {
}
}
}
那我们再看看关联起来之后做了什么
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
if (mView == null) {
mView = view;
//开始绘制
requestLayout();
//mParent的赋值 这里也很重要
view.assignParent(this);
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//这里就是线程的检查 也是子线程不能更新ui原因 后续分析
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//添加内存屏障 也叫消息屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//开始通知sync执行绘制 是在sync下一次刷新开始绘制
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除内存屏障 也叫消息屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}
private void performTraversals() {
//根据窗口信息和lp获取测量规则
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (didLayout) {
// 执行布局
performLayout(lp, mWidth, mHeight);
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
// 执行绘制
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
}
}
}
4. 开始View或者ViewGroup内部的绘制流程
- 测量规则
上面分析了getRootMeasureSpec,其表面意思获取根 view 的测量规则,所有View在测量阶段都是遵循一个规则,即根据父View的MeasureSpec和自身的LayoutParams如何测量,但是,DecorView是根据窗口的大小和自身的LayoutParams决定的
三种模式:
MATCH_PARENT-->MeasureSpec.EXACTLY
WRAP_CONTENT-->MeasureSpec.AT_MOST
UNSPECIFIED-->MeasureSpec.EXACTLY
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
-View的测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
如果用户想让wrap_content变成自己测量的或者自己写的,就需要重写测量模式的规则
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//具体值就是自己传入的
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// wrap_content和Match_content就是父View的大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
- ViewGroup的测量
对于DecorView来说,执行的就是ViewGroup的measure方法,内部会判断是否有子View,然后循环遍历子View的measure
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
5.layout流程
经过performMeasure流程,ViewTree中各个元素的大小已经被记录下来,接下来会进入另一个遍历的流程,即Layout,它的作用是ViewGroup用来确定子元素的位置信息。
public void layout(int l, int t, int r, int b) {
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
}
6.draw流程
一个对象的layout确定后,才可以执行draw
public void draw(Canvas canvas) {
// Step 1, draw the background, if needed
int saveCount;
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
}
整体上做了以下几步:
- 绘制背景
- 绘制自己的内容
- 绘制childen
- 绘制装饰(前景/滚动条)
- 我们在子线程更新UI会报错,但有时候不会报错,这是为什么呢,具体报错日志是这个样子的,意思是 只能在创建视图的原始线程(mThread或者主线程也可以叫工作线程)才能去接触到里面的View
Only the original thread that created a view hierarchy can touch its views.
假设我们在oncreate里面去更新TextView的text就会出错,具体原始什么我们跟一下源码
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
....
//mLayout是在onMeasure中构建的
if (mLayout != null) {
checkForRelayout();
}
....
}
private void checkForRelayout() {
requestLayout-->parent的requestlayout
}
最终执行到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.");
}
}
这就是为什么报错的原因
2. 让子线程更新的方法
1. 先让主线程执行一次,然后子线程执行的时候变量已经被更改就不会checkThread
mDatabind.tvVoicePrice.setOnClickListener {
mDatabind.tvVoicePrice.text = "asasa"
Thread{
mDatabind.tvVoicePrice.text = "ssssss"
}.start()
}
2. 创建一个Loop 因为子线程没有Loop,有了Loop我们的让他循环起来
Thread{
Looper.prepare()
mDatabind.tvVoicePrice.text = "ssssss"
Looper.loop()
}.start()
3.通过硬件加速绕开 requestLayout只执行invalidate
4.通过Surface的holder获取canvas对象就可以在子线程更新UI
总结
在这一部分中,要先梳理好整个View的框架,比如 Activity,Window,ViewRoot,WindowManagerImpl 之间的关系,然后再看整个 ViewTree 的遍历以及处理会比较好理解。