Window机制

Android----Window 窗口机制

2018-10-13  本文已影响34人  海盗的帽子

csdn
个人博客

一.前言

Window 在 Android 有两种含义,一种是狭义的,一种是广义的。狭义的 Window 指的是 Window 这个抽象类,它的唯一的实现是 PhoneWindow 。广义的 Window 指的是 Android 中表示的窗口的概念,所谓的 Window 窗口机制实际上说的是广义的 Window ,在 Android 中窗口具体可以划分为 三种:
在 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 值越高就越前,也就是越靠近用户。
@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 关系的桥梁。有三个方面的作用:
它有两个重要的变量:

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 有两个子元素,一个是 </ViewStub> 的 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 相关的 系统服务,主要有如下几个职责:

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

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 的管理,其中有几个重要的成员

拒绝服务攻击 简称:DoS,也叫洪水攻击,是一种网络攻击手法,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。

1. 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);
    }
接着先看 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
上一篇下一篇

猜你喜欢

热点阅读