自由筆記-AndroidView模塊之View加載流程分析

Activity啓動時,View加載到Window流程

 

1、Window類,是一個抽象類,Window可以理解爲一個載體,所有視圖View的載體。

2、PhoneWindow,Window的主要實現體,該類內部包含了一個DecorView對象,該DectorView對象是所有應用窗口(Activity界面)的根View。

簡而言之,PhoneWindow類是把一個FrameLayout類即DecorView對象進行一定的包裝,將它作爲應用窗口的根View,並提供一組通用的窗口操作

3、DecorView類,Activity視圖的根,該類是PhoneWindow類的內部類,其實就是一個FrameLayout,將內容呈現到PhoneWindow上面。

4、當Activity啓動之後,會將PhoneWindow綁定到當前activity,並在onCreate方法中調用setContentView方法。具體實現爲PhoneWindow裏面的setContentView方法。

5、activity啓動之後,在performLaunchActivity方法中會初始化好activity,並調用attach方法,在該方法中,初始化好了window:mWindow = new PhoneWindow(this, window);

並且設置好windowManager。

mWindow.setWindowManager(

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),

mToken, mComponent.flattenToString(),

(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

該方法會拿到WMS遠程服務的代理,並且生成一個本地windowManager:WindowManagerImpl.而WindowManagerImpl中用來實現window與View操作的類是WindowManagerGlobal

 

6、在performLaunchActivity最後會調用activity的onCreate方法,這裏會調用setContentView來設定用戶需要顯示的內容

public void setContentView(@LayoutRes int layoutResID) {

getWindow().setContentView(layoutResID);//①調用PhoneWindow的setContentView的方法

initWindowDecorActionBar();

}

 

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();//②初始化DecorView,即整個activity的根view

}

mLayoutInflater.inflate(layoutResID, mContentParent);//⑥最後將我們需要設定的佈局文件初始化出來加入到mContentParent中

}

 

private void installDecor() {

if (mDecor == null) {

mDecor = generateDecor();//③生成一個DecorView(繼承的FrameLayout)

mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

mDecor.setIsRootNamespace(true);

if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {

mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);

}

}

if (mContentParent == null) {

mContentParent = generateLayout(mDecor);//④初始化DecorView的佈局文件,拿到給用戶定義content內容的ViewGroup。

}

protected ViewGroup generateLayout(DecorView decor) {//⑤

View in = mLayoutInflater.inflate(layoutResource, null);

decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

mContentRoot = (ViewGroup) in;

}

7、內容設定好之後,會在handleResumeActivity方法裏面,在activity的狀態全部初始化完成,走完onResume方法之後,通過window將之前setContentView

初始化好的View添加到窗口上

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 (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 && !a.mWindowAdded) {

a.mWindowAdded = true;

wm.addView(decor, l);

}

可以看到,最終調用了WindowManager的addView方法,這個方法最終會走到WindowManagerGlobal的addView方法中

 

8、在WindowManagerGlobal的addView主要做了幾件事:

root = new ViewRootImpl(view.getContext(), display);//新建ViewRoot,通過windowManager加入的View都會有一個自己的ViewRoot

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);//將參數加入到對應的集合中,方便以後管理

root.setView(view, wparams, panelParentView);調用root將View繪製到窗口上

 

9、setView中最關鍵的2個方法:

1、requestLayout,該方法會觸發整個View樹的繪製。會調用scheduleTraversals,觸發任務mTraversalRunnable調用 doTraversal()最後調用performTraversals方法

在該方法中調動這3個方法來觸發以下3個流程來完成view的繪製

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

performLayout(lp, desiredWindowWidth, desiredWindowHeight);

performDraw();

2、設定DecorView的parent爲ViewRoot: view.assignParent(this);這樣,在整個View樹中,無論哪個子View調用了requestLayout方法,都會最終

找到ViewRoot的requestLayout方法去執行重新繪製界面的流程。代碼如下:

if (mParent != null && !mParent.isLayoutRequested()) {

mParent.requestLayout();

}

 

10、View繪製完成之後,會在handleResumeActivity方法裏面調用Activity的makeVisible方法 ,顯示我們剛纔創建的mDecor視圖族。

void makeVisible() {

if (!mWindowAdded) {

ViewManager wm = getWindowManager();

wm.addView(mDecor, getWindow().getAttributes());

mWindowAdded = true;

}

mDecor.setVisibility(View.VISIBLE);

}

可以看到如果沒有加入到window中,會先加入到window中,走上面剛剛分析過的流程,然後設置爲顯示狀態。

View實例化過程:

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {

LayoutInflater factory = LayoutInflater.from(context);

return factory.inflate(resource, root);

}

factory.inflate(resource, root);

inflate(resource, root, root != null);

 

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

 

最終都會調用上面這個方法。參數說明:

resoure,就是佈局文件的資源id

root,所屬父佈局,如果root爲null,那麼該View會沒有layoutparam參數。參考源碼。在RecycleView或者listView這種有時候需要重新設定子item佈局參數的時候需要注意

attachToRoot,是否加入到root中,如果設定爲true,則實例化完之後自動添加進入root中,如果false,則不是。在RecycleView或者listView中,由於它們會自動

將child加入,所以使用這2個控件的時候,實例化子item的時候一定要傳入false。否則會報錯java.lang.IllegalStateException: The specified child already

has a parent. You must call removeView() on the child's parent first.

 

 

<merge />只能作爲XML佈局的根標籤使用。當Inflate以<merge />開頭的佈局文件時,必須指定一個父ViewGroup,並且必須設定attachToRoot爲true。

 

子線程更新UI:子線程更新UI必須要在子線程中有自己的ViewRoot,因爲在ViewRoot初始化的時候會拿到當前的線程對象,而在繪製View的時候,會執行CheckThread方法會檢查當前線程是否是創建ViewRoot的這個線程。如果不是,那麼會拋出異常,如果是,那麼不會。所以我們在主線程創建UI的時候,默認的ViewRoot中的當前線程對象是UI線程,在其他線程更新UI就會報錯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章