美文网首页Android开发Android技术知识Android开发经验谈
从源码角度分析Activity、Window、View的关系

从源码角度分析Activity、Window、View的关系

作者: Gillben | 来源:发表于2018-04-18 12:27 被阅读0次

    View依附于Window,而Activity负责管理Window。为什么会产生这样的关系呢?文章围绕这个问题。将会从Activity加载View的整个流程去分析Activity、Window、View三者之间的关系。

    1、在启动Activity时,会在onCreate()方法中调用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();
        }
    

    上述代码中,可以发现,直接把任务转移到Activity的setContentView()方法中,调用了getWindow().setContentView()加载我们的布局资源文件,然后initWindowDecorActionBar()初始化ActionBar,插个画:


    未命名文件.png

    getWindow()会返回一个Window对象mWindow,实际指向了Window的唯一实现类PhoneWindow,调用setContentView()是把资源文件加载到图中的Content部分。initWindowDecorActionBar()初始化图中的DecorActionBar。mWindow的初始化会在下一篇文章分析,在这里先提下,其实就是在启动Activity的时候,调用了Activity的attach()方法。

    2、通过1的分析,这时把视线转移到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 {
                //对布局文件进行解析
                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());
        }
    

    由于代码量太大,所以选择了一些关键性的代码来进行分析。接着分析,在上面的代码中,首先第一次启动mContentParent 肯定为null,调用installDecor(),在installDecor()方法里面通过generateDecor()生成一个DecorView对象mDecor,然后通过generateLayout(mDecor)获取一个mContentParent,mContentParent也就是上图的Content部分。

    3、在2中分析了DecorView和mContentParent的初始化,下面将任务转移到布局文件的解析mLayoutInflater.inflate(layoutResID, mContentParent)中,mLayoutInflater是一个LayoutInflater的对象,进入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 ,再调用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标签
                    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标签,如果是,则利用rInflate解析布局。否则,createViewFromTag()创建一个临时的temp;然后通过rInflateChildren()解析布局文件,内容添加到temp中,再利用root.addView()把temp添加进来,root是最初调用LayoutInflater的inflate(layoutResID, mContentParent)传进来的mContentParent,这个时候就完成了添加。

    4、在第3步中,我们已经把布局文件添加进了content中,那么视图是怎么显示出来的呢?下面接着分析,在源码中可以发现mContentParent是一个ViewGroup对象,即调用了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重绘,调用onDraw()方法,addViewInner()是把之前加载的View添加进ViewGroup中,并且当前ViewGroup会被当成一个parent,在View进行绘制的时候,调用parent遍历其中的childView。不管是requestLayout()还是invalidate()最终都会调用ViewParent接口,而ViewRootImpl是ViewParent的实现类,反过来说,也就是调用了ViewRootImpl的requestLayout()和invalidate()。

    5、在第4步中已经找到了View的绘制入口了,由于这部分内容也比较多,所以另写了一篇文章【从源码角度分析View的绘制流程】。在前面我们分析完了View的加载过程,并没有涉及到Window这个东西,只是把View添加进了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时执行的代码,注意下注释1的部分,ViewManager wm = a.getWindowManager();获取一个wm对象,a.getWindowManager()实际返回的是WindowManagerImpl对象(WindowManager(继承了ViewManager )的实现类),,在注释2中,那么我们就能知道实际是调用了WindowManagerImpl的addView()方法。

    6、经过第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调整参数
                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;
                }
            }
        }
    
    

    根据上面代码可以发现,任务转移到了WindowManagerGlobal的addView方法中,内部创建了一个ViewRootImpl对象root ,然后调用root.setView()。同时也把WindowManager.LayoutParams传到了ViewRootImpl中。

    总结: Activity中利用了WindowManager管理Window,而Window和View中间是通过ViewRootImpl建立关联。 流程分析到这里就结束了,文章中并没有深入,只是表层去探究了Activity、Window、View的关系。深层次还涉及到了AMS、WMS等IPC机制。后面再另起一篇文章来分析。最后看一张图,对上面流程的简述。

    未命名文件.png

    相关文章

      网友评论

        本文标题:从源码角度分析Activity、Window、View的关系

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