自由笔记-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.

 

 

只能作为XML布局的根标签使用。当Inflate以开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。

 

子线程更新UI:子线程更新UI必须要在子线程中有自己的ViewRoot,因为在ViewRoot初始化的时候会拿到当前的线程对象,而在绘制View的时候,会执行CheckThread方法会检查当前线程是否是创建ViewRoot的这个线程。如果不是,那么会抛出异常,如果是,那么不会。所以我们在主线程创建UI的时候,默认的ViewRoot中的当前线程对象是UI线程,在其他线程更新UI就会报错。

你可能感兴趣的:(Android技术)