您的位置:首页 > 移动开发 > Android开发

Android setContentView源码解析

2016-02-01 16:36 495 查看
转载请标明出处:

/article/7691972.html;

本文出自:【Kevin.zhou的博客】

前言:在《Android
关于屏幕的一些事儿》中的最后提到了希望做一个屏幕适配的帮助类,这段时间我们内核组一个IOS的哥们在封装3D跨平台内核,也问起我一些关于Android屏幕的事情,身边都是大牛,越来越感觉自己小白了。

一、 闲扯

以下几篇博客想写写关于View的一些事情,为什么会有这个想法呢?是我之前想的一个简单的解决屏幕适配的方法,由xml布局填充为View最终都会通过layoutInflater的inflater方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
... ...
}
那么我给LayoutInflater类设置一个动态代理,然后监视到要执行inflate方法的时候把xml里面的数据更改一下就可以了。但是LayoutInflater是直接继承自Object,那么基于接口的动态代理肯定是没戏了,那么只有通过基于子类的动态代理,于是把Java
Web框架中常用的CGLib搞了过来:

final LayoutInflater inflater = LayoutInflater.from(this);
Enhancer.create(LayoutInflater.class, new MethodInterceptor() {

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy arg3) throws Throwable {
Log.i("MainActivity", "LayoutInflaterde执行的方法名称:" + method.getName());
return method.invoke(inflater, args);
}

});

没想到已运行程序挂了,一查原因,Android生成的类文件和JDK生成的类文件不同,突然想起来很初学Android的时候确实看到过。那这条路是走不通啦!

所以还是好好缕缕View的思绪吧~

二、Hello World

简单回顾下创建一个Activity的过程:

1. 创建一个类直接或间接继承Activity

public class MainActivity extends Activity {

}

2. 书写布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>

3. 在Acticity的onCreate()方法中设置布局

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

4. 在Manifest清单文件中注册该Activity

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
运行效果如下:



三、setContentView 分析

对于以上几个步骤,我们早就烂熟于心,而且相信大家也肯定好奇过,为什么通过setContentView就可以显示布局文件呢?那么我们就从源码的角度分析下:

1. setContentView()显然是父类中的方法,它是如何定义的呢?
在Activity类中,我们看到该方法的定义:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
2. 这里是调用的getWindow()返回对象的setContentView()方法,那么getWindow()返回的是什么呢?

public Window getWindow() {
return mWindow;
}

可见Window是Activity中的一个成员变量。
3. Window是怎么初始化的?
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {

... ...
mWindow = new PhoneWindow(this);
... ...
}
4. PhoneWindow 源码中 setContentView

@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}

首先判断 mContentParent是否为空,如果为空则初始化 decor,通过以往经验知道DecoredView是Activity View结构的根View,这里的installDecor()应该就是初始化了mContentParent;如果mContentParent不为空则删除所有子View;
然后通过LayoutInflater将xml布局填充为View并加载到mContentView,关于LayoutInflater会在下一篇博客来具体分析。

至此,Activity的setContentView() 方法的分析也就告一段落,大致总结下就是:
1. 我们定义的Activity的setContentView()调用Activity的setContentView()方法;
2. Activity的setContentView调用 PhoneWindow类中的setContentView()方法;
3. PhoneWindow类中的setContentView() 将xml布局通过LayoutInflater填充为View布局并加载到根布局。

四、installDecor 分析

通过以上了解了xml布局填充为View并加载到Activity的根View上,那么这个根View是如何创建的呢?
1. PhoneWindow 两个重要成员变量
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
通过注释可以看出, mDecor即Activity的根View,mContentView就是我们放置Activity布局的父View,并且它是mDecor或者mDecor的子View。

2. installDecor() 分析

private void installDecor() {
if (mDecor == null) {
// 初始化 mDecor
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
// 初始化 mContentView
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) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
// 设置 mTitleView显示内容
... ...
} else {
// 设置 mTitleView显示内容
... ...
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
if (mActionBar != null) {
// 设置 ActionBar
... ...
}
}
}
}

installDecor()主要进行了两个重要的事情,就是把mDecor和mContentView进行了初始化。(1)、通过generateDecor()初始化mDecor;(2)、通过generateLayout初始化mContentView。

3. generateDecor 分析
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
DecorView是PhoneWindow的一个继承自FrameLayout的内部类,这里创建了一个DecorView并赋值给了mDecor。

4. generateLayout 分析

protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();

// 设置 当前Activity配置的主题theme
// 窗口是否是浮动的
mIsFloating = a.getBoolean(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(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
// 窗口中ActionBar是否在布局空间内
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}

if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}

if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
requestFeature(FEATURE_SWIPE_TO_DISMISS);
}
// 窗口是否全屏显示
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}

... ...

//窗口状态栏颜色配置
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
//窗口导航栏颜色配置
if (!mForcedNavigationBarColor) {
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
}

if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB) {
if (a.getBoolean(
R.styleable.Window_windowCloseOnTouchOutside,false)) {
setCloseOnTouchOutsideIfNotSet(true);
}
}

WindowManager.LayoutParams params = getAttributes();
//输入法配置
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(
R.styleable.Window_windowSoftInputMode,
params.softInputMode);
}

if (a.getBoolean(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);
}
}
//设置当前Activity的出现动画效果
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
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(
R.styleable.Window_windowBackground, 0);
}
if (mFrameResource == 0) {
mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
}
mBackgroundFallbackResource = a.getResourceId(
R.styleable.Window_windowBackgroundFallback, 0);
if (false) {
System.out.println("Background: "
+ Integer.toHexString(mBackgroundResource) + " Frame: "
+ Integer.toHexString(mFrameResource));
}
}
mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
}
}

// 为窗口添加 decor根布局
// Inflate the window decor.

int layoutResource;
int features = getLocalFeatures();
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) {
if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
} else {
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;
}

mDecor.startChanging();

// 通过布局填充器LayoutInflater将layoutResource填充为布局,加载到decor上
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

// 对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);

if (mTitleColor == 0) {
mTitleColor = mTextColor;
}

if (mTitle != null) {
setTitle(mTitle);
}
setTitleColor(mTitleColor);
}

mDecor.finishChanging();

return contentParent;
}


根据代码中的注释大家可以清楚看到,主要是读取Activity的theme/feature配置,设置窗口,然后根据窗口的属性来选择对应的窗口修饰并填充为View加载到mDecor中,并对 contentParent赋值。
根据配置选择layoutResource布局com.android.internal.R.layout.xxx,比如:com.android.internal.R.layout.screen_action_bar;

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/actionBarStyle">
<com.android.internal.widget.ActionBarView
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/actionBarStyle" />
<com.android.internal.widget.ActionBarContextView
android:id="@+id/action_context_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
style="?android:attr/actionModeStyle" />
</com.android.internal.widget.ActionBarContainer>
<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 android:id="@+id/lower_action_context_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/actionBarStyle"
android:visibility="gone" />
</LinearLayout>
布局主要分为了ActionBar和FrameLayout的布局,是不是有点熟悉这个FrameLayout的id为content,对头,我们在上面分析的mContentParent就是这个FrameLayout。如下图所示:



五、总结

根据以上分析,流程即如下图所示:



下一篇《Android
LayoutInflater源码解析》博客将对LayoutInflater进行源码分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: