正常情况下我们在Activity的onCreate方法中调用了setContentView,在setContentView的流程后,xml被转成了View并添加到了DecorView,但是并没有真正的绘制。
绘制流程从ActivityThread.handleResumeActivity开始:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); //1:从这会走到Activity.onResume
...
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;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
...
}
如果从1处往下追踪可以看到会先调用Activity的onResume方法,再往后走才会进行View的绘制和添加到Window。先略过,看主流程:
DecorView被传进了WindowManager(wm.addView(decor, l)),而WindowManager是一个抽象类,addView方法属于他的父类ViewManager(也是抽象方法)。这个wm的实现者是谁呢? ViewManager wm = a.getWindowManager()->Activity中的方法:
public WindowManager getWindowManager() {
return mWindowManager;
}
final void attach(Context context, ActivityThread aThread, ...
mWindowManager = mWindow.getWindowManager() ;//mWindow的类型为Window
}
Window中的方法:
public WindowManager getWindowManager() {
return mWindowManager;
}
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
因此上面的wm实现者为WindowManagerImpl,WindowManagerImpl.addView:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
执行到WindowManagerGlobal.addView:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
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);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
new ViewRootImpl并调用了它的setView方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
try {
//与wms通信,请求系统添加窗口
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
...
}
方法很长,但是最关键的是调用了requestLayout和mWindowSession.addToDisplay。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
requestLayout先是进行了线程检查,接着调用了scheduleTraversals:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
在开始layout前checkThread方法先检查了当前线程是否是创建View的线程,如果不是则抛出异常(也就是只有主线程才能更新UI)。接着scheduleTraversals执行了mTraversalRunnable,即TraversalRunnable:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
因此scheduleTraversals->doTraversal:
void doTraversal() {
...
performTraversals();
...
}
performTraversals开始遍历,这个方法有800多行:
private void performTraversals() {
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth; //所需的窗口宽度
int desiredWindowHeight; //所需的窗口高度
....
if (mFirst) { //第一次遍历
mFullRedrawNeeded = true;
mLayoutRequested = true;
final Configuration config = mContext.getResources().getConfiguration();
if (shouldUseDisplaySize(lp)) {
//如果lp.type=TYPE_STATUS_BAR_PANEL、TYPE_INPUT_METHOD或TYPE_VOLUME_OVERLAY则使用屏幕宽高作为所需
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
desiredWindowWidth = mWinFrame.width();
desiredWindowHeight = mWinFrame.height();
}
// We used to use the following condition to choose 32 bits drawing caches:
// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// Set the layout direction if it has not been set before (inherit is the default)
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
} else {
//如果不是第一次遍历则使用frame的宽高,frame保存了上一次的大小
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
...
if (layoutRequested) {
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode); //确保开启触摸模式
} else {
...
}
//measureHierarchy方法将会进行测量,最多三次
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
if (didLayout) {//layout
performLayout(lp, mWidth, mHeight);
...
}
if (!cancelDraw) { //draw
...
performDraw();
}
...
if (computesInternalInsets) {
try {
mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
contentInsets, visibleInsets, touchableRegion);
} catch (RemoteException e) {
}
}
}
省略大量代码,measureHierarchy、performLayout、performDraw分别进行了测量、布局和绘制的工作;
测量:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
//获取一个默认值
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第一次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
//第二次测量
baseSize = (baseSize+desiredWindowWidth)/2;
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第三次测量,使用ip.width/height
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureHierarchy中共有三次调用performMeasure,performMeasure中调用了View的measure。
第一次:读取一个默认值R.dimen.config_prefDialogWidth(对话框大小)进行测量。
第二次:如果View.MEASURED_STATE_TOO_SMALL,给出的大小不足则扩大一点再次测量。
第三次:如果还是无法满足,则将窗口的全部分给该View。
performLayout和performDraw最终调用了View的layout和draw方法。
绘制工作完成后,回头看ViewRootImpl.setView的mWindowSession.addToDisplay:
mWindowSession的类型为IWindowSession,它是一个Binder对象,Client端的代理,它的服务端实现为Session,因为可以在它的赋值处看到:
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
而openSession的实现则在WindowManagerService中:
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {
return new Session(this, callback);
}
Session.addToDisplay:
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
outInsetsState);
}
mService即WindowManagerService,到这里,View的绘制完成后跨进程调用了wms的addWindow方法,wms会为这个窗口分配Surface,最终交给SurfaceFlinger处理绘制到屏幕上。
流程图:
涉及的类:
Window:抽象类,定义了窗口的外观和行为策略(一个窗口具备什么功能、方法)。是View的直接管理者,View必须依附于Window这个抽象概念。他的唯一实现者是PhoneWindow。
WindowManager:接口类,继承了ViewManager接口。提供给应用层管理Widow(增加View到Window或移除、更新View)。
ViewManager:接口类,定义了addView、updateViewLayout、removeView三个接口方法。
WindowManagerImpl:WindowManager的实现者,创建了ViewRootImpl,重写了getDefaultDisplay方法,确定View 属于哪个屏幕,哪个父窗口。实际addView、removeView等操作交给WindowManagerGlobal执行。
WindowManagerGlobal:保存了View、ViewRootImpl、WindowManager.LayoutParams三个ArrayList列表,管理整个进程 所有的窗口信息。一个应用只会创建一个。通过它调用ViewRootImpl的方法。
ViewRootImpl:View树的树根并管理View树、触发View的测量布局和绘制、输入响应的中转站、通过session与WMS进行进程间通信(binder)。
WindowManagerService:窗口的最终管理者,它负责窗口的启动添加和删除,窗口的大小和层级也是由他管理。窗口的操作最终交给他实现。
相关问题:
1.为什么在子线程更新会报错?
因为调用了ViewRootImpl的checkThread,大致流程:
View.invalidate --> … --> parent.invalidateChildInParent --> ViewRootImpl.invalidateChildInParent --> checkThread();文章来源:https://www.toymoban.com/news/detail-832148.html
2.如何实现在子线程刷新Ui?
在ViewRootImpl 创建之前调用。由于handlerResumeActivity会先执行回调即activity的onResume方法,再往后走才会创建ViewRootImpl,因此在onresume方法之前子线程更新ui的流程是view.requestlayout->parent.requestlayout,parent即ViewRooltImpl,但此时并未创建不会执行到viewRootImpl的requestlayout也就不会执行checkThread方法。所有onResuem方法之前可以在子线程更新ui。或者在需要刷新Ui的子线程创建ViewRootImpl。文章来源地址https://www.toymoban.com/news/detail-832148.html
到了这里,关于android10 contentView的绘制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!