View依附于Window见剩,而Activity負(fù)責(zé)管理Window杀糯。為什么會產(chǎn)生這樣的關(guān)系呢?文章圍繞這個問題苍苞。將會從Activity加載View的整個流程去分析Activity固翰、Window、View三者之間的關(guān)系羹呵。
1骂际、在啟動Activity時,會在onCreate()方法中調(diào)用setContentView()去加載布局冈欢,在這一階段歉铝,只是把布局添加到了DecorView(根視圖)中,并沒有真正的依附于Window凑耻,那么是什么時候添加到Window的呢太示?這個問題先記著,接下來通過分析源碼揭開其廬山真面目香浩。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//......
}
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
//初始化ActionBar
initWindowDecorActionBar();
}
上述代碼中类缤,可以發(fā)現(xiàn),直接把任務(wù)轉(zhuǎn)移到Activity的setContentView()方法中邻吭,調(diào)用了getWindow().setContentView()加載我們的布局資源文件呀非,然后initWindowDecorActionBar()初始化ActionBar,插個畫:
getWindow()會返回一個Window對象mWindow,實際指向了Window的唯一實現(xiàn)類PhoneWindow岸裙,調(diào)用setContentView()是把資源文件加載到圖中的Content部分猖败。initWindowDecorActionBar()初始化圖中的DecorActionBar。mWindow的初始化會在下一篇文章分析降允,在這里先提下恩闻,其實就是在啟動Activity的時候,調(diào)用了Activity的attach()方法剧董。
2幢尚、通過1的分析,這時把視線轉(zhuǎn)移到PhoneWindow的setContentView()中翅楼,看下面源碼:
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 {
//對布局文件進(jìn)行解析
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);//加載一個DecorView
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//獲取content部分
//省略代碼....
}
//省略代碼....
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
由于代碼量太大尉剩,所以選擇了一些關(guān)鍵性的代碼來進(jìn)行分析。接著分析毅臊,在上面的代碼中理茎,首先第一次啟動mContentParent 肯定為null,調(diào)用installDecor()管嬉,在installDecor()方法里面通過generateDecor()生成一個DecorView對象mDecor皂林,然后通過generateLayout(mDecor)獲取一個mContentParent,mContentParent也就是上圖的Content部分蚯撩。
3础倍、在2中分析了DecorView和mContentParent的初始化,下面將任務(wù)轉(zhuǎn)移到布局文件的解析mLayoutInflater.inflate(layoutResID, mContentParent)中胎挎,mLayoutInflater是一個LayoutInflater的對象沟启,進(jìn)入LayoutInflater的inflate()源碼看下:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable 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();
}
}
在上面代碼中,利用Resources的getLayout()獲取一個XmlResourceParser對象parser 犹菇,再調(diào)用inflate(),
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//這里判斷是否是merge標(biāo)簽
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, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
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);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
先遍歷屬性美浦,然后判斷是否是merge標(biāo)簽,如果是项栏,則利用rInflate解析布局浦辨。否則,createViewFromTag()創(chuàng)建一個臨時的temp沼沈;然后通過rInflateChildren()解析布局文件流酬,內(nèi)容添加到temp中,再利用root.addView()把temp添加進(jìn)來列另,root是最初調(diào)用LayoutInflater的inflate(layoutResID, mContentParent)傳進(jìn)來的mContentParent芽腾,這個時候就完成了添加。
4页衙、在第3步中摊滔,我們已經(jīng)把布局文件添加進(jìn)了content中阴绢,那么視圖是怎么顯示出來的呢?下面接著分析艰躺,在源碼中可以發(fā)現(xiàn)mContentParent是一個ViewGroup對象呻袭,即調(diào)用了ViewGroup的addView()方法:
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
在上面代碼中,requestLayout()重新請求布局腺兴,會從父View開始左电,invalidate()會讓View重繪,調(diào)用onDraw()方法页响,addViewInner()是把之前加載的View添加進(jìn)ViewGroup中篓足,并且當(dāng)前ViewGroup會被當(dāng)成一個parent,在View進(jìn)行繪制的時候闰蚕,調(diào)用parent遍歷其中的childView栈拖。不管是requestLayout()還是invalidate()最終都會調(diào)用ViewParent接口,而ViewRootImpl是ViewParent的實現(xiàn)類没陡,反過來說涩哟,也就是調(diào)用了ViewRootImpl的requestLayout()和invalidate()。
5诗鸭、在第4步中已經(jīng)找到了View的繪制入口了染簇,由于這部分內(nèi)容也比較多参滴,所以另寫了一篇文章【從源碼角度分析View的繪制流程】强岸。在前面我們分析完了View的加載過程,并沒有涉及到Window這個東西砾赔,只是把View添加進(jìn)了DecorView的content部分蝌箍。下面我們看一段Acitivity的啟動流程中的源碼【從源碼探索Activity的啟動過程】:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
//省略代碼.........
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(); //1
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); //2
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
}
上面是Activity在onResume時執(zhí)行的代碼,注意下注釋1的部分暴心,ViewManager wm = a.getWindowManager();獲取一個wm對象妓盲,a.getWindowManager()實際返回的是WindowManagerImpl對象(WindowManager(繼承了ViewManager )的實現(xiàn)類),专普,在注釋2中悯衬,那么我們就能知道實際是調(diào)用了WindowManagerImpl的addView()方法。
6檀夹、經(jīng)過第5步分析筋粗,接下來看下WindowManagerImpl的addView()方法。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
//WindowManagerGlobal類中的addView方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) { //通過window調(diào)整參數(shù)
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
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.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
根據(jù)上面代碼可以發(fā)現(xiàn)娜亿,任務(wù)轉(zhuǎn)移到了WindowManagerGlobal的addView方法中,內(nèi)部創(chuàng)建了一個ViewRootImpl對象root 蚌堵,然后調(diào)用root.setView()买决。同時也把WindowManager.LayoutParams傳到了ViewRootImpl中沛婴。
總結(jié): Activity中利用了WindowManager管理Window,而Window和View中間是通過ViewRootImpl建立關(guān)聯(lián)督赤。 流程分析到這里就結(jié)束了嘁灯,文章中并沒有深入旁仿,只是表層去探究了Activity枯冈、Window尘奏、View的關(guān)系炫加。深層次還涉及到了AMS俗孝、WMS等IPC機(jī)制赋铝。后面再另起一篇文章來分析。最后看一張圖析恋,對上面流程的簡述助隧。