View的draw流程
- 绘制背景
- 绘制自己
- 绘制子View
- 绘制装饰
View.getLocationInWindow()和View.getLocationOnScreen()区别
getLocationInWindow
- 一个控件相对父控件上的坐标位置。
getLocationOnScreen
- 一个控件在相对整个屏幕的坐标位置。
首次View的绘制是发生在什么时候
是在ActivityThread的handleResumeActivity()方法中
流程分析
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
if (r.window == null && !a.mFinished && willBeVisible) {
// 获取到Window对象
r.window = r.activity.getWindow();
//获取到DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取到WindowMananger对象
ViewManager wm = a.getWindowManager();
........
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
/**
* 重点中的重点
* 调用WindowManager的addView(还记得 WindowManager是一个接口类吧,唯一实现子类是WindowManagerImpl)
* 其实调用了WindowManger的addView()方法就是将DecoeView添加到了WindowManagerService中了
* (PhoneWindow只是处理一些应用窗口通用的逻辑(设置标题栏、导航栏))
* 去看下WindowManagerImpl的addView()
*/
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
// WindowManagerImpl # addView()
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
/**
* mGlobal ----> WindowManagerGlobal # addView
*/
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// 在这里委托给WindowManagerGlobal 调用了它的addView()方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
....... 省略代码
//创建了ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
// view ---> DecorView 设置布局参数
view.setLayoutParams(wparams);
//将添加的View保存到View列表中
mViews.add(view);
// 将root保存到ViewRootImpl的列表中
mRoots.add(root);
// 将wparams参数保存到mParams列表中
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
/**
重点
* root 是ViewRootImpl的对象
* 调用了ViewRootImpl的setView()
* 将DecorView添加到WMS
* 将窗口和窗口参数设置到ViewRootImpl中
*/
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
// 以上主要保存了一些信息。并且将View和布局参数传入到了ViewRootImpl的setView中
//ViewRootImpl # setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//这里传递进来的view其实是创建好的DecorView
mView = view;
.......省略代码
/**
* 重点呀
* 在将View添加到WindowManagerService中之前,确保View进行了一次 measure-->layout->draw
* 调用了此方法后,与ViewRootImpl关联的View会执行一次measure-->layout->draw
*/
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
/**
* 重点分析
*/
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
......省略代码
// 这里这个iew 是DecorView,把ViewRootImpl作为它的父亲设置给DecorView
// 这也是DecorView与ViewRootImpl之间的关系
view.assignParent(this);
}
}
- 可以说首次View的绘制 ActivityThread # performResumeActivity()方法是作为入口,真正调用了requestLayout()方法是在ViewRootImpl的setView()方法中。
ViewRootImpl的创建时机在什么时候
正如“View首次绘制时机时的分析”,ViewRootImpl的创建时机是在View首次绘制时,是在调用WindowManagerGlobal的addView()的方法中进行创建的。
ViewRootImpl与DecorView的关系
ViewRootImpl 是作为DecorView的父亲
在ViewRootImpl的setView()方法中 -----> view.assignParent(this);
ViewRootImpl实现了ViewParent的接口
DecorView的布局是怎么样的
-
对于布局层级,是这样的,Activity ---> PhoneWindow ---> DecorView
-
对于DecorView 是包含 titel_bar和content两部分的,DecorView是继承FrameLayout
-
上述所说的是title_bar 和 content 对应的是 R.layout.screen_simple布局,这也是默认的布局
-
那么这个布局是在什么设置的呢?
-
入口是setContentView,真正执行的地方是在PhoneWindow的installDecor()方法中
// PhoneWindow # installDecor private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { //创建DecorView(其实是一个FrameLayout) mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { // 绑定一个Window mDecor.setWindow(this); } if (mContentParent == null) { // 将创建的DecorView传递 调用generateLayout方法 mContentParent = generateLayout(mDecor); } } // generaLayout(DedcorView view) protected ViewGroup generateLayout(DecorView decor) { ...... 省略代码 // Inflate the window decor. int layoutResource; // 默认加载的布局是R.layout.screen_simple; 实则是通过获取到的features来加载不同的 int features = getLocalFeatures(); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { // 这是一种布局 layoutResource = R.layout.screen_swipe_dismiss; setCloseOnSwipeEnabled(true); } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { //这是一种 layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. // 这是一种 layoutResource = R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { //这是一种 layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = a.getResourceId( R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar); } else { //这是一种 layoutResource = R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. // 默认的布局 layoutResource = R.layout.screen_simple; } mDecor.startChanging(); //加载布局 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); } // 加载布局 void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { if (mBackdropFrameRenderer != null) { loadBackgroundDrawablesIfNeeded(); mBackdropFrameRenderer.onResourcesLoaded( this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), getCurrentColor(mNavigationColorViewState)); } mDecorCaptionView = createDecorCaptionView(inflater); //解析资源文件,获取到View的对象 也就是布局文件对应的View对象 final View root = inflater.inflate(layoutResource, null); if (mDecorCaptionView != null) { if (mDecorCaptionView.getParent() == null) { addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); } else { // 将获取到的View添加到DecorView中 addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; initializeElevation(); }
-
DecorView的创建时机
在上述分析过程中,在PhoneWindow的installDecor()方法中有调用到generateLayout(-1)创建了DecorView
这是DecorView创建的地方。
installDecor() 调用的时机是从setContentView()作为入口的,执行的流程就是 Activity(setContentView) ---> PhoneWindow(setContentView) ----> installDecor()
所以DecorView的创建时机是setContentView()方法的执行
setContentView()干了什么
- 创建了DecorView
- 调用了LayoutInflate.inflate()方法,将布局文件解析成View
- 将View添加到DecorView当中。
layoutInflate的流程是什么
PhoneWindow的创建时机
- 创建的地方是Activity的attach方法中
- Activity#attach方法的执行入口是在ActivityThread # performLacunchActivity()
- perfromLaunchActivity()方法的执行设计到Activity的启动流程。
如何触发View的重新绘制呢?
- requestLayout()
- Invalidate()
requestLayout 和 invalidate的区别
区别和联系
- requestLayout:当我们移动一个View位置或者改变View的大小时候调用
- View调用这个requestLayout方法,会标记当前View以及父容器,同时逐层向上提交,直到ViewRootImpl中,会调用三大工作流程 measure、layout、draw 对每一个由标记的View进行这三步操作。
- invalidate的方法调用会引起View树的重新绘制,比如说 setVisible
- 当View调用了invalidate方法后,会为该View添加一个标记位,不断向父容器请求刷新,父容器计算出需要重绘的区域,传递到ViewRootImpl中,触发performTraversals方法,开始对View树重新绘制(绘制需要重新绘的视图)
- postInvalidate功能如同 invalidate 只不过是在非UI线程中调用。
- 总的来说就是:requestLayout() 会执行 onMeasure()和onLayout();invalidate()方法会执行onDraw()
- requestLayout()过程会设置 FORCE_LAYOUT标志位;invalidate()过程会设置dirty标志
- onLayout:如果该View是ViewGroup对象,需要实现该方法,对每个子View进行布局
- onDraw:绘制视图本身(ViewGroup不需要实现,View都需要重载这个方法)
- drawChild:去重新回调每个子View的draw()方法。
点击事件中开启一个子线程更新UI会怎么样?
btn_toast.setOnClickListener { listener ->
//Toast.makeText(this, "展示 吐司", Toast.LENGTH_SHORT).show()
Thread(Runnable {
var name = Thread.currentThread().name
Log.d("tag", "$name")
btn_toast.text = "子线程"
}).start()
}
这个问题分两种情况,控件宽高固定,控件高度固定宽度warp_content,
情况1
- 这种情况是会更新成功,并且不会crash
- 由于宽和高都是固定的,此时操作更新UI并不会走requestLayout,仅仅重新绘制一下内容,所以就不存在线程检查的操作,所以就更新成功了。
情况2
- 这种情况会crash
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. - 原因就在于,当前的宽度是wrap_content,而当我们去操作更新的UI操作时,此时会调用requestLayout(重新进行测量和布局),在我们的requestlayout中会进行线程的检查,发现没有在主线程就会报错。
从Activity创建到View的显示这个过程中主要都发生了什么?
这个问题很广泛,我个人认为可以拆分成以下几点回答
handleLaunchActivity
在handleLaunchActivity方法主要调用了performLaunchActivity() 和 handleResumeActivity()
- 在performLaunchActivity中主要做了如下几个事情
- 创建了Context
- 通过反射创建了Activity
- 通过反射创建了Application
- 调用了attach
- 为每个Activity创建一个PhoneWindow
- 将WindowManger与PhoneWindow进行关联
- 执行了Activity的onCreate()方法
setContentView()过程
调用流程是 Activity(setContentView) ---> PhoneWindow(setContentView) ---> installDecor()
- 在installDecor中主要做了如下几个事情
- 创建了DecorView ---> DecorView extends FrameLayout
- 为DecorView中content加载布局文件
handleResumeActivity()方法的执行过程
- 首先调用了performResumeActivity()
- 执行了Activity的onResume()
- 获取了Activity专属的Window
- 获取到了DecorView
- 接着调用了WindowManagerImpl的addView()
- 内部调用了WindowManagerGlobal # addView()
- WindowManagerGlobal # addView
- 创建了ViewRootImpl
- 调用了ViewRootImpl # setView()
- ViewRootImpl # setView
- requestLayout() ---> 保证在绘制之前 进行了 View的绘制流程
- 将携带数据的Window发送到系统进程 WMS中进行数据的绘制。
网友评论