大家好,今天来为大家解答深入解析Android View源码:DecorView与ViewRootImpl原理剖析这个问题的一些问题点,包括也一样很多人还不知道,因此呢,今天就来为大家分析分析,现在让我们一起来看看吧!如果解决了您的问题,还望您关注下本站哦,谢谢~
从setContentView说起
一般在Activity中,我们都会在onCreate()方法中写这句话:
setContentView(R.layout.main);显然,这是设置我们为活动定义的main.xml 布局。我们追踪一下源码,看看这个方法是怎么实现的。Activity#setContentView:
公共无效setContentView(@LayoutRes intlayoutResID){
getWindow().setContentView(layoutResID); //调用getWindow方法并返回mWindow
initWindowDecorActionBar();
}
.
公共窗口getWindow() {
返回mWindow;
}从上面可以看出,调用了mWindow的setContentView方法,那么这个“mWindow”是谁呢?尝试追踪源码,发现mWindow是Window类型,但它是一个抽象类,setContentView也是一个抽象方法,所以我们需要找到Window类的实现类。我们来看看Activity中mWindow是在哪里赋值的,可以发现它在Activity#attach方法中有如下实现:
最终无效附加(上下文上下文,ActivityThread aThread,
仪器仪表instr、IBinder 令牌、int ident、
Application应用、Intent意图、ActivityInfo信息、
CharSequence 标题、活动父级、字符串id、
NonConfigurationInstances 最后一个NonConfigurationInstances,
配置config、字符串引荐来源网址、IVoiceInteractor voiceInteractor) {
.
mWindow=new PhoneWindow(this);
.
我们只看关键部分。 PhoneWindow 类在这里被实例化。由此,我们知道PhoneWindow是Window的实现类。然后我们在PhoneWindow类中找到它的setContentView方法,看看它实现了什么。PhoneWindow#setContentView:
@覆盖
公共无效setContentView(intlayoutResID){
//Note: FEATURE_CONTENT_TRANSITIONS 可能在安装窗口的过程中设置
//装饰,当主题属性等具体化时。不检查该功能
//在这发生之前。
if (mContentParent==null) { //1
安装装饰();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
最终场景newScene=Scene.getSceneForLayout(mContentParent,layoutResID,
获取上下文());
过渡到(新场景);
} 别的{
mLayoutInflater.inflate(layoutResID, mContentParent); //2
}
最终回调cb=getCallback();
if (cb !=null !isDestroyed()) {
cb.onContentChanged();
}
首先判断mContentParent是否为null。如果为null,则执行installDecor()方法。那么这个mContentParent是什么呢?我们来看看它的评论:
//这是放置窗口内容的视图。它是
//mDecor 本身,或者内容所在的mDecor 的子级。
私有ViewGroup mContentParent;它是一个ViewGroup类型。结合代码我们可以知道,这个mContentParent就是我们设置的布局(即main.xml)的父布局。评论还提到,这个mContentParent是mDecor本身或者是mDecor的一个子元素。这句话是什么意思呢?让我留下一个问题,下面将对此进行解释。
我们先梳理一下上面的内容:Activity通过PhoneWindow的setContentView方法来设置布局。在设置布局之前,首先会判断mContentParent是否存在,而我们设置的布局文件就是mContentParent的子元素。
创建DecorView
接下来就是上面提到的installDecor()方法,我们看一下它的源码,PhoneWindow#installDecor:
私人无效installDecor() {
如果(mDecor==null){
mDecor=generateDecor(); //1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted mInvalidatePanelMenuFeatures !=0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
如果(mContentParent==null){
mContentParent=generateLayout(mDecor); //2
.
}
}
}首先,执行1号代码并调用PhoneWindow#generateDecor方法:
受保护的DecorView 生成装饰() {
返回新的DecorView(getContext(), -1);
可以看到这里实例化了DecorView,而DecorView是PhoneWindow类的内部类,继承自FrameLayout,所以可以看出它也是一个ViewGroup。
所以,DecroView到底充当了什么样的角色呢?其实DecorView是整个ViewTree的顶层View。它是一个FrameLayout布局,代表整个应用程序界面。布局下有两个子元素:标题视图和内容视图,内容视图为上面提到的mContentParent。接下来看代码,PhoneWindow#generateLayout方法protected ViewGroupgenerateLayout(DecorViewdecor){
//应用当前主题的数据。
//从主题文件中获取样式信息
TypedArray a=getWindowStyle();
.
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
请求功能(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
//如果没有标题,则不允许使用操作栏。
请求功能(FEATURE_ACTION_BAR);
}
如果(.){
.
}
//给窗户装饰充气。
//加载窗口布局
int 布局资源;
int features=getLocalFeatures();
//System.out.println("Features:0x" + Integer.toHexString(features));
if ((功能(1 FEATURE_SWIPE_TO_DISMISS)) !=0) {
layoutResource=R.layout.screen_swipe_dismiss;
} 否则如果(.){
.
}
查看=mLayoutInflater.inflate(layoutResource, null); //加载布局资源
Decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //给DecorView添加子View,即mContentParent
mContentRoot=(ViewGroup) 中;
ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT); //这里获取的是mContentParent
if (contentParent==null) {
throw new RuntimeException("窗口找不到内容容器视图");
}
if ((功能(1 FEATURE_INDETERMINATE_PROGRESS)) !=0) {
ProgressBar 进度=getCircularProgressBar(false);
如果(进度!=null){
进度.setInfiniteate(true);
}
}
if ((功能(1 FEATURE_SWIPE_TO_DISMISS)) !=0) {
注册SwipeCallbacks();
}
//剩余设置——背景和标题——仅适用
//到顶层窗口。
.
返回内容父级;
}从上面的代码可以看出,这个方法还是做了很多工作。首先根据设置的主题样式设置DecorView的样式,比如是否有标题栏之类的,然后给DecorView添加子View,而这里的子View就是上面提到的mContentParent。如果上面设置了FEATURE_NO_ACTIONBAR,那么DecorView就只有一个mContentParent的子View,这也解释了上面的问题:mContentParent是DecorView本身或者是DecorView的一个子元素。
用一张图来表示DecorView的结构如下:
DecorView结构总结:DecorView是一个顶级View,内部有两个子元素:titlebar和contentParent。 contentParent的id是content,我们设置的main.xml布局是contentParent内部的一个子元素。
创建完DecorView之后,我们回到PhoneWindow#setContentView方法,直接看代码: mLayoutInflater.inflate(layoutResID, mContentParent);这里加载了我们设置的main.xml布局文件,并且设置mContentParent为main.xml的父布局。至于如何加载,这里不再赘述。
至此,通过setContentView方法,已经创建了DecorView,并且加载了我们提供的布局。然而,此时我们的View仍然是不可见的,因为我们只是加载了布局,并没有对View进行任何测量、布局、测量。绘图工作。 View在进行测量过程之前,还有一步,就是将DecorView添加到窗口中,然后通过一系列的过程来触发ViewRootImpl#performTraversals方法。在这个方法中,测量、布局、绘图三大流程将正式启动。至于这一系列的流程是什么样的,因为涉及到很多机制,这里简单解释一下:
将DecorView添加至Window
每个Activity组件都有一个关联的Window对象,用于描述一个应用程序窗口。每个应用程序窗口都包含一个View对象,用于描述应用程序窗口的视图。上面已经分析了DecorView的创建过程。现在我们需要将DecorView 添加到Window 对象。要理解这个过程,我们首先需要简单了解一下Activity的创建过程:
首先启动ActivityThread#handleLaunchActivity中的Activity,这里会调用Activity#onCreate方法来完成上面介绍的DecorView创建动作。当onCreate()方法完成后,handleLaunchActivity方法会继续调用ActivityThread#handleResumeActivity方法。我们看一下该方法的源码:
最终无效handleResumeActivity(IBinder令牌,布尔clearHide,布尔isForward){
//.
ActivityClientRecord r=PerformResumeActivity(token,clearHide); //这里会调用onResume()方法
如果(r!=null){
最终活动a=r.activity;
//.
if (r.window==null !a.mFinished willBeVisible) {
r.window=r.activity.getWindow(); //获取窗口对象
视图装饰=r.window.getDecorView(); //获取DecorView对象
装饰.setVisibility(View.INVISIBLE);
ViewManager wm=a.getWindowManager(); //获取windowManager对象
WindowManager.LayoutParams l=r.window.getAttributes();
a.mDecor=装饰;
l.type=WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |=forwardBit;
如果(a.mVisibleFromClient){
a.mWindowAdded=true;
wm.addView(decor, l); //调用addView方法
}
//.
}
}
}在此方法中,获取与该活动关联的窗口对象、DecorView 对象和windowManager 对象。 WindowManager是一个抽象类,它的实现类是WindowManagerImpl,所以后面会调用WindowManagerImpl#addView方法。我们看一下源码:
公共最终类WindowManagerImpl 实现WindowManager {
私有最终WindowManagerGlobal mGlobal=WindowManagerGlobal.getInstance();
.
@覆盖
公共无效addView(视图视图,ViewGroup.LayoutParams参数){
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
}然后调用mGlobal的成员函数,而mGlobal是WindowManagerGlobal的一个实例,所以我们看WindowManagerGlobal#addView方法:
公共无效addView(视图视图,ViewGroup.LayoutParams参数,
显示display, WindowparentWindow) {
.
ViewRootImpl 根;
视图面板ParentView=null;
同步(mLock){
.
root=new ViewRootImpl(view.getContext(), display); //1
view.setLayoutParams(wparams);
mViews.add(视图);
mRoots.add(根);
mParams.add(wparams);
}
//最后执行此操作,因为它会触发消息以开始执行操作
尝试{
root.setView(view, wparams, panelParentView); //2
} catch (RuntimeException e) {
//BadTokenException 或InvalidDisplayException,清理。
同步(mLock){
最终int 索引=findViewLocked(view, false);
如果(索引=0){
删除ViewLocked(索引,true);
}
}
扔e;
【深入解析Android View源码:DecorView与ViewRootImpl原理剖析】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
终于有人写了关于 DecorView 和 ViewRootImpl 的文章!我想学习一下 Android 如何运作
有17位网友表示赞同!
对这些底层知识很感兴趣,一直在想怎么弄懂渲染流程
有14位网友表示赞同!
这篇文章能解决我在开发中遇到的很多疑惑吗?
有20位网友表示赞同!
我一直都知道 DecorView 和 ViewRootImpl 很重要,但不知道它们具体做了什么
有15位网友表示赞同!
看来 Android 原型系统越来越复杂了,需要好好学习一下源码才能真正理解
有16位网友表示赞同!
浅谈是不是有点太简单了,希望这篇文章能详细解释每个接口的作用
有5位网友表示赞同!
看完以后能不能分享一些实际项目中的应用场景呢?
有15位网友表示赞同!
想深入了解这些概念的同学可以看这篇文章哦!
有19位网友表示赞同!
我对 Android 的渲染机制一直很感兴趣,感谢作者分享这篇有用的文章!
有15位网友表示赞同!
学习代码总是需要一点突破口,希望这篇文章能帮我找到方向!
有7位网友表示赞同!
平时开发中很少关注源码,但是现在想要更深入地理解 Android
有8位网友表示赞同!
Android 开发确实需要学习源码才能更全面地掌握!
有13位网友表示赞同!
装饰视图和根视图在渲染机制中扮演着关键角色,好好了解一下非常有意义!
有13位网友表示赞同!
这篇文章能帮助我更好的理解 Android 如何布局和显示页面吗?
有10位网友表示赞同!
希望能看到更多关于 View 相关源码的解析,这样更容易入门学习!
有7位网友表示赞同!
感谢作者分享这份宝贵的知识!
有12位网友表示赞同!
看来 android 源码确实是个庞大的体系,需要一点时间慢慢探索!
有13位网友表示赞同!
希望更多开发者能关注 Android 的底层实现
有18位网友表示赞同!