【Android 顶级视图 DecorView 的前世今生】关山初度尘未洗,策马扬鞭再奋蹄!这篇文章主要讲述Android 顶级视图 DecorView 的前世今生相关的知识,希望能为你提供帮助。
在Activity的启动过程中会执行ActivityThread#performLaunchActivity方法,
其中调用Activity#attach。在attach()方法中实例化Activity持有的mWindow属性为Window的唯一实现类PhoneWindow。
ActivityThread#performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
activity.attach(...);
...
}Activity#attach
final void attach(...) {
...
mWindow =
new PhoneWindow(this, window);
mWindow.setWindowManager(...);
mWindowManager =
mWindow.getWindowManager();
...
}
在ActivityThread#handleResumeActivity中, 通过ActivityThread#performResumeActivity方法调用Activity#onResume之后, 将会获取DecorView并通过WindowManager添加进ViewRootImpl。
final void handleResumeActivity(...) {
...
r =
performResumeActivity(token, clearHide, reason);
r.window =
r.activity.getWindow();
View decor =
r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm =
a.getWindowManager();
a.mDecor =
decor;
wm.addView(decor, l);
...
r.activity.makeVisible();
...
}PhoneWindow#getDecorView
public final View getDecorView() {
if (mDecor =
=
null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
r.activity.getWindow()返回的就是在Activity#attach中实例化的PhoneWindow对象。之后调用PhoneWindow#getDecorView去获取DecorView对象, 具体过程参见 Android setContentView()源码解析。然后填充decorView但是设置为不可见(通过r.activity.makeVisible()设置可见), 最后通过WindowManager#addView将decorView添加进ViewRootImpl。ViewRootImpl下文会仔细分析, 关于addView的过程参考Android 使用WindowManager实现悬浮窗及源码解析。细心的同学可能会注意到这时的DecorView刚通过WindowManger#addView添加到ViewRoot, 其实这也解释了为什么说“在onCreate至onResume过程中, Activity已经对系统可见, 但是还没有展示到界面上”的原因。这时因为Activity被装载而且已经执行完attach、onCreate、onStart、甚至执行完onResume, 但是DecorView始终没有被添加到Window上。onCreate、onStart、onResume中获取不到控件宽高也是因为这个原因, 在WindowManager#addView(内部实例化ViewRoot)之后才会真正的执行layout、measure、draw。
DecorView是FrameLayout的子类, 说白了也是继承自View。其余XML中的View/ViewGroup都是被添加进DecorView属于“将View/ViewGroup添加进ViewGroup”类型, 不具有代表性, 感兴趣的可以参考Android XML布局文件解析过程源码解析。
如果在Activity#onCreate中调用setContentView()方法, 那么会直接创建DecorView。如果没调用setContentView()方法, 那么在ActivityThread#handleResumeActivity中会通过r.window.getDecorView()自动创建DecorView。总之, 不管你创建于否, Activity中总会存在由PhoneWindow创建的DecorView。PhoneWindow的作用就是操作View, Activity调用findViewById类似的方法都是通过PhoneWindow间接操作View。如此, DecorView作为View树的顶级视图通过PhoneWindow便和Activity关联了起来。
在WindowManger#addView的过程中, 调用了ViewRootImpl#addView。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView =
=
null) {
...
mView =
view;
requestLayout();
res =
mWindowSession.addToDisplay(...);
...
}
}
}
首先将传递进来的View赋值给全局属性mView, 之后用mView属性来操作DecorView。mWindowSession.addToDisplay的过程在Android 使用WindowManager实现悬浮窗及源码解析中已经解析过, 简述作用就是将window添加进WindowManagerService的mWindowMap属性进行管理。requestLayout()这个方法就厉害了, 可能你会自定义View, 可能你看过View绘制的三大流程( measure、layout、draw) , 可是你知道这些东西的源头吗? 没错, 都在requestLayout()这里。插个题外话, 通过上述的代码可以发现, PhoneWindow也不是直接操作DecorView, 中间还隔着个ViewRootImpl。
@
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."
);
}
}
通常在子线程中更新UI, 会爆出checkThread中的错误。但是仔细看这个方法, mThread是在初始化ViewRootImpl时实例化的, 等于创建ViewRootImpl的线程。正常情况下我们是在主线程创建的ViewRootImpl, 实际上在子线程中也是可以创建的, 例如创建Toast。Toast也是一种window, 参考Android 高级自定义Toast及源码解析。但是Thread.currentThread()为更新UI时的线程。这两个如果同时在子线程会怎么样呢? 什么也不会发生。是的, 子线程是可以更新UI的。但是这里对UI的创建线程有要求, 只有子线程创建的ViewRootImpl可以在子线程中更新UI。子线程更新UI的示例代码如下:
new Thread(new Runnable() {
@
Override
public void run() {
Looper.prepare();
// Toast.makeText(MainActivity.this,"
一口仨馍"
,Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(MainActivity.this)
.setTitle("
Game of Thrones"
)
.setMessage("
winter is coming"
)
.setPositiveButton("
yes,my lord"
, null)
.show();
Looper.loop();
}
}).start();
scheduleTraversals()方法经过层层调用( mTraversalRunnable-> doTraversal-> performTraversals)
private void performTraversals() {
final View host =
mView;
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host =
mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}private void performDraw() {
// draw(fullRedrawNeeded) -->
drawSoftware
mView.draw(canvas);
}
performTraversals方法巨长, 这里只截取View绘制三大流程的起点。mView就是之前缓存的DecorView。之后便开始了View的measure、layout、draw、onMeasure、onLayout、ondraw。。。
更多Framework源码解析, 请移步 Framework源码解析系列[目录]
推荐阅读
- make: *** No rule to make target `out/target/common/obj/APPS/framework-res_intermediates/src/R.stamp
- 如何建立ElasticSearch里的mappings()
- 如何在亚马逊上发现有利可图的图书市场()
- 如何在2022年从博客转移到书籍(详细步骤介绍)
- 如何在一周内将你的亚马逊Kindle图书销量提高600%()
- 什么是亚马逊KDP发布((以及如何开始))
- 书籍出版指南(第一次成功的Kindle作者犯的10个错误)
- 新手指南(开设电子商务商店时需要注意的5件事)
- 如何为你的新博客或业务选择利基市场(详细指南)