WMS深入浅出

发布时间 2023-03-23 15:22:08作者: 懒懒初阳

Android中Window的创建过程

在Android中,Window是与操作系统交互的最上层组件。当一个应用程序启动时,首先会创建一个Activity,并且将此活动附加到一个新的窗口上。

以下是Android中窗口的创建过程:

  1. ActivityManagerService(AMS)接收到一个启动指令,AMS通知ActivityThread来处理请求。

  2. ActivityThread启动一个名为"system_server"的进程,该进程负责系统级别的服务。

    AMS“启动”system_server进程system_server 进程包含不同的系统服务,其中就包括 ActivityManagerService (AMS),负责管理应用程序的生命周期以及任务栈等。当启动 Android 系统时,init 进程会执行 init.rc 脚本,该脚本定义了在系统启动时需要启动的所有进程。其中包括 zygote 进程和 system_server 进程。而 AMS 是由 ActivityThread 启动并注册到 system_server 的。具体过程如下:

    • ActivityThread 中的 main() 方法启动时,会创建一个 ApplicationThread 对象,并调用 ApplicationThreadattach() 方法。
    • ApplicationThreadattach() 方法中,会创建 SystemServerConnection 对象,并通过该对象连接到 system_server 进程。
    • SystemServerConnection 对象通过 Binder 机制注册到 system_server 进程并获取 IBinder 实例,这样 system_server 进程就能够调用该对象提供的方法。
    • 此时 system_server 进程就可以使用 ActivityManagerNative.getDefault() 获取 AMS 所提供的服务,从而开始管理应用程序的生命周期。

    所以,system_server 进程不是直接拉起 AMS 进程,而是先由 ActivityThread 注册到 system_server 进程,然后再由 system_server 进程获取 AMS 所提供的服务来进行应用程序管理工作。

    ApplicationThread是一个在应用程序进程内部的私有Binder线程,它允许其他进程通过Binder机制与应用程序交互,例如创建Activity、更新应用程序UI等。该线程通常由ActivityManagerService调用,在进程启动时创建,并在整个应用程序生命周期中存在。

  3. 在系统服务进程中,WindowManagerService (WMS)被启动。

  4. WMS创建一个 WindowManager 对象,并与 Surface Flinger 进行通信,Surface Flinger 负责显示所有从应用程序发来的视图。

  5. WMS调用DisplayManager Service,查找可用的显示设备和默认显示器的信息,以确定需要创建多少个物理窗口。每个物理窗口都对应一个SurfaceView对象,表示要在屏幕上呈现的视图内容。

  6. ActivityThread开始加载所需的资源、类和代码库,并启动一个新的Activity。

  7. 在Activity的onCreate()方法中,调用setContentView(layout)方法,该方法将XML布局文件转换为视图树。ActivityManagerService将创建新的区域,创建一个新的窗口,并将其添加到Windows Manager Service中。

    • 在Android中,视图树指的是屏幕上显示所有UI元素(View、ViewGroup)的结构。每个Activity都有自己的视图树,该树通常由一个叫做DecorView的根View开始。

    • 更新机制:
      当发生界面更新时,不需要手动地去更新每个View或ViewGroup , 而是通过调用invalidate()或者postInvalidate()通知系统进行重绘更新整个视图树,会标记与其关联的视图(父、子视图)dirty,最后会在合适时间一起刷新掉。

      • invalidate():这个方法会告诉系统当前控件状态已经过期,并请求重新绘制。但是它不能直接在子线程中使用,只能在主线程中使用。

      • postInvalidate() :和invalidate()一样,也是用来请求重绘界面, 不同之处就是,它可以在非ui线程中被调用,比如说asyncTask任务完成后重新记录ListView的高度等并且针对大多数情况是足够的。

      在真正执行重绘前,系统会先把所有需要重绘的区域存起来放在一个列表中。接着遍历这个列表将所有需要更新的UI 绘制到Bitmap对象中,最后将Bitmap一次性复制到屏幕缓存里。

  8. 当Activity的窗口准备就绪后,WMS将发送一些事件给输入控制系统,例如:触摸屏动作、键盘按键等等,这样用户才能与刚创建的窗口进行交互。

总之,Window在Android中扮演着非常重要的角色, ViewGroup 和 View 对象以及跨应用程序的 Activity 之间的通信都依赖于它们。

WMS对view的管理

当 Activity 启动时,ActivityManagerService 会创建一个新的 Window,并使用 WindowManager 将其添加到窗口管理器的视图层次结构中。当需要显示新的 UI 组件时(例如对话框、Toast 等),应用程序会向 WindowManager 发出请求。WindowManager 将新的 View 或 ViewGroup 添加到与该应用程序相关联的 Window 上。

ViewRootImpl

ViewRootImpl 位于 Android Framework 层中,是 View 系统的总管,它是连接 Window Manager 和 View 系统的纽带。当 ActivityThread 启动应用程序后,便创建一个 PhoneWindow,而 PhoneWindow 内部拥有一个 ViewRootImpl 对象,实现Activity和Window和View树的构建、事件的分发、View的绘制和视图的更新等功能。

是 Android 系统中用来连接 Activity 的界面事件处理机制,其主要包括两个部分:

  • 作为 WindowManagerGlobal 的内部类,用于在创建/每次动态更新界面时,控制构建窗口,并通过 WindowSession、SurfaceControl 和 View 将窗口信息和视图内容传递给 SurfaceFlinger 进行显示。

  • 作为 View 类的内部类,用于承接所有涉及到的视图绘制或交互事件,并将这些事件反馈回应用程序。这个内部类能够捕获输入设备的第一部分 View 来自屏幕设备的输入事件,并根据需要从 ActivityManager 中获取最新的 UI 绘制。

当一个 Activity 被启动时,Android 系统在底层调用 PhoneWindow.setContentView() 函数并返回 DecorView 对象(ID 为-1),它是整个视图树的根节点。在中心控制循环之后,Android 系统会先将该视图树的布局测量传递到当前窗口(measure()),然后再发出重绘命令,在屏幕上绘制视图并填充组件。最后 draw() 方法被调用以展示所有的子视图和相应的父视图。在这整个过程中,ViewRootlmpl 充当了桥梁和协调员的角色,保证视图的快速渲染和正确地响应全部事件。

Choreographer

Choreographer 是一个系统级别的回调机制,主要作用是协调并掌控界面的刷新,确保动画和其他 UI 变化的流程的顺畅。当 UI 发生更新时,比如用户移动手指或者系统通知 View 需要重绘等,View 会向 Choreographer 注册一个回调 Choreographer.frameCallback(),结合 VSync(垂直同步,即图与图之间的时间间隔,在 Android 中默认为 16ms)来调整任务执行时间点。Choreographer 利用这个机制优化动画效果,确保界面刷新流畅。

Choreographer运行机制如下:

  1. 在UI线程中,Choreographer类会持有一份Vsync信号的引用。Vsync信号代表了显示器每秒的刷新次数。

  2. Choreographer通过注册ViewRootImpl对象来监听输入事件。

  3. 当用户触发屏幕操作等事件时,input event被enqueue到主线程队列中。

  4. Main Thread Looper每次从消息队列中读取message时,就会通知Choreographer发送处理。

  5. 每当Vsync信号到达,Choreographer收到回调后会计算当前应该绘制哪一帧画面,并标记所有需要更新的View及其所在的Window。

  6. 系统根据这些需要更新的Window & View 进行重绘。

  7. 重绘结束后,再次记录下需要更新的View或布局。

  8. 待下一个Vsync信号到达时,就会通知所有需要更新的View执行postInvalidate(相关方法)将对应的区域加入到重绘队列中准备重绘。

  9. 之后,计算渲染时机并更新Vsync信号时间戳,进入下一轮循环。

总之,Choreographer充当了一个类似合唱指挥的作用, 它能够确保Android UI的流畅性和稳定性。

Surface&Canvas

Surface和Canvas是Android中管理View绘制的重要对象。其中,Surface作为一个低层次的抽象,主要提供了基本的像素级的绘制操作,而Canvas则在其上提供了更高级别的图形绘制操作接口。

具体来说,在开发中,View通过获取自己的Canvas实例,使用drawXXX()方法对其进行绘制,即可形成视图元素的样式和布局等属性。然后,这样的操作会被SurfaceFlinger捕捉到并发送到Surface中,在底层完成实际的像素写入、缓存以及帧合成等操作,并将最终的效果呈现到设备上。

此外,Choreographer也有助于优化Surface和Canvas的协作,它可以监测系统的刷新频率和当前性能状况,根据这些信息调整各个应用和服务所需要的绘制和交互逻辑及时得到执行。

因此,三者之间的关系比较密切,一方面Canvas是View的绘制主体之一,为Surface中的操作提供了更加高层次的封装;另一方面,Surface则为Canvas提供了最终的显示和渲染环境;Choreographer则使得这样的协作可以在大部分情况下保持良好的稳定性和流畅程度。

SurfaceView的使用方法示例:

SurfaceView 是 Android 中专门用于在子线程中绘制 UI 的一个 View。

其实,SurfaceView是由 Surface、Canvas 和 SubThread 三部分组成:

  • Surface:是UI线程和子线程之间的交互(内存共享队列等)。
  • Canvas:负责绘图。
  • SubThread:子线程。

在 SurfaceView 中,通过 Surface 来连接 UI 线程和子线程,将子线程中渲染后的图片展示到 UI 上。这样,在 UI 线程中就不需要再去处理绘制的工作了,只需要显示即可。

如下具体实例:

  • XML布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/outer_layout" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 

    <SurfaceView 
        android:id="@+id/surface_view" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_centerInParent="true"/> 

</RelativeLayout>
  • Java代码
public class MyActivity extends Activity implements SurfaceHolder.Callback{  

    private SurfaceView surfaceView;
    private SurfaceHolder holder;
    private boolean running = false;

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_my);  
          
        surfaceView = (SurfaceView)findViewById(R.id.surface_view);  
        holder = surfaceView.getHolder();  
        holder.addCallback(this);
    }  
 
    @Override  
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {  
    }  
 
    @Override  
    public void surfaceCreated(SurfaceHolder arg0) {  
        running = true;  
        new Thread(new Runnable() {
            public void run() {
                while (running) {
                    Canvas canvas = null;
                    try {
                        // 获取Canvas对象
                        canvas = holder.lockCanvas();
                        if (canvas != null) {
                            // 在Canvas上绘制具体内容
                            draw(canvas);
                        }
                    } finally {
                        if (canvas != null) {
                            // 解除Canvas锁定并提交修改
                            holder.unlockCanvasAndPost(canvas);
                        }
                    }

                    // 线程休眠一段时间
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }  
 
    @Override  
    public void surfaceDestroyed(SurfaceHolder arg0) {  
        running = false;  
    }  

    /**
     * 在Canvas上绘制具体内容的方法
     */
    private void draw(Canvas canvas) {
        // 具体绘制步骤省略...
        // 可以调用各种drawXXX方法实现绘制
    }
}  

在什么情况下使用SurfaceView取决于你的需求。如果你需要在你的应用程序中绘制大量图形或动画,则使用SurfaceView通常更为适合。如果只是简单的静态图像,那么ImageView或其他视图控件就足够了。总之,相比其他视图控件而言,SurfaceView具有更好的性能和更多的灵活性。

Vsync

是一种基于垂直同步信号的时间同步机制。在Android中,设备GPU渲染界面的速率固定为每秒60帧,即16.67毫秒每帧。然而,CPU和GPU之间可能存在数据传输延迟或其他因素导致两者工作不协调。这就会导致画面撕裂、抖动或卡顿等现象。

为了解决这些问题,Android引入了VSync机制,以确保CPU和GPU的工作同步。具体实现方式是将VSync信号提供给应用程序,然后使应用程序在每个VSync前进行绘图,并在下一个VSync到来时显示该绘图。这样就可以确保CPU和GPU在同一个时间点上运行。

在Android系统中,VSync信号由硬件发出,它告诉设备硬件屏幕已经准备好接受新的缓冲区。然后,Choreographer将向应用程序发出VSync信号,以触发应用程序响应并执行相应的操作,如更新UI、执行动画等。

Dialog&Toast

dialog源码具体实现

Dialog类继承自Window类,通过内部包含的Window对象来实现对话框窗口的创建和管理。具体实现流程如下:

  1. 构造函数

Dialog有多个构造函数供选择,其中默认会调用样式为Theme_Dialog的构造函数。

  1. 创建并装载内容View

在Dialog中通过setContentView方法将指定的布局文件或View对象设为对话框的内容视图,并使用Window对象来实现该过程。

  1. 设置标题

可以使用setTitle方法设置对话框的标题,注意:要在创建内容前完成标题的设置才能让标题真正生效。

  1. 设置按钮及监听器

提供了设置多个普通按钮和列表对话框专用按钮的回调监听方法。

  1. 显示对话框

首先检查对话框已经拥有了一个Window对象,如果没有就创建一个默认的,并从ActivityManager中获取当前应用程序的WindowToken(Activiy控制焦点的Window)分配给这个Window对象,在最后通过show()方法显示出对话框。

此外,还需要关注触摸事件的处理,当对话框显示后,会开启会modalThread线程进行排他性操作,直到用户操作结束。

以上就是Dialog源码实现的基本流程。

使用dialog示例

使用Dialog可以显示各式各样的弹窗,例如提醒、确认等。以下是一些使用Dialog实例的方法:

根据Xml文件创建Dialog

  1. 新建一个Xml文件(例如:custom_dialog.xml),用来构建Dialog。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minWidth="300dp"
    android:minHeight="250dp">

    <TextView android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello World!" />

    <Button
        android:id="@+id/dialog_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="确定"
        android:layout_below="@id/text"
        android:layout_centerHorizontal="true"/>

</RelativeLayout>
  1. 在Activity中,创建Dialog对象并通过指定Xml文件进行设置。
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Dialog dialog = new Dialog(this);
        dialog.setContentView(R.layout.custom_dialog);
        dialog.show();
        
        // 返回按钮监听 
        Button button = dialog.findViewById(R.id.dialog_button);
        button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dialog.dismiss();
                }
            });
    }
}

AlertDialog源码解析

===============

AlertDialog是Android的一个常用对话框组件,可以向用户显示一些提示信息并提供操作选项。

整体架构

继承关系

AlertDialog继承自Dialog类,而Dialog类又继承自Window类。Window类持有ViewRootImpl与DecorView对象,并为它们提供生命周期管理等功能。AlertDialog在此基础上实现了对话框的布局、样式和逻辑控制等功能。

public class AlertDialog extends Dialog implements DialogInterface {
    ...
}
组成部分

AlertDialog大致可分为以下几个部分:

  1. 对话框标题
  2. 对话框内容区域
  3. 操作按钮

其中,对话框内容区域可能会包含普通文本、列表、自定义视图等多种类型的数据。

如何使用

显示一个AlertDialog
new AlertDialog.Builder(context)
    .setTitle(titleResId)
    .setMessage(messageResId)
    .setPositiveButton(R.string.ok, null)
    .show();
创建AlertDialog实例

AlertDialog内部有一个静态内部类Builder,用于创建AlertDialog实例。Builder通过设置各种属性来产生不同类型的AlertDialog,最后调用show()方法显示出来。

源码分析

我们下面看下AlertDialog.Builder中具体实现细节。

变量声明
private final P mParams;
private int mTheme = 0;
private boolean mCancelable = true;

Builder模式的核心之一就是将需要配置的参数封装到一个Builder类中,并提供链式调用API来设置各个参数的值。在AlertDialog.Builder中,变量mParams就是Builder所持有的参数集合。

构造函数
public Builder(@NonNull Context context) {
    this(context, resolveDialogTheme(context, 0));
}

public Builder(@NonNull Context context, @StyleRes int themeResId) {
    mContext = context;
    mTheme = themeResId;
    mCancelable = true;
    mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    mButtonPositiveIconAttrId = R.attr.alertDialogButtonGroupStyle;
    mButtonNegativeIconAttrId = R.attr.alertDialogButtonGroupStyle;
    mButtonNeutralIconAttrId = R.attr.alertDialogButtonGroupStyle;

    mParams = createParameters();
}

Builder的构造函数接收Context和themeResId两个参数。Context用于获取系统服务,比如LayoutInflater;而themeResId则用于设置AlertDialog的样式,如果没有指定,则使用默认样式。

createParameters()
protected P createParameters() {
        return (P) new AlertController.AlertParams(mContext, mTheme);
}

createParameters()方法的作用是创建AlertController.AlertParams对象,并返回参数类型为P的对象。由于P是泛型,其实现类是AlertController.AlertParams(AlertDialog的内部类)。

setTitle()
public Builder setTitle(@StringRes int titleId) {
    mParams.mTitle = mParams.mContext.getText(titleId);
    return this;
}

public Builder setTitle(CharSequence title) {
    mParams.mTitle = title;
    return this;
}

setTitle()方法用于设置AlertDialog的标题,可以接收String和StringRes两种类型的参数。

setMessage()
public Builder setMessage(@StringRes int messageId) {
    mParams.mMessage = mParams.mContext.getText(messageId);
    return this;
}

public Builder setMessage(CharSequence message) {
    mParams.mMessage = message;
    return this;
}

setMessage()方法用于设置AlertDialog的消息文本。

setPositiveButton()
public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener 

使用AlertDialog


  1. 创建Builder,设置相应参数。
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Title");
builder.setMessage("Message");

builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
                 // action for OK button
        }
        });
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
                 // action for Cancel button
        }
        });
  1. 显示弹窗。
AlertDialog dialog = builder.create();
dialog.show();

使用自定义Dialog

  1. 创建一个继承于Dialog的新类,重写构造方法,进行必要的初始化工作。
public class CustomDialog extends Dialog
{
    public CustomDialog(Context context)
   {
        super(context);
        setContentView(R.layout.custom_dialog);
        // setting properties
   }

   // setters of custom dialog components
   
   private String mValue;
   
   public void setValue(String value)
   {
       mValue = value;
   }
   
   // getters of custom dialog components
   
   public String getValue()
   {
       return mValue;
   }
}
  1. 在需要的地方使用Dialog。
CustomDialog dialog_1 = new CustomDialog(this);

button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                CustomDialog dialog_1 = new CustomDialog(MainActivity.this);
                TextView display_value = dialog_1.findViewById(R.id.display_value);

                display_value.setText("Hello world!");
                dialog_1.show();
            }
        });

从WMS角度分析activity启动

从 WMS 的角度来分析 Activity 启动流程的具体过程:

  1. AMS 开始启动 Activity,调用 WMS 的 addWindow 方法,并传递一些参数。
  2. WMS 首先创建一个用于显示 Activity 的 View,并将其添加到 WindowManager 中。这个 View 通常是 PhoneWindow 类的实例。
  3. 接着,WMS 会根据 Activity 的属性设置窗口的类型和特性,例如是否全屏、有没有标题栏等。
  4. WMS 将 Activity 的 View 添加到 WindowManager 的视图树中,并计算出 Activity 窗口的大小和位置。
  5. 最后,WMS 通知 AMS 发送消息,即 Activity 已准备好并显示出来了。

Window的添加和删除过程如下:

Window的添加(也叫“拦截”)过程如下:

  1. ActivityManagerService(以下简称AMS)通过Binder机制向应用进程中的WMS发送启动Activity请求。
  2. WMS根据请求信息,创建新的WindowManager.LayoutParams。该LayoutParams中记录了窗口的各种参数,例如窗口类型、布局、大小、位置等等。
  3. WindowManagerGlobal类是一个单例类,WMS在创建完WindowManager.LayoutParams后,将该参数传入WindowManagerGlobal.addView()方法进行窗口的添加操作。注意,该方法并不会直接添加窗口View到DecorView中,而是创建两个辅助类ViewRootImpl和ViewRootImpl.Called。
  4. ViewRootImpl才是window真正的添加者,它在WindowManagerGlobal.addView()方法的内部实现中被调用。该类完成以下任务:
    1. 创建普通View或SurfaceView对象,并为之设置Context。
    2. 通过WindowManager.LayoutParams信息,初始化视图的测量和布局参数。“视图的测量大小”指的就是上文提到的 “sizeCompatMode”,即“全屏模式” 或 “裁剪屏幕模式”。这个模式的设置有一定的先后顺序。
    3. 注册Activity View监听器,在窗口状态发生变化时,通知Activity做出相应处理(如onResume()等)。
  5. 此时View已经准备好了,但我们需要通过WindowManager.addView()将测量和布局结果写入DecorView中。此措施足够友好地保障外部View和界面的稳定性和可靠性。

Window的移除过程如下:

  1. 当Activity销毁时,最顶层Window即DecorView要被移除。AMS接收到该 Activity 对象销毁的请求。
  2. AMS调用WMS的removeView时,wms判断getWindowListLocked()是否为空,如果不为空,那么调用对应的viewRootImpl.destroy()。
  3. 在destroy()方法中会调用 Surface.Release() 方法,把 surface 销毁掉。
  4. 接着,我们在 final i = mViews.indexOf(view) 将 mView 对应的 view 移除。
  5. 同时,我们还要将 ViewCallbacks,即 Activity 赋值 null,避免内存泄漏。