美文网首页AndroidAndroid开发经验谈Android开发
Android 由setContentView探究Activit

Android 由setContentView探究Activit

作者: linda_zhou | 来源:发表于2017-08-24 08:57 被阅读243次

    前言

    当我们打开一个activity需要显示内容的时候,只需要在onCreate方法中执行setContentView方法,一行代码搞定,很简单,有么有。但是,有没有想过setConentView方法内部,执行了那些操作,Window、DecorView、ViewRootImpl是怎么回事,本文就来一步步分析其内部工作流程。源码基于Android API 21。

    Activity#setContentView

        public void setContentView(int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    
        ...
    
        public Window getWindow() {
            return mWindow;
        }
    

    可以看到,Activity#setContentView内部调用了mWindow中的setContentView方法,那这个mWindow是什么呢?可以看出它是Window类型的。先来看看mWindow是在哪里创建的,通过源码可以发现在Activity#attach中对其进行了赋值操作(注:在Activity启动过程中,会执行ActivityThread#performLaunchActivity方法,在这个方法中会调用Activity#attach方法)。

        final void attach(Context context, ActivityThread aThread,
                Instrumentation instr, IBinder token, int ident,
                Application application, Intent intent, ActivityInfo info,
                CharSequence title, Activity parent, String id,
                NonConfigurationInstances lastNonConfigurationInstances,
                Configuration config, IVoiceInteractor voiceInteractor) {
    
            ...
            //在这里创建Activity所属的Window对象并赋值给mWindow
            mWindow = PolicyManager.makeNewWindow(this);
            mWindow.setCallback(this);//Activity实现了Window的Callback接口,这里给Window注册监听
            mWindow.setOnWindowDismissedCallback(this);
            mWindow.getLayoutInflater().setPrivateFactory(this);
            if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
                mWindow.setSoftInputMode(info.softInputMode);
            }
            if (info.uiOptions != 0) {
                mWindow.setUiOptions(info.uiOptions);
            }
            ...
    

    创建Window并初始化DecorView

    接着上文看下PolicyManager#makeNewWindow

        public static Window makeNewWindow(Context context) {
            // this will likely crash somewhere beyond so we log it.
            Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
                    "Call to PolicyManager.makeNewWindow is not supported", null);
            return null;
        }
    

    发现PolicyManager#makeNewWindow方法并没有具体实现,并且PolicyManager中的方法全在接口IPolicy中声明了。

    public interface IPolicy {
        public Window makeNewWindow(Context context);
    
        public LayoutInflater makeNewLayoutInflater(Context context);
    
        public WindowManagerPolicy makeNewWindowManager();
    
        public FallbackEventHandler makeNewFallbackEventHandler(Context context);
    }
    

    其真正的实现是在Policy类中,如下:

        public Window makeNewWindow(Context context) {
            return new PhoneWindow(context);
        }
    

    可以看到,最终是实例化了PhoneWindow,PhoneWindow是Window的唯一子类。
    至此,Window的创建过程就结束了,接着看PhoneWindow#setContentView方法。

        @Override
        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.
    
            //重点在这里,mContentParent为null,执行installDecor方法。
            if (mContentParent == null) {
                installDecor();//初始化DecorView
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                        getContext());
                transitionTo(newScene);
            } else {
                //这个方法内部会把我们的布局文件的内容添加mContentParent中。
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();//执行回调
            }
        }
    

    这个方法中主要两个工作:1. 如果mContentParent为null,则执行installDecor方法,即初始化DecorView;2. 通过mLayoutInflater.inflate(layoutResID, mContentParent将我们的布局内部添加到mContentParent;3. 当屏幕的内容发生改变时,执行回调方法onContentChanged。那么这个mContentParent是指什么呢?这里先保留疑问,我们接下来就会解释。来看下installDecor方法做了什么工作。

    private void installDecor() {
            //如果DecorView为null,则创建DecorView
            if (mDecor == null) {
                mDecor = generateDecor();
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            }
    
            ...
    
            if (mContentParent == null) {
                初始化DecorView的布局结构,获取mContentParent并返回。
                mContentParent = generateLayout(mDecor);
                ...
            } 
    
            ...
    
        }
    
        protected DecorView generateDecor() {
            return new DecorView(getContext(), -1);
        }
    
    protected ViewGroup generateLayout(DecorView decor) {
            // Apply data from current theme.
          
            TypedArray a = getWindowStyle();
            ...
            //获取一堆主题中设置的属性进行相关设置(好长一堆~~)
            if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
                requestFeature(FEATURE_NO_TITLE);
            } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
                // Don't allow an action bar if there is no title.
                requestFeature(FEATURE_ACTION_BAR);
            }
    
            if (...) {
                ...
            }
            ...
         
            // Inflate the window decor.
            //根据不同的窗口特征,layoutResource对应不同的布局文件,用来填充DecorView(又是很长一堆~~)。
            int layoutResource;
            int features = getLocalFeatures();
            // System.out.println("Features: 0x" + Integer.toHexString(features));
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = R.layout.screen_swipe_dismiss;
            } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
                if (mIsFloating) {
                    TypedValue res = new TypedValue();
                    getContext().getTheme().resolveAttribute(
                            R.attr.dialogTitleIconsDecorLayout, res, true);
                    layoutResource = res.resourceId;
                } else {
                    layoutResource = R.layout.screen_title_icons;
                }
                // XXX Remove this once action bar supports these features.
                removeFeature(FEATURE_ACTION_BAR);
                // System.out.println("Title Icons!");
            } 
            
            ...
            
            //重点在这里
            //把对应的布局文件填充并加载到DecorView,初始化DecorView的结构
            View in = mLayoutInflater.inflate(layoutResource, null);
            decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            mContentRoot = (ViewGroup) in;
            //看到contentParent了吧,它对应了DecorView中id为com.android.internal.R.id.content的ViewGroup。
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            if (contentParent == null) {
                throw new RuntimeException("Window couldn't find content container view");
            }
            ...
    
            return contentParent;
        }
    

    这个主要做了三件事:1.根据主题中属性进行一些相关设置2.根据不同的窗口特征,获取对应的布局文件,用来初始化DecorView的布局结构。3.获取contentParent,contentParent对应了DecorView布局文件中id为com.android.internal.R.id.content的ViewGroup,它其实是一个FrameLayout。如图所示:

    DecorView结构图.png

    再回到上文PhoneWindow#setContentView方法中,当获取到mContentParent后,还会执行mLayoutInflater.inflate(layoutResID, mContentParent)将我们写的xml布局文件加载到mContentParent,至于加载细节我们这里就不做分析了。

    小结:至此,我们已经实例化了Window,初始化了DecorView布局结构,并且把我们布局文件加载到了mContentParent中。不过DecorView并没有显示出来,因为View并不能单独存在,必须依附于Window。

    在Activity在启动流程中(Activity的启动流程很复杂,我们这里不做具体分析),当执行了ActivityThread#performLaunchActivity方法后还会执行ActivityThread#handleResumeActivity方法,在这个方法中首先会调用Activity的onResume方法,接着调用Activity的makeVisible方法。

        void makeVisible() {
            if (!mWindowAdded) {
                ViewManager wm = getWindowManager();//获取WindowManager
                wm.addView(mDecor, getWindow().getAttributes());//通过WindowManager完成Window的添加过程
                mWindowAdded = true;
            }
            mDecor.setVisibility(View.VISIBLE);//让DecorView可见
        }
    

    此方法将完成Window的添加过程,以及让DecorView显示出来,至此Activity界面加载流程就结束了

    将DecorView添加到Window

    接着上文,看看DecorView是如何添加到Window中的。Window其实是个抽象的概念,它并不是一个实体。我们可以把Window理解成一种抽象的功能集合,每个Window都关联一个View和ViewRootImpl。

    public interface ViewManager
    {   
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }
    

    发现ViewManager是一个接口,WindowManager继承了ViewManager,也是一个接口。看它的实现类WindowManagerImpl中的addView方法。

        @Override
        public void addView(View view, ViewGroup.LayoutParams params) {
            mGlobal.addView(view, params, mDisplay, mParentWindow);
        }
    

    发现WindowManagerImpl#addView并没有实现具体细节,而是交给了WindowManagerGlobal中的addView去处理。

    public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
            ...
    
            ViewRootImpl root;
    
            synchronized (mLock) {
                ...
                //实例化ViewRootImpl,它是WindowManager和DecorView的纽带
                root = new ViewRootImpl(view.getContext(), display);
    
                view.setLayoutParams(wparams);
    
                mViews.add(view);//保存所有Window对应的View
                mRoots.add(root);//保存所有Window对应的ViewRootImpl
                mParams.add(wparams);//保存所有Window对应的布局参数
            }
    
            // 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方法

    /**
         * We have one child
         */
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    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.
                    //内部最终会调用performTraversals方法开启View的绘制流程。
                    requestLayout();
                    if ((mWindowAttributes.inputFeatures
                            & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                        mInputChannel = new InputChannel();
                    }
                    try {
                        mOrigWindowType = mWindowAttributes.type;
                        mAttachInfo.mRecomputeGlobalAttributes = true;
                        collectViewAttributes();
                        //通过IWindowSession来完成Window的添加过程
                        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mInputChannel);
                    } catch (RemoteException e) {
                        mAdded = false;
                        mView = null;
                        mAttachInfo.mRootView = null;
                        mInputChannel = null;
                        mFallbackEventHandler.setView(null);
                        unscheduleTraversals();
                        setAccessibilityFocus(null, null);
                        throw new RuntimeException("Adding window failed", e);
                    } finally {
                        if (restore) {
                            attrs.restore();
                        }
                    }
                    ...//省略一大波代码
             }
    

    这个方法很长,我们只看与本文主流程相关部分。有两个重要的地方。

    1. 通过requestLayout方法开启顶级View的测绘。
        @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
    
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
            }
        }
    
        final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
        final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
        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;
                }
            }
        }
    
        private void performTraversals() {
                ...
                //执行测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                ...
                //执行布局
                performLayout(lp, desiredWindowWidth, desiredWindowHeight);
                ...
                //执行绘制
                performDraw();
        }
    

    performTraversals方法依次会执行performMeasureperformLayout以及performDraw来完成对顶级View的测量、布局、绘制。performMeasure方法中会调用measure方法,measure方法中又会调用onMeasure方法,在onMeasure方法中会完成子View的measure过程,measure过程就从父View传到了子View。子View又会重复父View的动作,如此反复,就完成了整个View树的测量过程。performLayout以及performDraw方法与performMeasure类似。可以说View的三大工作流程是performTraversals开始的

    1. 通过WindowSession完成window的添加过程。

    先来看下mWindowSession的创建过程

       public ViewRootImpl(Context context, Display display) {
            mWindowSession = WindowManagerGlobal.getWindowSession();//获取WindowSession
       }
    
    public static IWindowSession getWindowSession() {
            synchronized (WindowManagerGlobal.class) {
                if (sWindowSession == null) {
                    try {
                        InputMethodManager imm = InputMethodManager.getInstance();
                        IWindowManager windowManager = getWindowManagerService();
                        sWindowSession = windowManager.openSession(
                                new IWindowSessionCallback.Stub() {
                                    @Override
                                    public void onAnimatorScaleChanged(float scale) {
                                        ValueAnimator.setDurationScale(scale);
                                    }
                                },
                                imm.getClient(), imm.getInputContext());
                        ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale());
                    } catch (RemoteException e) {
                        Log.e(TAG, "Failed to open window session", e);
                    }
                }
                return sWindowSession;
            }
        }
    
        public static IWindowManager getWindowManagerService() {
            synchronized (WindowManagerGlobal.class) {
                if (sWindowManagerService == null) {
                    sWindowManagerService = IWindowManager.Stub.asInterface(
                            ServiceManager.getService("window"));
                }
                return sWindowManagerService;
            }
        }
    

    mWindowSession类型是IWindowManager,它是一个Binder对象,可见Window的添加过程是个IPC过程。

    openSession方法是在WindowManagerService具体实现的

        public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
                IInputContext inputContext) {
            if (client == null) throw new IllegalArgumentException("null client");
            if (inputContext == null) throw new IllegalArgumentException("null inputContext");
            Session session = new Session(this, callback, client, inputContext);
            return session;
        }
    

    在WindowManagerService#openSession实例化了Session并返回。Session是IWindowSession的实现类。addToDisplay方法也是在Session中具体实现的。

        @Override
        public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
                int viewVisibility, int displayId, Rect outContentInsets,
                InputChannel outInputChannel) {
            return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                    outContentInsets, outInputChannel);
        }
    

    在这个方法内部又调用了WindowManagerService#addWindow方法,最终完成了Window的添加过程。其具体细节不再说了。。。

    希望能对您有所帮助,若文中有错误或表述不当的地方还望指出,互相交流,共同成长!

    相关文章:
    Android View 测量流程(Measure)源码解析
    Android View 布局流程(Layout)源码解析
    Android View 绘制流程(Draw)源码解析

    相关文章

      网友评论

        本文标题:Android 由setContentView探究Activit

        本文链接:https://www.haomeiwen.com/subject/ijccdxtx.html