首页 > IT职业 > IT生活 > Android之View的诞生之谜
我们的activity中的视图是什么时候被加载的呢?有个方法你肯定会很眼熟:setContentView(R.layout.main);其实我们的activity就是通过这个方法加载我们的布局文件进行视图的渲染。那么我们就从他入手吧。
我们进入setContentView(R.layout.main)的源码看一下,注意代码中的注视:
public void setContentView(@LayoutRes int layoutResID) { //1、调用getWindow().setContentView(layoutResID);
// 加载我们的布局资;getWindow实际上是调用了phoneWindow
getWindow().setContentView(layoutResID); //2、
initWindowDecorActionBar();
}
window是什么东东?window是一个抽象类,他只有一个实现类,那就是phoneWindow,phoneWindow是android系统中窗口的顶级类。
我们接着看 getWindow().setContentView(layoutResID);
@Override public void setContentView(int layoutResID) {
//在渲染布局资源前做一些前期准备工作//1、 判断mContentParent是否为null,mContentParent其实// 是负责加载我们页面内容的容器,后面我们会讲到if (mContentParent == null) {
installDecor();
} else {
//1、如果不为null,说明原来页面上已经有内容了,
// 所以我们要移除所有的内容,后面再加载新的内容上去mContentParent.removeAllViews();
}
//调用mLayoutInflater来根据我们的布局资源id渲染视图mLayoutInflater.inflate(layoutResID, mContentParent);
.....
}
在 渲染我们的布局文件前,先调用了installDecor()来初始化mContentParent,之前也说mContentParent是负责加载我们页面内容的容器,到底是不是呢?我们看下installDecor源码便知道了:
private void installDecor() {//mDecor是window下的一个内部类,你可以理解成他是window用来填充视图的容器if (mDecor == null) {//1、通过 mDecor = generateDecor(); 实例化了DecorView,// 而DecorView则是PhoneWindow类的一个内部类,继承于// FrameLayout;
mDecor = generateDecor();
mDecor.setDescendantFocusability(
ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted &&
mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}if (mContentParent == null) {//2、通过传入mDecor来初始化mContentParent
mContentParent = generateLayout(mDecor);
...
}
}
}
从2处我们看到mContentParent被创建,那么它是如何被创建的呢,他真的是如我们前面所说负责加载内容部分的父容器么?我们来一探究竟,我们看 mContentParent = generateLayout(mDecor)的源码:
protected ViewGroup generateLayout(DecorView decor) { // 1、获得系统当前的style
TypedArray a = getWindowStyle();
... if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { //2、如果style是Window_windowNoTitle是true,
//说明当前的style是没有标题部分的,则请求移除标题
requestFeature(FEATURENOTITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { // 3、同样,检查是否需要显示系统的ActionBar
requestFeature(FEATUREACTIONBAR);
}
... //4、下面开始初始化我们的mContentParent了
int layoutResource; int features = getLocalFeatures(); if ((features & (1
小小的发现:从上面的代码我们可以解释很多开发中的技巧,看下面的代码,在加载我们的资源文件前,他就检查了FEATUREACTIONBAR和FEATURENOTITLE属性,所以我们想让activity全屏或者没有actionBar的话,必须在setContentView调用之前设置。
接下来我们回到前面
setContentViewgetWindow().setContentView(layoutResID);方法,继续看mLayoutInflater.inflate(layoutResID, mContentParent); 这个方法 mContenParent我们已经知道是什么了,然后通过mLayoutInflater.inflate,我们的布局就被渲染出来了。
DecorView补充: DecorView是整个ViewTree的最顶层View,我们之前分析过她是是个FrameLayout布局,代表了整个应用的界面。在该布局下面,有标题view和内容view这两个子元素,而内容view则是上面提到的mContentParent。如下图:
小结:调用setContentView方法,实例化了DecorView, DecorView有两个子布局,一个是加载顶部状态栏的,一个是加载我们的内容布局的,activity添加的xml就是内容布局的一个字元素
到目前为止,通过setContentView实例化了DecorView并且加载了设置进来的布局文件。然后,并没有发现任何与测量、布局、绘制相关的点,可能你会想,我们不会搞错了吧,其实没有哦,你们想想,setContentView实在,既然还是不可见的,那我为什么要耗费资源去测量呢,你最终能不能露个脸还说不准呢。亏本的买卖咱不干。其实要想知道什么时候开始执行测量等工作,我们可以看下ActivityThread的源码,ActivityThread是android用来管理activity的,这家伙知道的肯定多一些。那么我们就来了解下ActivityThread的执行流程。
首先ActivityThread通过调用handleLaunchActivity启动我们的目标activity,
private performLaunchActivity (ActivityClientRecord r,Intent customIntent{
......
activity.mCalled = false; //1、下面调用了Activity的onCreate方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
} if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()");
}
}
也就是说在performLaunchActivity调用之后,activity的onCreate被调用,我们的资源文件不加载,但是此时还是不可见的,也就还没有进行侧脸之类的事情。
然后我们继续看ActivityThread.handleResumeActivity的源码:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
......//1、可以看到,这里执行了activity的onResume方法ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) { final Activity a = r.activity;
....... if (r.window == null && !a.mFinished && willBeVisible) { // 2、获得window对象
r.window = r.activity.getWindow(); //3、 从window中获取DecorView对象
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE); //4、从activity中获得与之关联的windowManager对象
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit; if (a.mVisibleFromClient) {
a.mWindowAdded = true; //5、终于找到你了,这里将decor与WindowManager关联上,也就是将我们的decor正式//添加到window中,
wm.addView(decor, l);
}
......
}
}
}
知识补充:
Window是一个抽象的概念,一个Window对应一个View和一个ViewRootImpl;
Window和View是通过ViewRootImpl联系起来的。
ViewRootImpl才是一个View真正实现的动作。
WindowManager中也有一个WindowManagerImpl作为实现的类,负责具体的操作。
跟到这里,我们来总结一下,activity启动过程中,在执行handleResumeActivity时将我们的顶层视图DecorView通过WindowManager挂载到window中。
而WindowManager是个接口类,那么我们看看其实类对象WindowManagerImpl.addView方法
public void addView(View view, ViewGroup.LayoutParams params) { //1、这里通过mGlobal调用addView进行添加,而mGlobal是什么呢?
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
mGlobal其实是WindowManagerGlobal的一个内部实例,接着看WindowManagerGlobal.addView的源码:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...... //注意这个对象
ViewRootImpl root;
View panelParentView = null; synchronized (mLock) {
...... //1、通过DecorView获得上下文以及传入display实例化一个ViewRootImpl对象
//也就是说ViewRootImpl与DecorView关联起来了
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
} try { //2、这里调用了ViewRootImpl的setView方法,将DecorView与ViewRootImpl产生来关联。
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) { synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) {
removeViewLocked(index, true);
}
} throw e;
}
}
我们继续看ViewRootImpl.setView方法的源码
public final class ViewRootImpl implements ViewParent,
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
......
if (view instanceof RootViewSurfaceTaker) {
//1、这里会向系统发出申请,接管屏幕视图的渲染工作
mSurfaceHolderCallback =
((RootViewSurfaceTaker)view).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
}
}
//2、这里,我们看到了很熟悉的一个方法,这就是绘制我们的view的入口了
requestLayout();
......
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes(); //3、通过WindowSession来完成Window的添加过程这是一个IPC的过程,这里就不在深入了。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e);
} finally { if (restore) {
attrs.restore();
}
}
......
}
}
}
......
}
setView完成的工作很多,如声明输入事件的管道,DisplayManager的注册,view的绘画,window的添加等等
作为绘制view的入口,我们来看下requestLayout方法
@Overridepublic void requestLayout() { if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true; //1 、很开心,开始调度进行绘制流程了
scheduleTraversals();
}
}
ViewRootImpl.scheduleTraversals()调用后,系统会发起一个异步消息,然后在异步消息执行过程中调用performTraversals()完成具体的View树遍历;
小子,总算是找到你了,我们来看下胜利的果实吧!
private void performTraversals() {
... if (!mStopped) { //1、获取顶层布局的childWidthMeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//2、获取顶层布局的childHeightMeasureSpec
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); //3、测量开始测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
if (didLayout) { //4、执行布局方法
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
} if (!cancelDraw && !newSurface) {
... //5、开始绘制了哦
performDraw();
}
}
通过上面内容,我们学到了一些小技巧,如移除状态栏的一些步骤,之前我们可能知道,嗯,是的,要在setContentView前调用requestFeature才可以,通过这次分析,我们之前可能是知道要这样子做才行,现在我们知道了为什么要这样子做。是不是写起代码来更踏实了呢?
通过这次分析,我们对于activity的创建流程也略知一二,希望对你有帮助
测量、布局、绘制的工作我们放到下一章节进行学习
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2920/viewspace-2813039/ ,如需转载,请注明出处,否则将追究法律责任。