从setContentView分析Android加载布局的流程
来源:互联网 发布:大学生网络知识竞赛 编辑:程序博客网 时间:2024/06/07 04:48
一.概述
在Activity中,我们基本都会用到setContentView方法,这个方法是干啥的想必大家都知道,把我们写好的布局文件显示到界面上。今天我们就去看看底层的源码,分析一下到底是如何实现的。
二.分析
1.Activity$getWindow()
public class MainActivity extends Activity { private TextView tvText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvText = (TextView) findViewById(R.id.textview); }
我们先点进去MainActivity 中的setContentView方法,可以进入到Activity中,看到如下的代码:
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
在Activity的setContentView中调用了两个方法,我们只分析第一个,我们看看getWindow()返回的是一个什么东西,
public Window getWindow() { return mWindow; }
是一个Window对象mWindow,大家注意,Window是一个抽象类,并且Window中的setContentView是一个抽象方法,那么Activity中肯定就有一个Window抽象类的实现,我们查找代码发现 mWindow对象赋值方法如下:
mWindow = PolicyManager.makeNewWindow(this);
我们可以看到这里调用了PolicyManager中的静态方法makeNewWindow,我们继续点进去,
public final class PolicyManager { private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy"; private static final IPolicy sPolicy; static { // Pull in the actual implementation of the policy at run-time try { Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME); sPolicy = (IPolicy)policyClass.newInstance(); } catch (ClassNotFoundException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be loaded", ex); } catch (InstantiationException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex); } catch (IllegalAccessException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex); } } // Cannot instantiate this class private PolicyManager() {} // The static methods to spawn new policy-specific objects public static Window makeNewWindow(Context context) { return sPolicy.makeNewWindow(context); }
我们看到其实是调用了sPolicy对象的makeNewWindow方法,那么这个sPolicy对象是什么呢?很明显的可以看到是在上面通过反射得到的Policy的一个对象,所以我们接下来进入到Policy这个类中,寻找makeNewWindow这个方法,我们看到如下的代码,
public Window makeNewWindow(Context context) { return new PhoneWindow(context); }
最终返回的是PhoneWindow这样一个对象,通过着一系列的查找,也就是说Activity中的getWindow()方法得到的其实是一个PhoneWindow对象,其实PhoneWindow是Window的唯一实现类,我们在进入到Window这个类的源码中的时候官方已经告诉我们了,
* <p>The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */public abstract class Window {
2.PhoneWindow$setContentView
由于我们平时写的setContentView最终是调用了PhoneWindow里面的
setContentView,所以我们看看这里面的代码,
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } //将我们的布局文件添加到mContentParent中,这个mContentParent是根布局中id为content的一个FrameLayout mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
首先判断mContentParent是否为null,mContentParent是什么呢?接下来会分析,一开始条件成立,进入installDecor()方法。
private void installDecor() { if (mDecor == null) { ///创建DecorView mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null) { mContentParent = generateLayout(mDecor); // Set up decor part of UI to ignore fitsSystemWindows if appropriate. mDecor.makeOptionalFitsSystemWindows(); mTitleView = (TextView)findViewById(com.android.internal.R.id.title); if (mTitleView != null) { mTitleView.setLayoutDirection(mDecor.getLayoutDirection()); if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) { View titleContainer = findViewById(com.android.internal.R.id.title_container); if (titleContainer != null) { titleContainer.setVisibility(View.GONE); } else { mTitleView.setVisibility(View.GONE); } if (mContentParent instanceof FrameLayout) { ((FrameLayout)mContentParent).setForeground(null); } } else { mTitleView.setText(mTitle); } } else { mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); if (mActionBar != null) { mActionBar.setWindowCallback(getCallback()); if (mActionBar.getTitle() == null) { mActionBar.setWindowTitle(mTitle); } final int localFeatures = getLocalFeatures(); if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) { mActionBar.initProgress(); } if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { mActionBar.initIndeterminateProgress(); } boolean splitActionBar = false; final boolean splitWhenNarrow = (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0; if (splitWhenNarrow) { splitActionBar = getContext().getResources().getBoolean( com.android.internal.R.bool.split_action_bar_is_narrow); } else { splitActionBar = getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowSplitActionBar, false); } final ActionBarContainer splitView = (ActionBarContainer) findViewById( com.android.internal.R.id.split_action_bar); if (splitView != null) { mActionBar.setSplitView(splitView); mActionBar.setSplitActionBar(splitActionBar); mActionBar.setSplitWhenNarrow(splitWhenNarrow); final ActionBarContextView cab = (ActionBarContextView) findViewById( com.android.internal.R.id.action_context_bar); cab.setSplitView(splitView); cab.setSplitActionBar(splitActionBar); cab.setSplitWhenNarrow(splitWhenNarrow); } else if (splitActionBar) { Log.e(TAG, "Requested split action bar with " + "incompatible window decor! Ignoring request."); } // Post the panel invalidate for later; avoid application onCreateOptionsMenu // being called in the middle of onCreate or similar. mDecor.post(new Runnable() { public void run() { // Invalidate if the panel menu hasn't been created before this. PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); if (!isDestroyed() && (st == null || st.menu == null)) { invalidatePanelMenu(FEATURE_ACTION_BAR); } } }); } } }
在代码的第三行,我们看到
mDecor = generateDecor();
我们进入generateDecor这个方法,
protected DecorView generateDecor() { return new DecorView(getContext(), -1); }
这里创建了一个DecorView对象,DecorView是PhoneWindow里面的一个内部类,继承自FrameLayout,到目前为止,setContentView
方法里生成一个FrameLayout类型的DecorView组件,我们继续向下看,有这样的代码
if (mContentParent == null) { mContentParent = generateLayout(mDecor);
把 DecorView 对象 mDecor 作为参数传递给 generateLayout方法得到 mContentParent。generateLayout()方法中的代码实现如下:
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle(); //省去一些代码/**以下这些是Activity 窗口属性特征的设置*/ //窗口是否浮动,一般用于Dialog窗口是否浮动:是否显示在布局的正中间。 mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false); int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) & (~getForcedWindowFlags()); if (mIsFloating) { setLayout(WRAP_CONTENT, WRAP_CONTENT); setFlags(0, flagsToUpdate); } else { setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); } //设置窗口是否支持标题栏,隐藏显示标题栏操作在此处。 if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE); } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) { // Don't allow an action bar if there is no title. requestFeature(FEATURE_ACTION_BAR); }//ActionBar导航栏是否不占布局空间叠加显示在当前窗口之上。 if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) { requestFeature(FEATURE_ACTION_BAR_OVERLAY); } if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) { requestFeature(FEATURE_ACTION_MODE_OVERLAY); }//当前Activity是否支持全屏 if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) { setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); } if (a.getBoolean(com.android.internal.R.styleable.Window_windowOverscan, false)) { setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags())); } if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) { setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags())); } if (a.getBoolean(com.android.internal.R.styleable.Window_windowEnableSplitTouch, getContext().getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB)) { setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags())); } a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMajor, mMinWidthMajor); a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMinor, mMinWidthMinor); if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor)) { if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue(); a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor, mFixedWidthMajor); } if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor)) { if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue(); a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor, mFixedWidthMinor); } if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor)) { if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue(); a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor, mFixedHeightMajor); } if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor)) { if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue(); a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor, mFixedHeightMinor); } final Context context = getContext(); final int targetSdk = context.getApplicationInfo().targetSdkVersion; final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB; final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; final boolean targetHcNeedsOptions = context.getResources().getBoolean( com.android.internal.R.bool.target_honeycomb_needs_options_menu); final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE); if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) { addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY); } else { clearFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY); } if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) { if (a.getBoolean( com.android.internal.R.styleable.Window_windowCloseOnTouchOutside, false)) { setCloseOnTouchOutsideIfNotSet(true); } } WindowManager.LayoutParams params = getAttributes(); if (!hasSoftInputMode()) { params.softInputMode = a.getInt( com.android.internal.R.styleable.Window_windowSoftInputMode, params.softInputMode); } if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled, mIsFloating)) { /* All dialogs should have the window dimmed */ if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; } if (!haveDimAmount()) { params.dimAmount = a.getFloat( android.R.styleable.Window_backgroundDimAmount, 0.5f); } } if (params.windowAnimations == 0) { params.windowAnimations = a.getResourceId( com.android.internal.R.styleable.Window_windowAnimationStyle, 0); } // The rest are only done if this window is not embedded; otherwise, // the values are inherited from our container. if (getContainer() == null) { if (mBackgroundDrawable == null) { if (mBackgroundResource == 0) { mBackgroundResource = a.getResourceId( com.android.internal.R.styleable.Window_windowBackground, 0); } if (mFrameResource == 0) { mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0); } if (false) { System.out.println("Background: " + Integer.toHexString(mBackgroundResource) + " Frame: " + Integer.toHexString(mFrameResource)); } } mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000); } // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = com.android.internal.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 = com.android.internal.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( com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = com.android.internal.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( com.android.internal.R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = com.android.internal.R.layout.screen_action_bar; } else { layoutResource = com.android.internal.R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode; } else { //默认布局文件. layoutResource = com.android.internal.R.layout.screen_simple; // System.out.println("Simple!"); } mDecor.startChanging();//通过布局添加器LayoutInflater获取layoutResource布局, View in = mLayoutInflater.inflate(layoutResource, null); //将XML资源为layoutResource的布局添加到decor容器里面,至此PhoneWindow 内部类DecorView就添加了子布局 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //此处很重要,通过findViewById找到 contentParent容器,也是该方法的返回值。 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { ProgressBar progress = getCircularProgressBar(false); if (progress != null) { progress.setIndeterminate(true); } } // Remaining setup -- of background and title -- that only applies // to top-level windows. if (getContainer() == null) { Drawable drawable = mBackgroundDrawable; if (mBackgroundResource != 0) { drawable = getContext().getResources().getDrawable(mBackgroundResource); } mDecor.setWindowBackground(drawable); drawable = null; if (mFrameResource != 0) { drawable = getContext().getResources().getDrawable(mFrameResource); } mDecor.setWindowFrame(drawable); // System.out.println("Text=" + Integer.toHexString(mTextColor) + // " Sel=" + Integer.toHexString(mTextSelectedColor) + // " Title=" + Integer.toHexString(mTitleColor)); if (mTitleColor == 0) { mTitleColor = mTextColor; } if (mTitle != null) { setTitle(mTitle); } setTitleColor(mTitleColor); } mDecor.finishChanging(); return contentParent; }
以上代码代比较复杂,主要做了以下几件事情。
(1)初始化窗口的特征,是否显示标题栏,是否全屏,是否支持ActionBar浮动等等。
(2)通过LayoutInflater 将默认根xml布局转换成view
(3)将找到的View添加到DecorView根布局当中。
(4)从根布局中找到id为R.id.content的 contentParent 容器。也就是当前方法的返回值。
接下来我们看看这个com.android.internal.R.layout.screen_simple布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:fitsSystemWindows="true"> <!-- Popout bar for action modes --> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@android:id/title_container" android:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" style="?android:attr/windowTitleBackgroundStyle"> </FrameLayout> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>
我们的DecorView就添加了上面的布局,线性布局LinearLaout里包含两个组件,ViewStub是懒加载,默认不显示,FrameLayout是什么呢?看看id=content,就是我们下面这行找到的contentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
那么这个父容器 contentParent有什么作用呢?
我们回到下面的两行代码,都是之前出现过的
//获得父容器mContentParent = generateLayout(mDecor);
mLayoutInflater.inflate(layoutResID, mContentParent);
这里通过LayoutInflater将 setContentView(layoutResID)传进来的布局id加载到 父容器mContentParent中,至此,setContentView就将布局添加到Activity里面了。
现在我们来梳理一下流程:
Activity setContentView—>Window setContentView—>PhoneWindow setContentView—->PhoneWindow installDecor—–>PhoneWindow generateLayout——>PhoneWindow mLayoutInflater.inflate(layoutResID, mContentParent);
Activity 类中有一个Window抽象类的实现PhoneWindow类,该类中有个内部类DecorView,继承自FrameLayout,在DecorView容器中添加了根布局,根布局中包含了一个id为 contnet的FrameLayout 内容布局,我们的Activity加载的布局xml最后添加到 id为content的FrameLayout布局当中了。用一个图来描述,如下:
总结:
1.关于requestWindowFeature(Window.FEATURE_NO_TITLE); 去除标题栏的疑问,如果你自己的xxxActivity是继承自Activity,那么恭喜你使用以上方法可以去除标题栏,如果你自己的xxxActivity是继承自AppCompatActivity或者ActionBarActivity,那么很遗憾告诉你,此次系统默认的标题栏已经在主题中去除,此时显示的标题栏是ActionBar导航栏,如果需要去除导航栏,你可以通过如下代码:getSupportActionBar().hide();来隐藏导航栏。
2.requestWindowFeature(Window.FEATURE_NO_TITLE);方法需要在 setContentView方法之前使用,由上面 Step5分析可得,设置Activity Window 特征是在setContentView方法中设置的,因此,如果需要改变Activity Window窗口特征,需要在setContentView方法之前。其实这里有疑问???为什么设置全屏的方法
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
可以在setContentView之后呢???求解。
- 从setContentView分析Android加载布局的流程
- 从setContentView方法分析Android加载布局流程
- 从setContentView方法分析Android加载布局流程
- 从setContentView方法分析Android加载布局流程
- Android布局加载之setContentView源码分析
- Activity setContentView 加载布局流程
- 从setContentView谈谈android的布局层级
- Android布局文件的加载过程分析:Activity.setContentView()源码分析
- android源码分析——由SetContentView串起来的布局加载机制
- 源码分析setContentView加载布局文件的过程
- 【Android API】setContentView流程分析
- 源码分析 setContentView() 布局加载机制
- android中布局和View创建的源码分析---setContentView
- Android setContentView 加载布局源码解析
- Android加载layout, 从setContentView开始
- android视图学习---Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起
- Android 4.2 SetContentView 流程分析(一)
- Android 4.2 SetContentView 流程分析(二)
- Android高效加载大图、多图解决方案,有效避免程序OOM
- Tcp
- Java 图片压缩
- 使用Unity发布第一个程序时遇到的问题
- SQL分组统计:由一个表的两列作为轴
- 从setContentView分析Android加载布局的流程
- Java动态加载一个类的几种方法以及invoke
- Spring4.x 不再支持JpaTemplate和JpaDapSupport类了
- 字符流中第一个不重复的字符
- xml(ibatis配置)中CDATA的用法
- DialogFragment详解
- 第一次用,高大上的感觉啊.
- 缓存数据 ehcache
- 如何不让你的APP在模拟器中运行。