美文网首页
Window和WindowManager

Window和WindowManager

作者: Mr_dvbkhm | 来源:发表于2019-06-03 22:55 被阅读0次
    1. Window的内部机制

    Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系
    因此Window并不是实际存在的,它是以View的形式存在

    1.1 Window的添加过程

    Window的添加过程需要通过WindowManager(继承自ViewManager)的addView来实现,WindowManager是一个接口,它的真正实现是
    WindowManagerImpl类,WindowManagerImpl的路径:android.view.WindowManagerImpl
    我们来看看WindowManagerImpl 是怎么实现那三个管理view 生命周期的方法的:

    @Override
        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
        }
    
        @Override
        public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.updateViewLayout(view, params);
        }
    
        ...
    
        @Override
        public void removeView(View view) {
            mGlobal.removeView(view, false);
        }
    

    可以发现,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal,
    我们来看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) {
                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.
                synchronized (mLock) {
                    final int index = findViewLocked(view, false);
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                }
                throw e;
            }
        }
    

    这里可以分析为以下几个方法:

    1. 检查参数是否合法,如果是子Window那么还需要调整一些布局参数
    2. 创建ViewRootImpl并将View添加到列表中
    3. 通过ViewRootImpl的setView方法来更新界面并完成Window的添加过程

    我们已经知道,View的绘制过程是由ViewRootImpl来完成的,这里当然也不例外,在setView内部会通过requestLayout来完
    成异步刷新请求。在下面的代码中,scheduleTraversals实际是View绘制的入口,ViewRootImpl中:

    /**
         * We have one child
         */
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    mView = view;
    
                    mAttachInfo.mDisplayState = mDisplay.getState();
                    mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
    
                    ...
    
                    // 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();
                    if ((mWindowAttributes.inputFeatures
                            & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                        mInputChannel = new InputChannel();
                    }
                    mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                            & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                    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) {
                        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();
                        }
                    }
    
                    ...
                }
            }
        }
    
    @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
    

    接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中,mWindowSession的类型是IWindowSession,
    它是一个Binder对象,真正的实现类是Session,也就是Window的添加过程是一次IPC调用,Session内部会通过
    WindowManagerService来实现Window的添加

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

    这样,Window的添加请求就交给WindowManagerService去处理了,进一步的分析逻辑过于复杂,这里不做描述。

    1.2 Window的删除过程

    和添加过程一样,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGlobal来实现的

    public void removeView(View view, boolean immediate) {
            if (view == null) {
                throw new IllegalArgumentException("view must not be null");
            }
    
            synchronized (mLock) {
                int index = findViewLocked(view, true);
                View curView = mRoots.get(index).getView();
                removeViewLocked(index, immediate);
                if (curView == view) {
                    return;
                }
    
                throw new IllegalStateException("Calling with view " + view
                        + " but the ViewAncestor is attached to " + curView);
            }
        }
    
    1.3 Window的更新过程

    我们继续看WindowManagerGlobal 中的updateViewLayout():

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
            if (view == null) {
                throw new IllegalArgumentException("view must not be null");
            }
            if (!(params instanceof WindowManager.LayoutParams)) {
                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
            }
    
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    
            view.setLayoutParams(wparams);
    
            synchronized (mLock) {
                int index = findViewLocked(view, true);
                ViewRootImpl root = mRoots.get(index);
                mParams.remove(index);
                mParams.add(index, wparams);
                root.setLayoutParams(wparams, false);
            }
        }
    

    updateViewLayout方法做的事情就比较简单了,首先它需要更新View的LayoutParams并替换掉老的LayoutParams,接着再更新
    ViewRootImpl中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams方法来实现的。在ViewRootImpl中会通过
    scheduleTraversals方法来对View重新布局,包括测量、布局、重绘这三个过程。除了View本身的重绘以外,ViewRootImpl还会通
    过WindowSession来更新Window的视图,这个过程最终是由WindowManagerService的relayoutWindow()来具体实现的,
    它同样是一个IPC过程.

    2. Window的创建过程

    View是Android中的视图的呈现方式,Android中可以提供视图的地方有Activity、Dialog、Toast,还有一些依托Window而实现的视图,
    比如PopUpWindow、菜单,它们也是视图。但是View不能单独存在,它必须附着在Window这个抽象的概念上面,接下来我们分析Activity
    的视图是怎么附属在Window上的

    2.1 Activity的Window创建过程

    我们创建一个与用户交互的Activity时,都需要setContentView(int ResourceID) ,我们创建的activity 都继承自Activity,
    看Activity的setContentView方法

    public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    

    也就是说Activity将具体实现交给了Window处理,而Window的具体实现是PhoneWindow,文件路径是:
    com.android.internal.policy.PhoneWindow

    @Override
        public void setContentView(View view, ViewGroup.LayoutParams params) {
            // 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)) {
                view.setLayoutParams(params);
                final Scene newScene = new Scene(mContentParent, view);
                transitionTo(newScene);
            } else {
                mContentParent.addView(view, params);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    PhoneWindow的setContentView方法大致遵循如下几个步骤:

    1. DecorView是Activity中的顶级View,如果没有DecorView,那么就创建它
    2. 将View添加到DecorView的mContentParent中
    3. 回调Activity的onContentChanged方法通知Activity视图已经发生改变

    到这个时候DecorView还没有被WindowManager正式添加到Window中,也就是说,对用户来说页面还不可见,ActivityThread继续
    管理着activity的生命周期,在ActivityThread的handleResumeActivity方法中,首先会调用Activity的onResume方法,
    接着会调用Activity的makeVisible(),到这里Activity的视图才能被用户看到

    void makeVisible() {
            if (!mWindowAdded) {
                ViewManager wm = getWindowManager();
                wm.addView(mDecor, getWindow().getAttributes());
                mWindowAdded = true;
            }
            mDecor.setVisibility(View.VISIBLE);
        }
    
    2.2 Dialog的Window创建过程

    Dialog的Window的创建过程和Activity类似,我们看Dialog 的构造函数

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
            if (createContextThemeWrapper) {
                if (themeResId == 0) {
                    final TypedValue outValue = new TypedValue();
                    context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                    themeResId = outValue.resourceId;
                }
                mContext = new ContextThemeWrapper(context, themeResId);
            } else {
                mContext = context;
            }
    
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
            final Window w = new PhoneWindow(mContext);
            mWindow = w;
            w.setCallback(this);
            w.setOnWindowDismissedCallback(this);
            w.setWindowManager(mWindowManager, null, null);
            w.setGravity(Gravity.CENTER);
    
            mListenersHandler = new ListenersHandler(this);
        }
    

    子类(自定义的Dialog)调用Dialog的setContentView()将布局id传入,
    Dialog 设置布局源码:
    public void setContentView(@NonNull View view) {
    mWindow.setContentView(view);
    }
    布局管理依然是交给PhoneWindow,接下来的步骤和activity 是类似的

    2.3 Toast的Window创建过程

    Toast属于系统Window,和Dialog不同,它的工作过程就稍显复杂
    我们看Toast 的show 方法:

    public void show() {
            if (mNextView == null) {
                throw new RuntimeException("setView must have been called");
            }
    
            INotificationManager service = getService();
            String pkg = mContext.getOpPackageName();
            TN tn = mTN;
            tn.mNextView = mNextView;
    
            try {
                service.enqueueToast(pkg, tn, mDuration);
            } catch (RemoteException e) {
                // Empty
            }
        }
    

    显示和隐藏Toast都需要通过NMS(NotificationManagerService)来实现,由于NMS运行在,系统的进程中,所以只能通过远程调用的
    方式来显示和隐藏Toast,需要注意的是TN这个类,它是一个Binder类,在Toast和NMS进行IPC的过程中,当NMS处理Toast的显示或隐
    藏请求时会跨进程回调TN中的方法,这个时候由于TN运行在Binder线程池中,所以需要通过Handler将其切换到当前线程中。这里的当前
    线程是指发送Toast请求所在的线程。注意,由于这里使用了Handler,所以这意味着Toast无法在没有Looper的线程中弹出,这是
    因为Handler需要使用Looper才能完成切换线程的功能.

    相关文章

      网友评论

          本文标题:Window和WindowManager

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