版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一.setContentView(int layoutResID)
在自定义的 Activity 类中调用 setContentView()方法,会调到安卓 SDK 的 Activity 类中的 setContentView()方法。
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
在 Activity 类中的加载我们自己写的 xml 文件。首先,点击查看 getWindow() 方法。
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
直接返回 mWindow,mWindow 的定义是一个 Window 对象。查看 Window 源码,可以发现 Window 是一个抽象类,而且有且只有一个实现类 PhoneWindow。Window 类注解写着:
The only existing implementation of this abstract class is
android.policy.PhoneWindow,
即 Activity 中的 setContentView(int layoutResID) 方法下调用的是 PhoneWindow 中的 setContentView(int layoutResID) 方法。
这个 PhoneWindow 是在 Activity 中的 attach() 方法中初始化
二.PhoneWindow
PhoneWindow 类被隐藏起来,直接在 jar 中是无法看到的,需要导入 SDK 下的 resource 中源码才可以看到。
查看 PhoneWindow 中的 setContentView(int layoutResID) 方法:
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set 这里写代码片 in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
mContentParent 是一个 ViewGroup 容器,通过名字,我们可以猜测到这是我们显示的内容的容器。点击查看定义,由源码的注释可以看见, mContentParent 的内容不是 mDecor 的话就是 mDecor 的一个子类。mDecor 是一个 DecorView 对象,而 mDecor 是 window (即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;
继续点进去查看 DecorView 的源码,发现 DecorView 继承自 FrameLayout。
**小结:**每个 Activity 下都有一个 Window, 这个 Window 的具体实例是 PhoneWindow,PhoneWindow 下又都有个 DecorView 的 FrameLayout。
继续查看 PhoneWindow 的 setContentView(int layoutResID) 方法。当 mContentParent 为空时候进行 installDecor() 初始化,否则清除 mContentParent 下所有的 child view。
先查看 installDecor() 方法:(代码太长,这边就不全部贴出来)
首先就是当 mDecor 为空的时候,调用 generateDecor() 直接新建一个 DecorView,然后调用 generateLayout()来初始化 DecorView 的结构。
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
这是 generateLayout() 的第一行代码,跟自定义控件时获取窗口的属性一样。
TypedArray a = getWindowStyle();
接下去一大段代码是对 Window 的一些属性进行判断,首先是这几行代码,判断要显示的是不是浮窗类型的(如 Dialog)。setFlags 方法即我们平时使用的 getWindow().setFlags()这个方法。后续一大段代码也是进行一些其他属性的判断,然后进行 requestFeature() 或 setFlags() 方法的调用。requestFeature() 会对 Feature 相应的状态位进行设置。
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);
}
generateLayout()里面比较重要的一小段代码,在这里设置 DecorView 的布局为 MATCH_PARENT。
protected ViewGroup generateLayout(DecorView decor) {
......
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
......
}
在这里设置 DecorView 的布局为 MATCH_PARENT,不同版本源码有所差异。
往下一直拉,可以发现有这么一行获取 Feature 的代码,后面对 Feature 进行判断,然后加载不同的布局文件,这些布局文件将被加载到 DecorView 中。
int features = getLocalFeatures();
根据上方内容,在我门调用 setContent 方法的时候,方法内部会对 Feature 的值进行判断,然后加载不同的布局文件。所以当我们需要自己调用 requestFeature() 的方法,这个方法必须在 setContent() 方法之前
我们在 PhoneWindow 加载的不同布局文件中,选择最简单的一个 R.layout.screen_simple 打开来看。
布局文件就是一个 LinearLayout 下包含 一个 ViewStub 和一个 FrameLayout,这个 FrameLayout 的 id 是 content。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<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"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
在 generateLayout()的方法最后,调用 findViewById()方法获取上方布局文件中 id 为 content 的 FrameLayout,并将这个 FrameLayout 返回到 PhoneWindow 的 mContentParent 中,
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
返回到 PhoneWindow 的 setContent()方法,当加载的界面有动画时候会先加载动画,如果没有动画的时候,就会把我们的布局文件加载到 mContentParent, 也就是系统布局文件中 id 为 content 的 FrameLayout。
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
三.LayoutInflater
点击 PhoneWindow 中 SetContent ()方法下的 mLayoutInflater.inflate(layoutResID, mContentParent),最后调用的是 LayoutInflater 下的 inflate(int resource, ViewGroup root, boolean attachToRoot) 方法。
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
先用 XmlResourceParser 解析 xml 文件,点击 inflate(parser, root, attachToRoot) 方法。
读取 xml 设置的属性:
final AttributeSet attrs = Xml.asAttributeSet(parser);
对传进来的 xml 解析结果进行遍历,不是根节点的话继续循环,最终找到根节点:
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
当获取到跟节点后,在这里会对根节点是否是 meger 标签进行判断,当接线是 meger 标签, 并且 root 为空或者不是添加到跟节点(root 即我们上方说的 PhoneWindow 下的 mContentParent)的话则抛出异常。
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, attrs, false, false);
}
meger 必须添加到 ViewGroup 下,而我们前面说过,自定义的 xml 会加载到 mContentParent ( id 为 content 的 fragment)。这也是为什么我们自己写的 xml 中 meger 标签要作为跟节点的一个原因
如果不是 meger 标签的话,调用 createViewFromTag 把根节点创建出来,同时加载出该跟节点的属性,并进行设置。
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, attrs, false);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
这就是 xml 文件中各个标签的属性如何被设置到界面中的
不论是否是 meger 标签,最后方法都会调用 rInflate()这个方法, rInflate()用了一个 while 循环对该节点的子类进行遍历。
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, parent, attrs, inheritContext);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, attrs, inheritContext);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) parent.onFinishInflate();
}
其中对 include 和 merge 两个标签进行判断, include 标签不能作为根节点与 merge 标签要作为跟节点都在这里设置。
如果是其他标签的话,则继续调用 createViewFromTag 方法进行创建,同时加载出该跟节点的属性,并进行设置。然后继续递归调用 rInflate 方法。 最后方的 parent.onFinishInflate() 则是一个回调。
四.AppCompatActivity
查看 AppCompatActivity 下的 setContentView() 方法,可以很明显的看到与 Activity 的差异,AppCompatActivity 是使用 getDelegate()获取代理而不是直接使用 Window。
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
点击查看 getDelegate()方法,是使用 AppCompatDelegate.create()直接创建新的代理返回。在 AppCompatDelegate.create() 方法中,根据使用的版本不同返回对应的 AppCompatDelegate 的实现类。
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
小结:
即继承 AppCompatActivity 的 Activity 类中 setContentView() 方法最终调的是 AppCompatDelegate的对应版本实现类中的 setContentView()方法。
五.AppCompatDelegateImplV7
AppCompatDelegate 的实现类根据支持的版本不同,有不一样的结果,这边以 AppCompatDelegateImplV7 类为例。点击查看 AppCompatDelegateImplV7 的 setContentView() 方法。
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
在 ensureSubDecor()方法下也有类似 PhoneWindow 中根据不同属性去加载不同的布局文件。 在 AppCompatDelegateImplV7 的 onCreate() 方法中调用了 PhoneWindow 中的 getDecorView() 方法,以此初始化 PhoneWindow 的 DecorView 。不同的 AppCompatDelegate 的实现在这一块有所不同,但最终都是一样调用 PhoneWindow 中的。
在 ensureSubDecor() 加载的布局文件中,我们也选择一个相对简单的布局 R.layout.abc_screen_simple 进行解析:
<android.support.v7.internal.widget.FitWindowsLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/action_bar_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<android.support.v7.internal.widget.ViewStubCompat
android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/abc_action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/abc_screen_content_include" />
</android.support.v7.internal.widget.FitWindowsLinearLayout>
其中 include 包含的 abc_screen_content_include 为:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.v7.widget.ContentFrameLayout
android:id="@id/action_bar_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</merge>
可以发现,在 AppCompatDelegateImplV7 中加载的布局文件的样式跟在 PhoneWindow 中加载的是一致的,只是把控件换成对应的兼容性的控件。
ensureSubDecor() 方法继续往下,AppCompatDelegateImplV7 有如下一段代码(其他实现类实现可能不同,但功能一致), 他把 PhoneWindow 中的获取的 id 为 content 的 FrameLayout 子类复制到 上面 AppCompatDelegateImplV7 中获取的 布局文件下,在把这个布局设置到 PhoneWindow 中。有些版本是根据直接替换 id,但目的一样,都是把 PhoneWindow 下的 content 替换成带兼容性组件的布局。
final ViewGroup decorContent = (ViewGroup) mWindow.findViewById(android.R.id.content);
final ViewGroup abcContent = (ViewGroup) mSubDecor.findViewById(
R.id.action_bar_activity_content);
// There might be Views already added to the Window's content view so we need to
// migrate them to our content view
while (decorContent.getChildCount() > 0) {
final View child = decorContent.getChildAt(0);
decorContent.removeViewAt(0);
abcContent.addView(child);
}
// Now set the Window's content view with the decor
mWindow.setContentView(mSubDecor);
继续回到 AppCompatDelegateImplV7 的 setContent()方法,这时候 mSubDecor.findViewById(android.R.id.content) 获取的 ViewGroup 已经被替换成带兼容性的组件。
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
六.ActivityThread
Activity 在启动的时候会通过 ActivityThread 中的 handleLaunchActivity() 方法, ActivityThread 一启动就会调用 handleLaunchActivity ()方法。我们先来看一下再 handleLaunchActivity() 中调用的 performLaunchActivity()方法,
在 performLaunchActivity() 中会通过类反射获取我们的 Activity,接着在下方调用 Activity 的 attach 方法进行初始化(包括最开始讲的 PhoneWindow 初始化)。
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
继续往下,初始化后开始调用 Activity 的生命周期方法。在下方代码中调用 onCreate()。
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
回到 handleLaunchActivity() 继续往下,调用 handleResumeActivity(),很明显,这个调用的是 onResume()方法。
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
handleLaunchActivity()继续往下,他会获取到 DecorView,同时调用 WindowManager 的 addView() 方法进行保存。
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
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;
wm.addView(decor, l);
}
}
点击 wm.addView(decor, l) 进行查看,调用的是 WindowManagerImpl 下的 addView()方法。
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
WindowManagerImpl 下的 addView()方法又直接调用 WindowManagerGlobal 下的 addView()方法。在WindowManagerGlobal 的 addView()方法中,根据传进来的 DecorView 新建 ViewRootImpl 和获取 DecorView 的参数,同时把这些存储起来。在方法的最后,调用了 ViewRootImpl 的 setView()。
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) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
在 ViewRootImpl 的 setView()方法中,有一系列操作,包括动画,输入法等。之后会调用一个跨进程的消息,把我们的 DecorView 显示出来。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
ViewRootImpl 的 setView()继续往下,会调用 view.assignParent(this) 把 ViewRootImpl 设置为我们 DecorView 的 ViewParent ;
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
这也是 view 在用 requestLayout()方法的时候最终会走到 ViewRootImpl 的 requestLayout ()方法的根本原因。我们在调用 view 的 requestLayout()方法时,会不停的调用自己 ViewParent 的 requestLayout()方法
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
七.UI绘制流程
当调用到 ViewRootImpl 的 requestLayout()方法,就会发起重新绘制的流程。(这里只讲重点位置)
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
查看 scheduleTraversals()方法,会调用一个 mTraversalRunnable 的线程。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
mTraversalRunnable 的类是内部类 TraversalRunnable 。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
TraversalRunnable 执行方法 doTraversal()。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
在 doTraversal()方法中调用了 performTraversals()。 performTraversals()方法中按顺序调用了 performMeasure(), performLayout(), performDraw()方法。
performMeasure()方法中调用了 mView.measure()这个方法,而 mView 是我们前面说的 DecorView,最终这个调到了 View 中的 measure()方法。 在 View 的 measure()方法中会调用 onMeasure()这个方法。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
performLayout()调用了 mView 的 layout() 方法,同样最终调到 View 的 layout()方法。layout()方法 中会调用 onLayout()这个方法。
performDraw() 方法调用了 draw()这个方法,在 draw()方法中末调用了 drawSoftware(),drawSoftware()里面会 mView.draw(),最终也会调到 View 的 draw()方法。draw()方法中会调用 onDraw()这个方法。
所以,在 UI 绘制的流程中,会先调用 onMeasure(),再调用 onLayout(), 接着才调用 onDraw()。