Android setContentView方法解析(二)
上一篇我们说到在Activity中setContentView()其实调用的就是PhoneWindow的setContentView();然后对DecorView进行初始化,DecorView是PhoneWindow的内部类又是FrameLayout 的子类,其实DecorView就是这个屏幕的View。在mDecor初始化完成之后我们看到这样一段代码, mContentParent = generateLayout(mDecor);我们进入看它是怎么实现的,代码比较多,我们找关键的看
protected ViewGroup generateLayout(DecorView decor) {
…………
/*根据不同的属性设置不同的title*/
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);
}
…………
if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
…………
int layoutResource;
…………
/*根据不同的属性找到相应的layout*/
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
…………
} else { layoutResource = com.android.internal.R.layout.screen_title; }
…………
{ layoutResource = com.android.internal.R.layout.screen_simple; }
/*加载layout,然后add到DecorView中,inflate方法是加载xml文件的,在listView的getView中和fragment的onCreateView中用的比较多,具体源码我们以后在分析。*/
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
…………
if (mTitle != null) {
setTitle(mTitle);
}
setTitleColor(mTitleColor);
…………
return contentParent;}
我们通过查找发现,Android系统自定义的layout有很多,下面只截图了一部分,
通过上面代码我们可以看出generateLayout返回的是contentParent,它其实就是我们自定义layout的父控件,其中ID_ANDROID_CONTENT是在window类中定义的,
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
系统自定义的那些layout都有这个ID为content的View,我们可以随便打开一个看看,下面是screen_title.xml
<!--
This is an optimized layout for a screen, with the minimum set of features
enabled.
-->
<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"<strong></strong>
android:layout_height="wrap_content" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle"><font size="" color=""></font>
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</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>
到目前为止我们已经把setContentView()的源码分析的差不多了,可能还会有很多人感到困惑,View是怎么显示到Activity上的,我们仔细查看在Activity中找到这样一段代码
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
看到这里可能有的人就笑了,这个mDecor不就是我们上面讲到的DecorView吗,那么makeVisible又是在什么地方调用的?通过源码查找我们发现有这样一个方法,通过注释我们大概能明白就是如果不打算显示UI它本身,可以使用,默认的是在Activity配置中设置,这个我们一般用的比较少,
/**
* Control whether this activity's main window is visible. This is intended
* only for the special case of an activity that is not going to show a
* UI itself, but can't just finish prior to onResume() because it needs
* to wait for a service binding or such. Setting this to false allows
* you to prevent your UI from being shown during that time.
*
* <p>The default value for this is taken from the
* {@link android.R.attr#windowNoDisplay} attribute of the activity's theme.
*/
public void setVisible(boolean visible) {
if (mVisibleFromClient != visible) {
mVisibleFromClient = visible;
if (mVisibleFromServer) {
if (visible) makeVisible();
else mDecor.setVisibility(View.INVISIBLE);
}
}
}
除了在Activity中调用makeVisible()方法,我们发现在ActivityThread类中也调用了这个方法,上一篇我们说过这个类比较重要,除了在main方法中初始化looper以外,还负责Activity的生命周期的调用,等以后有时间讲到Android的消息机制时,大家可能就会知道我们在主线程中不需要初始化looper,而在子线程处理消息是必须要初始化looper,原因就在这。好了,言归正传,刚才说到在ActivityThread类中调用makeVisible()方法,一共有两处,一处是
private void updateVisibility(ActivityClientRecord r, boolean show) {
…………
r.activity.makeVisible();
…………
}
它主要是在StopActivity和WindowVisibility时调用,这里就不在介绍,但是还有一处地方,大家看到可能会感觉比较熟悉,上源码,代码比较多,我们就看我们要看的,其他的略过
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
boolean reallyResume) {
…………
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
…………
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
…………
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible()
}
}
…………
} final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
boolean reallyResume) {
…………
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
…………
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
…………
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible()
}
}
…………
}
其实在它内部调用的就有Activity的Onresume()方法,那么handleResumeActivity除了在handler的RESUME_ACTIVITY分支调用外又在handleLaunchActivity()这个方法中调用的,
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
…………
/*performLaunchActivity方法除了创建Activity以外还调用了Activity的onCreate()方法和onStart()方法,在这里我们就先不在介绍,我们知道就行了,以后有时间在单独介绍这块*/
Activity a = performLaunchActivity(r, customIntent);
…………
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
…………
}
好了,我们先回到刚才讲的makeVisible方法中来,我们知道mDecor就是我们看到的整个页面,然后让它VISIBLE,但是在它之前是怎么add的, 我们看到这样一段代码wm.addView(mDecor, getWindow().getAttributes());然后我们在看看ViewManager 类
/** Interface to let you add and remove child views to an Activity. To get an instance
* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
发现他只是一个接口,有3个方法,增加,更新,删除,对于管理View已经足够了,通过源码我们发现WindowManager 是实现ViewManager 接口的。
public interface WindowManager extends ViewManager {
但他是在什么地方实现的呢,我们在Activity的attach方法中看到这样的一行代码
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) {
…………
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
…………
mWindowManager = mWindow.getWindowManager();
…………
}
我们继续查看源码
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
看到了吧WindowManager的真正实现类是WindowManagerImpl,它里面封装的是WindowManagerGlobal,所有的操作都是通过WindowManagerGlobal来实现的,方法比较多,我们就随便挑一个看看,其他的就不在分析了
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
…………
ViewRootImpl root;
…………
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
…………
}
}
我们看到了ViewRootImpl这个类,这个类是ViewParent的子类,不能小看这个类,所有View的子类的measure,Layout,Draw包括事件的分发机制都要经过它,这里我们知道就行,就先不在介绍。mViews和mRoots其实就是个list,这里看最后加粗的一行代码,我们继续跟踪,找到它,
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
…………
mFallbackEventHandler.setView(view);
…………
mAttachInfo.mRootView = view;
…………
requestLayout();
…………
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
…………
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
}
mPendingOverscanInsets.set(0, 0, 0, 0);
mPendingContentInsets.set(mAttachInfo.mContentInsets);
mPendingVisibleInsets.set(0, 0, 0, 0);
if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException(
"Unable to add window " + mWindow +
" -- another window of this type already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException(
"Unable to add window " + mWindow +
" -- permission denied for this window type");
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException(
"Unable to add window " + mWindow +
" -- the specified display can not be found");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
…………
view.assignParent(this)
…………
}
}
}
如果大家看到上面抛出的异常是不是很激动,我们知道一个dialog或者PopupWindow必须在Activity中弹出,如果Activity不存在了,那么我们通过handle弹出的dialog或者Popupwindow就会报上面所呈现的异常。在上面我们还看到了这样一段代码mAttachInfo.mRootView = view,那么mAttachInfo又是什么呢,在ViewrootImpl的构造函数中看到这样一段代码,mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);,我们知道Attachinfo是View的内部类,
在它内部我们找到了它
/**
* The view root impl.
*/
final ViewRootImpl mViewRootImpl;
所以
mDecor通过windowManager把它加载到ViewrootImpl的mAttachinfo之后,在Activity启动的时候通过调用Activity的makeVisible()方法把它显示出来,好了,setContentView的方法已经分析完了,有时间在把其他的也都给不上,自己分析难免会有疏忽,欢迎大家一块讨论,谢谢。
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>