Android----Window 窗口机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_38089373/article/details/81105393

一.前言

Window 在 Android 有两种含义,一种是狭义的,一种是广义的。狭义的 Window 指的是 Window 这个抽象类,它的唯一的实现是 PhoneWindow 。广义的 Window 指的是 Android 中表示的窗口的概念,所谓的 Window 窗口机制实际上说的是广义的 Window ,在 Android 中窗口具体可以划分为 三种:
- 应用程序窗口:对应 Activity 显示的视图
- 子窗口:对应 Dialog ,PopupWindow ,Menu 等
- 系统窗口:对应 StatusBar ,Toast ,输入法键盘等
在 Window 窗口机制中,这三种类型就统称为 Window ,因为它们的作用就是显示某种视图,因此一般说的 Window 没有说明都指的是广义的 Window。

二.Window 的管理

Window 的管理涉及到应用进程和系统服务进程之间的通信,这里分为两个方面进行说明。

(一).在应用进程涉及的几个类

1.ViewManager

这是一个接口,里面只有三个方法,定义了对 View ( 也就是所说的视图 )的基本操作,添加,刷新和移除

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

2.WindowManager

这也是一个接口,继承 ViewManager,负责对 Window 进行管理,和操作。每一个 Window 都持有这个对象,用于对自己窗口内部的 View 进行操作。这里还有一个静态的内部类 LayoutParams, 这个类定义 Window 的一些属性,窗口的类别和显示顺序。属性指的是窗口大小,位置,状态等信息,而窗口的类别就是前言中说的三种,这是通过设置 type 值显示的,每个类别有一个范围的 type 值,而显示顺序也是根据 type 值来确定的, type 值越高就越前,也就是越靠近用户。

  • 应用程序窗口的 type :1 ~ 99
  • 子窗口的 type : 1000 ~ 1999
  • 系统窗口 type : 2000 ~ 2999
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
    ...
     public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
         ...
         // 应用程序窗口
        public static final int FIRST_APPLICATION_WINDOW = 1;
        public static final int TYPE_BASE_APPLICATION   = 1;
        public static final int TYPE_APPLICATION        = 2;
        public static final int TYPE_APPLICATION_STARTING = 3;
        public static final int TYPE_DRAWN_APPLICATION = 4;
        public static final int LAST_APPLICATION_WINDOW = 99;

        //子窗口
        public static final int FIRST_SUB_WINDOW = 1000;
        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4; 
        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
        public static final int LAST_SUB_WINDOW = 1999;

        //系统窗口
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        ...
        public static final int LAST_SYSTEM_WINDOW      = 2999;

     }
}

3.WindowManagerImpl

WindowManager 的实现类,持有 Window 类型的变量,因而对 Window 的操作会在这里调用,但是对 View 的三个操作并没有在这里实现,而是由成员变量 WindowManagerGlobal 类型的 mGlobal 去执行。

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

4.WindowManagerGlobal

这个类虽然没有实现 ViewManager ,但是它在这里同样定义了这三种操作,同时它还有几个重要的变量。而对于具体的 View 的操作,这里也没有实现而是交由 ViewRootImpl 去实现。

public final class WindowManagerGlobal {
        ...
        private final ArrayList<View> mViews = new ArrayList<View>(); 所有 Window 对应的 View 
        private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); //所有 Window 对应的 ViewRootImpl 。
        private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>(); 所有 Window 的属性参数
        private final ArraySet<View> mDyingViews = new ArraySet<View>(); //已经执行 remove 方法但是还未真正完成删除的 View 。

        ...

         public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
        ...                
        }

        public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        ...                  
        }
        public void removeView(View view, boolean immediate) {
        ...                   
        }
}

5.ViewRootImpl

ViewRootImpl 是连接 Window 和 View 关系的桥梁。有三个方面的作用:

  • 一是管理 View 树,并完成 View 的绘制三大流程
  • 二是管理 Surface 并对输入事件的的转发。
  • 三是与 WMS ( WindowManagerServer )进行进程间的通信。
它有两个重要的变量:
  • IWindowSession mWindowSession : Binder 对象,服进程在 应用程序进程中的代理对象,用于调用服务进程中的方法。在 WMS 中的是是实现为 Session 。
  • W mWindow : Binder 对象,用于服务进程的调用应用进程的方法。主要是 WMS 添加Window 成功后在 Window 进行显示或者其他操作。

6.DecorView ,mNextView,mDecor 等

DecorView 对于大家可能比较熟悉,这是 Activity 视图中的“根视图”,而其他两个则是 Toast 和 Dialog 的根视图。这看起来好像每种窗口类型都有自己的“根视图”,事实上的确是这样的,我们都知道 Window 实际上就是为了显示各种各样的视图,因此一个 Window 中就可能会有多个 View, 而使用“根视图”则提供了结构上方便,所有的 View 都是以“根视图”来进行添加删除等的,因此便于 WindowManager 对窗口进行管理。在 Activity 中 DecorView 是PhoneWindow 的一个变量,但是并不是所有的窗口都会使用 PhoneWindow 和 抽象接口 Window。

整体的体系如图:
点击查看大图
Window机制.png

(二).创建 Window ( 在应用进程 )

在了解了上面几个概念后,下面就以实际的例子进行说明,虽然不同类型的窗口在创建 Window 的时候整体机制是一样的,即通过 WindowManager 进行管理,但是在一些具体的方面还有有差别的。

1.Activity 的窗口创建和 setContentView

在 Activity 启动过程中的最后一步 Activity 实例创建完后会需要调用 attach 方法关联上下文,在这里会创建 Window 对象。

    final void attach(...){
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ...
    }    

在创建完 Window 后就需要往这个 Window 添加一个最低层的 View 用于管理以后的所有的 View,没错这个 View 就是 DecorView。DecorView 实际上是一个 FrameLayout ,它里面有一个 LinearLayout , LinearLayout 有两个子元素,一个是 的 actionbar , 一个就是 FrameLayout 的 content 。setContentView 就是在这个 FrameLayout 里添加我们的 LayoutView.在这之前需要先创建 DecorView 。

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

首先这里 mContentParent 就是 FrameLayout 的 content 。因为是第一次创建为 null, 就要初始化 DecorView

    private void installDecor() {
        ...
        if (mDecor == null) {
            mDecor = generateDecor(-1);
           ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        }
        ...
    }
    protected ViewGroup generateLayout(DecorView decor) {
        ...
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }

在初始化DecorView 后就会加载 Content View ,并添加到 DecorView .这里的 ID_ANDROID_CONTENT 实际上就是 com.android.internal.R.id.content,在 layout 文件中可以看到

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

在上述步骤中只是创建了 Window 实例, 只是将 View 添加到 DecorView ,但是真正的 Window 的添加还未实现,而具体的实现在之前说过是由 WindowManager 进行操作的,这一步在 hanldeResumeActivity 中。

    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

在 WindowManager 的 addView 方法中就会见到之前说的几个对象的层层调用,一直到 ViewRootImpl, 这个过程如图所示:
addView.png
在 ViewRootImpl 会先调用 requestLayout 对View 进行绘制,然后调用 addToDisplay 添加到 Window 中。这两个方法都涉及与 WMS 的通信,具体则是由 WindowSession 实现,下面就介绍 Window 创建过程中和 WMS 的通信的部分。

(三).在 SystemServer 进程涉及的几个类

1.WindowManagerServer

WindowManagerServer 是 Android 系统中负责处理 Window 和 View 相关的 系统服务,主要有如下几个职责:


  • 对所有的 Window 进行添加,删除等管理。这部分主要涉及 DisplayContent,WindowToken 和 WindowState 这几个对象。
  • 对View 进行绘制,这里需要为每个窗口分配 Surface

一个 Surface 就是一个对象,该对象持有一群像素(pixels),这些像素是要被组合到一起显示到屏幕上的。每一个 window 都有唯一一个自己的 surface,window 将自己的内容绘制到该 surface 中。 Surface Flinger 根据各个 surface 在 Z 轴上的顺序 (Z-order) 将它们渲染到最终的显示屏上,z 轴就是以屏幕为平面,由里到外的一个轴向,而屏幕就是 x 和 y 轴组成的平面。

  • 对从输入系统 InputManagerService 传过来的触摸事件寻找合适的窗口进行处理。
  • 对 Window 的动画效果的管理,这主要是由 WindowAnimator 对象进行管理。
  • 2.WindowState

    用于保存窗口的信息,窗口的信息是可以随时改变的,比如窗口的位置,大小等变化,通常窗口信息的改变,就会进行相应的 View 的改变。WindowState 是保持在一个 Map 里面,这个 Map 保存着系统所有的窗口。

    3.AppWindowToken/WindowToken

    AppWindowToken 是 WindowToken 的 子类,可以译为令牌,一个 Activity 对应一个 WindowToken, 当应用进程向 WMS 发出创建 Window 的申请的时候需要出示正确的令牌。这里需要注意子窗口通常需要依赖于父窗口才能添加,比如在 Activity 上显示 Dialog 或者 Menu ,因此子窗口通常使用的是父窗口的 WindowToken ,这就是为什么创建 Dialog 的时候关联的上下文不能使用 ApplicationContext ,因为 application 没有 WindowToken.

    (四).创建 Window ( 在 Server 进程 )

    在了解了服务端几个类后,下面就是 Window 的创建在 Server 端的过程,首先回到之前的在 ViewRootImpl 的两个方法,requestLayout 方法调用后在 SystemServer 会进行一个 Surface 的创建和绘制,之后就会 view 的绘制。这里属于 Surface 的创建过程,这里就不进行展开,在绘制完成后就会执行 addWindow 方法.
    addWindow 的源码很长,主要是做一下几个部分:
    1.对窗口的参数进行检查,比如窗口的类型,窗口的 WindowToken.输入法这种没有 WindowToken 的系统自己会创建一个 windowToken。
    2.创建 WindowState 对象,将 WindowToken 和 WindowState 关联起来并添加到 HashMap。
    3.将窗口按照 z 轴的位置添加到 DisPlayContent 的合适的位置。
    4.最后就是窗口的位置,动画等信息保存下来。这样一个窗口创建成功。

    (五).Dialog 的 Window 创建

    Dialog 是一个子窗口,需要依赖于父窗口才能显示。

        Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
            ...
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
            final Window w = new PhoneWindow(mContext);
            mWindow = w;
            ...
            w.setWindowManager(mWindowManager, null, null);
    
        }
     @Override
        public Object getSystemService(@ServiceName @NonNull String name) {
    
    
            if (WINDOW_SERVICE.equals(name)) {
                return mWindowManager;
            }
            return super.getSystemService(name);
        }
    

    Dialog 会创建自己的 Window 对象,然后就会在 Activity 的 getSystemService 方法中可以拿到 Activity 的 mWindowManager 对象,这样 Dialog 就会和 Activity 有一样的 Token 。接着看 show 方法。

        public void show() {
            ...
            mDecor = mWindow.getDecorView();
            ...
            mWindowManager.addView(mDecor, l);
    
        }

    之前说过所有的 Window 都有一个“根视图”用于对 所有的View 进行管理,在 Dialog 中就对应 mDecor,可以看到同样的,mWindowManager 会调用 addView 执行窗口的添加工作。 后面的过程基本和上述的 setContentView 的过程相似。

    (六).Toast 的 Window 创建

    Toast 是一个系统级的窗口,因此在创建的过程中就有些不同。这里涉及到了几个新的对象

    (1)涉及的几个对象

    1.NotificationManagerService ( NMS )

    这是一个系统服务,用于管理系统通知,因此也负责 Toast 的管理,其中有几个重要的成员
    - ArrayList mToastQueue ,这是保存系统中所有 Toast 的队列,每个非系统应用的 Toast 在这个队列的数量不能超过 50 个,这样做是避免网络攻击。

    拒绝服务攻击 简称:DoS,也叫洪水攻击,是一种网络攻击手法,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。
    - WorkerHandler 用于处理 Toast 的显示时间,即在 Toast 显示过后发送一个 延迟的消息进行取消。

    2. TN

    这是 Toast 的内部类,继承自 ITransientNotification.Stub ,因此可以跨进程通信,用于处理 Toast 窗口的创建和 View 的添加。

    (2)Toast 的 Window 创建过程

    首先看 makeText 方法

        public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
                @NonNull CharSequence text, @Duration int duration) {
            Toast result = new Toast(context, looper);
    
            LayoutInflater inflate = (LayoutInflater)
                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
            TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
            tv.setText(text);
    
            result.mNextView = v;
            result.mDuration = duration;
    
            return result;
        }

    这里的 mNextView 就是 Toast “根视图”,可以看到这个 mNextView 有一个 TextView 用于显示 Toast 的文本信息。
    接着看 show 方法

        public void show() {
            ...
            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 的 enqueueToast ,这就到 NMS 服务中。在 NMS 服务中会将 Toast 封装为 ToastRecord ,并添加到 mToastQueue 队列中。一直到 从队列中取出进行显示的时候就会调用 TN 的 show 方法,并发送一个延迟的消息。

     @GuardedBy("mToastQueue")
        private void scheduleTimeoutLocked(ToastRecord r)
        {
            mHandler.removeCallbacksAndMessages(r);
            Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
            long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
            mHandler.sendMessageDelayed(m, delay);
        }
    • SHORT_DELAY = 2 s
    • LONG_DELAY = 3.5 s
      接着先看 TN 的 show 方法
            @Override
            public void show(IBinder windowToken) {
                if (localLOGV) Log.v(TAG, "SHOW: " + this);
                mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
        }
    
    
            mHandler = new Handler(looper, null) {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case SHOW: {
                                IBinder token = (IBinder) msg.obj;
                                handleShow(token);
                                break;
                            }
            ...
            public void handleShow(IBinder windowToken) {
                ...
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                ...
                mWM.addView(mView, mParams);
                ...
                } catch (WindowManager.BadTokenException e) {
                        /* ignore */
                }
            }
        }

    依次从三个方法看下来就可以知道 TN 的 show 最后会调用 WindowManager 的 addView 方法,这里可以看到 为了调用 handleShow 方法这里使用了 Handler 。这是因为 NMS 调用 TN 的方法是一种跨跨进程的方式,show 方法就运行在 Binder 线程池中,因此需要使用 Handler 回到 Toast 的线程去调用 addView 方法。
    现在回到 NMS 中,之前发送了个延迟的消息,然后过了相应的时间后就会对这个消息进行处理,也就是将 Toast 窗口删除。

      @Override
            public void handleMessage(Message msg)
            {
                switch (msg.what)
                {
                    case MESSAGE_TIMEOUT:
                        handleTimeout((ToastRecord)msg.obj); //跳到下面
                ...
                }
            }
        private void handleTimeout(ToastRecord record)
        {
            if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
            synchronized (mToastQueue) {
                int index = indexOfToastLocked(record.pkg, record.callback);
                if (index >= 0) {
                    cancelToastLocked(index);//跳到下面
                }
            }
        }
     @GuardedBy("mToastQueue")
        void cancelToastLocked(int index) {
            ToastRecord record = mToastQueue.get(index);
            try {
                record.callback.hide(); // 执行 hide 
            } catch (RemoteException e) {
            ...
            }
    
            ToastRecord lastToast = mToastQueue.remove(index);//移除队列
            mWindowManagerInternal.removeWindowToken(lastToast.token, true,  DEFAULT_DISPLAY);//删除对应 窗口
    
            keepProcessAliveIfNeededLocked(record.pid);
            if (mToastQueue.size() > 0) {
            ...
                showNextToastLocked(); // 如果队列中还有就继续执行
            }
        }

    在延迟的消息到达后就会执行 hide 方法,这个过程和添加实际上是相似的,这里就不再说明。接着将 Toast 从队列中删除,并进行窗口的删除,接着判断队列中是否还有 Toast ,如果就继续执行。具体的过程可看下图:
    点击查看大图
    Toast show.png
    最后再补充一点,前面提到过 为了使 TN 的 show 方法切换到 对应的线程而使用了 Hanler ,而其中 Handler 的 Looper 是这样的

            if (looper == null) {
                    // Use Looper.myLooper() if looper is not specified.
                    looper = Looper.myLooper();
                    if (looper == null) {
                        throw new RuntimeException(
                                "Can't toast on a thread that has not called Looper.prepare()");
                    }
                }

    这就说明当不指定 looper 的时候,默认的是主线程 Looper 。但是如果在 子线程中指定该线程的 Looper ,并调用 Looper.prepare() 方法,就可以在子线程中调用 Toast 的 show 方法。
    参考
    《Android 开发艺术探索》
    Android解析WindowManager
    Android解析WindowManagerService

    猜你喜欢

    转载自blog.csdn.net/m0_38089373/article/details/81105393