DecorView如何添加到Window

来源:互联网 发布:江苏运时数据招聘 编辑:程序博客网 时间:2024/06/11 21:04

前几篇文章讲了
从setContentView开始,了解view的加载过程
LayoutInflater 是怎么把xml添加到decorview?
今天来看一下

DecorView如何添加到Window

1,首先要了解Activity的启动过程

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {  ···省略若干行        Activity a = performLaunchActivity(r, customIntent);   ···省略若干行       }

Activity是通过ActivityThead启动的,首先会走handleLaunchActivity方法,之后会走 performLaunchActivity(r, customIntent)方法,如下

performLaunchActivity(xxxxxx){Activity activity = null;        try {            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();            activity = mInstrumentation.newActivity(                    cl, component.getClassName(), r.intent);            ···省略若干行            if (activity != null) {                Context appContext = createBaseContextForActivity(r, activity);                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());                Configuration config = new Configuration(mCompatConfiguration);                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "                        + r.activityInfo.name + " with config " + config);              //调用attach方法                activity.attach(appContext, this, getInstrumentation(), r.token,                        r.ident, app, r.intent, r.activityInfo, title, r.parent,                        r.embeddedID, r.lastNonConfigurationInstances, config,                        r.referrer, r.voiceInteractor);                         ···省略若干行}

看performLaunchActivity方法,在这个方法里会通过反射来拿到我们当前的Activity,然后会调用attach方法,之后再看attach方法;

final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,        //在这里初始化了PhoneWindow。。        mWindow = new PhoneWindow(this);        mWindow.setCallback(this);

再回到performLaunchActivity方法,当attch完成之后会走callActivityOnCreate方法。

performLaunchActivity。。省略若干行if (r.isPersistable()) {       //斤如callActivityOnCreate方法,它会执行activity.performCreate方法     mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);                } else {                    mInstrumentation.callActivityOnCreate(activity, r.state);                }

也就是说Activity的生命周期,执行顺序,调用流程都会在ActivityThread这个类中执行,例如callActivityOnRestoreInstanceState,callActivityOnPostCreate等。

我们再回到handleLaunchActivity,当它走完performLaunchActivity方法之后,会调用handleResumeActivity,callActivityOnPause等方法
如下,

{//点进去最终会是callActivityOnResume这个方法来调用OnResume方法handleResumeActivity(r.token, false, r.isForward,                    !r.activity.mFinished && !r.startsNotResumed);            if (!r.activity.mFinished && r.startsNotResumed) {                            try {                    r.activity.mCalled = false;                    mInstrumentation.callActivityOnPause(r.activity);}if (r.window == null && !a.mFinished && willBeVisible) {                r.window = r.activity.getWindow();                //获取到DecorView                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;                    //把decor添加到WindowManager中                    wm.addView(decor, l);                }

从上面可以看到当走完handleResumeActivity方法后,通过View decor = r.window.getDecorView();获取到DecorView并设置为隐藏。然后获取WindowManager,然后把decor添加到WindowManager中;
之后我们再进入WindowManager中,发现它是一个接口。通过搜索发现它的实现类为WindowManagerImpl,上面代码中的 wm.addView(decor, l) 这个方法就是走的WindowManagerImpl中的addView方法。

我们进WindowManagerImpl这个类看看者方法

@Override    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {        applyDefaultToken(params);        //这里其实是WindowManagerGlobal 实例的addView方法        //这里的view就是我们传进来的DecorView        mGlobal.addView(view, params, mDisplay, mParentWindow);    }

进入 mGlobal.addView方法

 public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) { 。。。省略 //ViewRootImpl 这个类很重要,绘制流程基本都是在这个类完成的。        ViewRootImpl root;        View panelParentView = null;         。。。省略             //这里new一个ViewRootImpl,绘制流程基本都是在这个类完成的           root = new ViewRootImpl(view.getContext(), display);            view.setLayoutParams(wparams);        //这是三个List,把他们添加到一个list中去,也就是把他们都保存到这个类中,这个类是管理root ,和decorView的。            mViews.add(view);            mRoots.add(root);            mParams.add(wparams);        }        // do this last because it fires off messages to start doing things        try {        //这里调用setView把我们的decorView和一些参数传进去            root.setView(view, wparams, panelParentView);        } catch (RuntimeException e) {          }

//以上就是Activity的启动过程中生成phonewindow,并加载DecorView,之后发起绘制的基本过程。

进一步深入了解,我们再进入setView()方法,首先把传进去的DecorView赋值给mView,然后调用mWindowSession.addToDisplay这个方法把我们的DecorView显示出来。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {        synchronized (this) {            if (mView == null) {            //把传进去的DecorView赋值给mView                mView = view;                。。省略 者其中是一些动画的处理和输入法的处理等                // Schedule the first layout -before- adding to the window                // manager, to make sure we do the relayout before receiving                // any other events from the system.                requestLayout();//这个很常见                。。省略                 try {                 //下面是发起一个跨进程的通信,通过方法名可以猜测,是用来显示的                    mOrigWindowType = mWindowAttributes.type;                    mAttachInfo.mRecomputeGlobalAttributes = true;                    collectViewAttributes();                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                            getHostVisibility(), mDisplay.getDisplayId(),                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,                            mAttachInfo.mOutsets, mInputChannel);                } catch (RemoteException e) {           }                 }    }    ...省略    //关键代码     //DecorView把WindowManagerImpl设置为顶层ViewGroup     view.assignParent(this);

我们再进入这个方法view.assignParent(this);这个方法是View中的方法

//就送给我们的View分配Parentvoid 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");        }    }

讲一下requestLayout这个方法
首先,View.requestLayout—>其实是调用View类的mParent .requestLayout–>最终会到DecorView的requestLayout,在这个里面会调用ViewRootImpl的requestLayout。为什么会最终会调用DecorView的requestLayout,是因为在setView()方法的时候利用assignParent(ViewParent parent)设置了DecorView的根部局。所以这个mParent 其实就是ViewRootImpl。 其次,走到ViewRootImpl.requestLayout后就会重新进行绘制流程,代码如下

 @Override    public void requestLayout() {        if (!mHandlingLayoutInLayoutRequest) {            checkThread();            mLayoutRequested = true;            scheduleTraversals(); //遍历操作        }    }

scheduleTraversals()方法

void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            if (!mUnbufferedInputDispatch) {                scheduleConsumeBatchedInput();            }            notifyRendererOfFramePending();            pokeDrawLockIfNeeded();        }    }

这里有一个线程,看一下mTraversalRunnable,它会不断的走 doTraversal()方法

final class TraversalRunnable implements Runnable {        @Override        public void run() {            doTraversal();        }    }

再看一下 doTraversal()方法,里面重要的是 performTraversals()方法,可以猜测这个就是发起重绘的方法。

void doTraversal() {       。。省略            performTraversals();       。。省略    }

继续看 performTraversals()

 private void performTraversals() {        // 这个就是我们的DecorView        final View host = mView;        //...省略一些操作,大家可以通过变量名来猜测它是干什么的        //顶层视图DecorView所需要窗口的宽度和高度,因为我们必须要知道父容器的宽高才能测量子View的宽高。         int desiredWindowWidth;        int desiredWindowHeight;        mDisplay.getRealSize(size);        desiredWindowWidth = size.x;        desiredWindowHeight = size.y;        else {        //这这里的宽高是是手机屏幕的宽高,见下面代码                DisplayMetrics packageMetrics =                    mView.getContext().getResources().getDisplayMetrics();                desiredWindowWidth = packageMetrics.widthPixels;                desiredWindowHeight = packageMetrics.heightPixels;            }        。。。省略,大部分为初始化操作         // Ask host how big it wants to be         //重点方法,通过注释可以猜测,这里是测量布局所占用空间。   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 。。。省略 //   performLayout(lp, desiredWindowWidth, desiredWindowHeight);    。。。省略//   performDraw();}

进入 performMeasure方法看一看,它是会调用DecorView的measure方法,DecorView是继承View的,所以实际调用的是View的measure方法。所以这些测量,layout,draw都是再ViewRootImpl发起的。

 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);        }    }

//以上就是Activity的启动过程中生成phonewindow,并加载DecorView,之后发起绘制的过程。

总结:DecorView如何添加到Window?
看下面绘制流程图,可以发现最终调用了ViewRootImpl.setView,在setview方法里调用了view.assignParent(this);,将Decorview的mParent设置成ViewRootImpl
这也就是为什么View调用requestLayout方法的时候最终会走到ViewRootImpl的requestLayout
这里写图片描述
这篇博客也帮大家找到了UI绘制流程的起始点是在生什么地方,也就是下面三个方法:///测量–performMeasure()// 摆放布局–performLayout()
// 绘制–performDraw()