zoukankan      html  css  js  c++  java
  • Android-Window(一)——初识Window

    Android-Window(一)——初识Window

    学习自

    开发环境

    • SDK 24
    • AS 3.0.1
    • Everything 工具 用来快速查找无法直接通过AS找到的类文件

    UI先行

    Notion to say, UI对于一个APP的成功与否实在是太重要了,我想毕竟大多数人都是一个颜控。当然我也不例外,在我们选择一软件产品的时候,我们肯定会选择比较漂亮的哪一款。 在Android中漂亮的UI都是由各种各样的View来组成的,在很多时候,我们View都呈现在Activity上。 提到了View那么View的添加、更新和删除是由谁负责的呢? 如果你的回答是Activity,请你试想一
    下,不仅仅是Activity可以显示在屏幕上,Dialog、Toast,同样也可以显示在屏幕上。那么是不是他们都各自管理着自己的View呢? 这样显然是不符合设计的!所以有必要让一个具有普适性的 东西 来承载View了。 这个 东西 就是我们今天的主角——Window.

    Window是一个抽象的窗口的概念,负责承载视图,不管是Activity的视图,但是Dialog或者Toast的视图都是由Window来承担的。就像在Activity中调用的 setContentView 其中调用的Window的 setContentView 方法。可以这理解--Window是View的直接管理者

    WindowManager

    要想知道,Window的工作原理,我们需要知道先怎么添加一个Window,要先添加Window需要通过WindowManager来完成。而WindowManager是一个接口继承自 ViewManager。其中有三个主要的方法:

    • addView 负责穿件Window并向其中添加View
    • updateViewLayout 更新Window中View的布局
    • remoteView 通过移除Window中的View可以实现移除Window
    class MainActivity : AppCompatActivity() {
        lateinit var mButton: Button
        lateinit var mLayoutParams: WindowManager.LayoutParams
        lateinit var mWindowManager: WindowManager
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            addWindow()
        }
    
        /**
         * 添加一个Window
         * */
        private fun addWindow() {
            this.mButton = Button(this)
            this.mButton.text = "Hello Button"
    
            this.mLayoutParams = WindowManager.LayoutParams(
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.TYPE_APPLICATION,
                    0,
                    PixelFormat.TRANSPARENT)
    
            this.mWindowManager = this.windowManager
            this.mLayoutParams.flags =
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    
            this.mLayoutParams.gravity = Gravity.TOP or Gravity.LEFT
    
            this.mLayoutParams.x = 100
            this.mLayoutParams.y = 300
    
            this.mWindowManager.addView(this.mButton, this.mLayoutParams)
        }
    }
    

    当Activity启动以后,我们可以发现我们定义的Button已经显示在屏幕上,因此我们已经做了一些设置,所以此界面还会显示在锁屏界面。但是当我们尝试去关闭此界面的时候,打印出了以下的Log:

    android.view.WindowLeaked: Activity cn.shycoder.studywindow.MainActivity has leaked window android.widget.Button{9d337cc VFED..C.. ......ID 0,0-325,126} that was originally added here

    这是因为当Activity已经销毁的时候,但是Activity中的我们刚才手动创建的Window还没有被销毁,造成了Window的内存泄漏。为了避免出现这种情况需要在Activity的 onDestroy 方法中将我们创建的Window移除掉。

    override fun onDestroy() {
        super.onDestroy()
        this.mWindowManager.removeViewImmediate(this.mButton)
    }
    

    上面的添加Window的代码中,涉及到了几个比较重要的参数,我们依次来看一看。

    Flag 参数表示Windows的属性,有很多不同的选项,通过这些选项可以控制Window的表现特性。

    FLAG_NOT_FOCUSABLE
    表示Window不需要获取焦点,也不需要接收各种输入事件,启用此FLAG的时候,也会隐式启用 NOT_TOUCH_MODAL 标签,最终事件会直接传递给下层具有焦点的Window。

    FLAG_NOT_TOUCH_MODAL
    在此模式在,系统会将当前Window区域以外的点击事件传递给底层的Window,当前区域内的点击事件则自己处理。一般都需要开启此FLAG。

    FLAG_SHOW_WHEN——LOCKED
    开启此模式会将Widow显示在锁屏界面上。

    Type
    Type 属性表示的Window的类型,一种分为三种 应用Window(我们的Activity中的Window),子Window(Dialog就是子Window),系统 Window (Toast的Window就是一种系统Window)。其中子Window 比较特殊,必须依附于一个父Window中。如果你想要创建系统级的Window的话,得需要有相应的权限才行。

    Window是分层的,每一个Window都有着对应的 z-ordered, 层级大的Window会覆盖在层级小的Window上面。

    • 应用层级Window 1-99
    • 子Window 1000-1999
    • 系统Window 2000-2999

    Window添加的过程

    每一个Window都对应着一个View(一般是DecorView)和一个RootViewImpl,Window和View之间通过RootViewImpl来建立连接,View是Window的具体呈现。

    在上面呢, 我们通过了WindowManager添加了一个Window,现在我们来分析一下Window添加的流程,当你了解了这个流程之后,就会明白为什么WindowManager调用了 addView 方法,却一直在说添加一个Window,而不是添加一个View了。

    我们知道WindowManager 是一个接口其中并没有实现,WindowManager的实现类是 WindowManagerImpl,我们先来看这个类。

    public final class WindowManagerImpl implements WindowManager {
        //....
        @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类也没有直接实现WindowManager,而是通过调用 mGlobal 的方法来实现,而mGlobal变量是一个 WindowManagerGlobal。在声明此成员变量的时候酒同时也进行了初始化(以单例的形式)。

    在看addView的源码的时候,我们先来看一下WindowManagerGlobal 类中几个非常重要的集合变量:

    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
    
    • mViews 存放了Window所对应的View
    • mRoots 存放了所有Window的ViewRootImpl
    • mParams 存放了所有Window的布局参数
    • mDyingViews 存在的是正在被删除的View

    我们来看一看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");
        }
    	//判断params对象是否是WindowManager.LayoutParams
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }
    
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    	//如果parentWindow不为null则表示,当前要创建的Window是一个子Window
    	//通过adjustLayoutParamsForSubWindow方法做一些额外的参数调整
        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);
                    }
                }
            }
    		    //实例化ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
    
            view.setLayoutParams(wparams);
    
            //一顿添加集合的操作
            mViews.add(view);
    		    mRoots.add(root);
            mParams.add(wparams);
    
            // 将View显示出来
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
    

    setView

    在此方法中完成了Window的添加的过程。该方法的代码量非常多,现在只来看一下主要的代码:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
      //....
      //异步请求刷新布局
      //通过此方法会进行一系列的操作最终会完成View的 measure -> layout -> draw 的三大流程
      requestLayout();
    
      try {
          //通过 mWindowSession 完成添加Window的操作
          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();
          }
      }
      //.....
    }
    

    WindowSession

    在上面的代码中,了解到setView方法最终通过 MWindowSession 完成了Window的添加,那么它是什么呢?

    mWindowSession 的类型是Binder,其实现类是Session,从Session类我们可以推断出添加Window的过程一个 IPC(跨进程通信) 的过程。

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        //调用WindowManagerService的addWindow方法
        //在addWindow方法中将会完成Window的添加
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
    

    关于WindowManagerService这个类,因为博主水平有限,这里就不在瞎叨叨了。大家可以看一下 文章顶部的链接中的源码的分析。

    从上面的一系列的源码中,我们发现,虽然是调用了WindowManager类的addView方法,其实最终还是添加了一个Window。不要被方法名称所迷惑。

    类图

    通过对上面Window添加的过程,我们可以得出以下的类图, 本图片是从 凶残的程序员 大神的博客中模仿来的.

    2018-09-11_19-03-24

    Window删除的过程

    前面我们已经分析了,WindowManager的实现类是 WindowManagerImpl 而在 WindowManagerImpl 则是调用了WindowManagerGlobal来实现,这里我们直接来看WindowManagerGlobal的 removeView 方法。

    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
    
        synchronized (mLock) {
            //找到待删除的View的索引
            int index = findViewLocked(view, true);
            //获取到要删除的ViewRootImpl
            View curView = mRoots.get(index).getView();
            //删除View
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }
            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
    

    在上面的代码中,获取到了要溢出的View的索引,饭后通过调用 removeViewLocked 方法:

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();
    
        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        //通过RootViewImpl对象发送一个删除View的请求
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            //添加到待删除的View集合中
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
    

    上面的主要的代码就是通过 ViewRootImpl 对象的 die 方法来删除 View,其分为两种情况 一种是 立即删除(同步的) 一种是发送消息等待删除(异步)。我们来看一下die方法的代码。

    /**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
    
        //如果是同步删除的话直接调用 doDie方法完成删除操作
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }
    
        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!
    " +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        //如果是异步的删除操作,那么发送一个消息,交给Handler去处理
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
    

    从上面的代码中,我们可以清晰地看到,会通过两种同步和异步的方式来删除View。而不管是同步或者异步,删除View的时候都是调用的 doDie 方法。 我们来看一看doDie方法中做了些什么工作。

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            //移除View
            if (mAdded) {
                //真正移除View的逻辑是在此方法中
                dispatchDetachedFromWindow();
            }
    
            if (mAdded && !mFirst) {
                destroyHardwareRenderer();
    
                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }
                    mSurface.release();
                }
            }
            mAdded = false;
        }
        //将相关对象从WindowManagerGlobal的几个列表中删除
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
    

    从doDie的源码中,我们可以了解到, 移除View的真正的逻辑在 dispatchDetachedFromWindow 方法中,我们来看看此方法。

    void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            //调用View的dispatchDetachedFromWindow 方法,在此方法中会进行资源的回收
            mView.dispatchDetachedFromWindow();
        }
        //一顿回收资源
        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();
    
        destroyHardwareRenderer();
    
        setAccessibilityFocus(null, null);
    
        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;
    
        mSurface.release();
    
        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
    
        try {
            //通过Session调用WinderManagerService完成移除Window的操作
            //一次IPC
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
        // Dispose the input channel after removing the window so the Win
        // doesn't interpret the input channel being closed as an abnorma
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
        unscheduleTraversals();
    }
    

    上面的代码比较长,但是大部分都是一些回收各种资源的代码,这些代码不需要我们太过注意。其中有两点需要我们注意的是 mView.dispatchDetachedFromWindow(); 这个方法将会对View所占用的资源进行回收。不同的View可能会对此方法有不同的实现,比如说ViewGroup的实现是 先调用子View的 dispatchDetachedFromWindow 方法 回收子View的资源,然后在回收自己所占用的资源。 在 View.dispatchDetachedFromWindow 方法中调用了 onDetachedFromWindow 方法,这个方法你可比较熟悉,因为当View从Window中被移除的时候,此方法会被调用, 我们通常会重写这个方法做一些回收资源的事情。 比如说:停止动画和线程等。

    当调用完View的dispatchDetachedFromWindow方法后,紧接着就是一些释放资源的操作,这些我们不用管,在释放完资源后,就通过 mWindowSession.remove 调用了 WindowManagerService的移除Window的方法,哦,请不要忘记这是一个IPC的操作。

    接着回到 doDie 方法,在doDie 方法的最后 调用了 WindowManagerGlobal的 doRemoveView方法,将相关的内容从各个集合中移除掉。至此移除Window的过程结束。

    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);
            //将原来的LayoutParams移除掉
            mParams.remove(index);
            //将新的LayoutParams添加到原理的位置
            mParams.add(index, wparams);
            //通过ViewRootImpl来进行更新
            root.setLayoutParams(wparams, false);
        }
    }
    

    方法很简单,仅仅是设置了一下新的LayoutParams,然后调用 ViewRootImpl的 setLayoutParams 方法。

    setLayoutParams方法代码比较多,但都是一些根据LayoutParams进行各种设置的代码,这里就不贴出来了,在setLayoutParams方法中比较重要的是调用了 scheduleTraversals 方法来对View进行重新布局,该方法最终会调用 performTraversals进行 View的 measure layout draw 三大过程。通知在方法中也会调用 relayoutWindow 方法,在该方法中会调用 Session 的 relayout 方法,最终会通过调用 WindowManagerService 的 relayoutWindow 方法来完成 Window的更新。这依然是一个IPC操作。

    总结

    最后,如果读者你能够看到这里,那我得十分感谢你能够忍受我这枯燥的文风,和我那三流的技术水平。本章的内容就到这了,如果本章的内容能够帮助你对Window有一个大致的了解,那对我来说真是一个值得高兴的事情,本文对Window的添加、删除和更新的流程做了一个简要的概览,并不是深入的分析其实现,主要目的是了解这些过程的脉络。Window是一个比较难的知识点,如果文中有任何纰漏,还望斧正。

  • 相关阅读:
    1442. Count Triplets That Can Form Two Arrays of Equal XOR
    1441. Build an Array With Stack Operations
    312. Burst Balloons
    367. Valid Perfect Square
    307. Range Sum Query
    1232. Check If It Is a Straight Line
    993. Cousins in Binary Tree
    1436. Destination City
    476. Number Complement
    383. Ransom Note
  • 原文地址:https://www.cnblogs.com/slyfox/p/9635078.html
Copyright © 2011-2022 走看看