WindowManagerService

1 WindowWindowManagerWindowManagerService(WMS)

Window 是一个抽象类,具体的实现类为 PhoneWindow,它对 View 进行管理。WindowManager 是一个接口类,继承自接口 ViewManager,它是用来管理 Window 的,它的实现类为 WindowManagerImpl,如果想要对 Window/View进行添加、更新和删除操作就可以使用 WindowManager

WindowManagerServiceAndroid 系统中的重要服务,管理所有窗口,也是输入事件的中转站。WindowManagerService 继承自 IWindowManager.Stub,与 WindowManager 通过 Binder 来进行跨进程通信。

WMS 作为系统服务有很多 API 是无法暴露给 WindowManager 的,这一点与 AMSActivityManager 的关系有些类似。

WindowWindowManagerWMS 的关系可以简略的用下图来表示:

Window、WindowManager和WMS的关系

Window 包含了 View 并对 View 进行管理,Window 用虚线来表示是因为 Window 是一个抽象概念,用来描述一个窗口,并不是真实存在的。WindowManager 用来管理 Window,而 WindowManager 所提供的功能最终会由 WMS 进行处理。

以下是 WindowManager 的关联类:

public abstract class Window { }
public class PhoneWindow extends Window implements MenuBuilder.Callback { }

public interface ViewManager {
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
public interface WindowManager extends ViewManager { }
public final class WindowManagerImpl implements WindowManager { 
	private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
}

public final class WindowManagerGlobal { }

interface IWindowManager { }
public class WindowManagerService extends IWindowManager.Stub
         implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { }

相关类继承关系

**WindowManager 是一个接口类,继承自接口 ViewManagerViewManager 中定义了 3 个方法,分别用来添加、更新和删除 View,**如下所示:

// /frameworks/base/core/java/android/view/ViewManager.java
public interface ViewManager {
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

**WindowManager 也继承了这些方法,而这些方法传入的参数都是 View 类型,说明 Window 是以 View 的形式存在的。WindowManager 在继承 ViewManager 的同时,又加入很多功能,包括 Window 的类型和层级相关的常量、内部类以及一些方法,其中有两个方法是根据 Window 的特性加入的,**如下所示:

// /frameworks/base/core/java/android/view/WindowManager.java
public Display getDefaultDisplay();
public void removeViewImmediate(View view);

WindowManager.getDefaultDisplay() 方法能够得知这个 WindowManager 实例将 Window 添加到哪个屏幕上了,换句话说,就是的到 WindowManager 所管理的屏幕(Display)。WindowManager.removeViewImmediate 方法则固定在这个方法换回钱要立即执行 View.onDetachedFromWindow(),来完成传入的 View 的相关的销毁工作。

2 WindowManagerService 的创建过程

WMS启动过程

WindowManagerService 是在 system_server 进程中创建,首先来看 SystemServer.main() 方法:

// /frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
    new SystemServer().run();
}

SystemServer.main() 中调用了 SystemServer().run() 方法:

// /frameworks/base/services/java/com/android/server/SystemServer.java
private void run() {
    try {
        ...
        Looper.prepareMainLooper(); // 创建消息 Looper
        Looper.getMainLooper().setSlowLogThresholdMs(
            SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);

        // Initialize native services.
        System.loadLibrary("android_servers"); // 1 创建动态库 libandroid_servers.so

        ...
        // Initialize the system context. 创建系统的 Context
        createSystemContext();

        // 2 Create the system service manager. 创建 SystemServiceManager
        mSystemServiceManager = new SystemServiceManager(mSystemContext);
        mSystemServiceManager.setStartInfo(mRuntimeRestart,
                                           mRuntimeStartElapsedTime, mRuntimeStartUptime);
        LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
        // Prepare the thread pool for init tasks that can be parallelized
        SystemServerInitThreadPool.get();
    } finally {
        traceEnd();  // InitBeforeStartServices
    }

    // Start services.
    try {
        traceBeginAndSlog("StartServices");
        startBootstrapServices(); // 3 启动引导服务
        startCoreServices(); // 4 启动核心服务
        startOtherServices(); // 5 启动其他服务
        SystemServerInitThreadPool.shutdown();
    } catch (Throwable ex) {
        Slog.e("System", "******************************************");
        Slog.e("System", "************ Failure starting system services", ex);
        throw ex;
    } finally {
        traceEnd();
    }
    ...
    // Loop forever.
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

在注释 1 处加载了动态库 libandroid_servers.so。接下来在注释 2 处创建 SystemServiceManager,它会对系统的服务进行创建、启动和生命周期管理。在注释 3 处的 startBootstrapServices() 方法中用 SystemServiceManager 启动了 ActivityManagerServicePowerManagerServicePackageManagerService 等服务。在注释 4 处的 startCoreServices() 方法中启动了 DropBoxManagerServiceBatteryServiceUsageStateServiceWebViewUpdateService。在注释 5 处的 startOtherServices() 方法中启动了 CameraServiceAlarmManagerServiceVrManagerService 等服务。从注释 345 处的方法可以看出,官方把系统服务分为三种类型,分别是引导服务、核心服务和其他服务,其中其他服务是一些非要紧的和不需要立即启动的服务,WindowManagerService 就是其他服务的一种。

以下是 SystemServer.startOtherServices() 方法的源代码:

// /frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
    ...
    traceBeginAndSlog("StartWindowManagerService");
    // WMS needs sensor service ready
    ConcurrentUtils.waitForFutureNoInterrupt(mSensorServiceStart, START_SENSOR_SERVICE);
    mSensorServiceStart = null;
    wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
                                   new PhoneWindowManager(), 
                                   mActivityManagerService.mActivityTaskManager); // 1
    ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
                              DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO); // 2
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
                              /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); // 3
    traceEnd();

    traceBeginAndSlog("SetWindowManagerService");
    mActivityManagerService.setWindowManager(wm);
    traceEnd();

    traceBeginAndSlog("WindowManagerServiceOnInitReady");
    wm.onInitReady(); // 4
    traceEnd();
	...
    try {
        wm.displayReady(); // 5
    } catch (Throwable e) {
        reportWtf("making display ready", e);
    }
    traceEnd();
    ...
    traceBeginAndSlog("MakeWindowManagerServiceReady");
    try {
        wm.systemReady(); // 6
    } catch (Throwable e) {
        reportWtf("making Window Manager Service ready", e);
    }
    traceEnd();
    ...
}

SystemServer.startOtherServices() 方法用于启动其他服务,其他服务大概有 100 个左右。在注释 1 处执行了 WindowManagerService.main 方法,其内部会创建 WindowManagerService,需要注意的是WindowManagerService.main 方法中传入的其中一个参数是 InputManagerServiceWindowManagerService 是输入事件的中转站,其内部包含 InputManagerService 并不奇怪。

WindowManagerService.main 方法是运行在 SystemServer.run() 方法中的,换句话说是运行在 system_server 进程中的。注释 2 和注释 3 处分别将 WindowManagerServiceInputManagerService 注册到 ServiceManager 中,这样,如果某个客户端想要使用 WindowManagerService,就需要到 ServiceManager 中查询信息,然后根据信息和 WindowManagerService 所在的进程建立通道,客户端就可以使用 WindowManagerService 了。

首先来看 WindowManagerService.main 方法:

// /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public static WindowManagerService main(final Context context, final InputManagerService im,
                                        final boolean showBootMsgs, final boolean onlyCore, 
                                        WindowManagerPolicy policy,
                                        ActivityTaskManagerService atm) {
    return main(context, im, showBootMsgs, onlyCore, policy, atm,
                SurfaceControl.Transaction::new);

}

@VisibleForTesting
public static WindowManagerService main(final Context context, final InputManagerService im,
                                        final boolean showBootMsgs, final boolean onlyCore, 
                                        WindowManagerPolicy policy,
                                        ActivityTaskManagerService atm,
                                        TransactionFactory transactionFactory) {
    DisplayThread.getHandler().runWithScissors(() -> // 1
             sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,
                                                  atm, transactionFactory), 0); // 2
    return sInstance;
}

注释 2 处运行在 Runnable.run 方法中,在这里创建了 WindowManagerService实例。

在注释 1 处调用了 DisplayThread.getHandler() 方法来得到 Handler 实例。DisplayThread 是一个单例的前台线程,这个线程用来处理低需要低延时显示的相关操作,并只能由 WindowManagerDisplayManagerInputManager 实时执行快速操作。

// frameworks/base/services/core/java/com/android/server/DisplayThread.java
public static Handler getHandler() {
    synchronized (DisplayThread.class) {
        ensureThreadLocked();
        return sHandler;
    }
}

DisplayThread.getHandler().runWithScissors(...) 方法的第一个参数是 Runnable,第二个参数是 timeout,这里传入的是 0。通过以上可以知道,WindowManagerService 的创建是运行在 android.display 线程中的。 以下是 Handler.runWithScissors(...) 方法的源代码:

// /frameworks/base/core/java/android/os/Handler.java
public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
    if (r == null) {
        throw new IllegalArgumentException("runnable must not be null");
    }
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout must be non-negative");
    }

    if (Looper.myLooper() == mLooper) { // 1
        r.run();
        return true;
    }

    BlockingRunnable br = new BlockingRunnable(r);
    return br.postAndWait(this, timeout);
}

在注释 1 处根据每一个线程只有一个 Looper 的原理来判断当前线程(system_server 线程)是否是 Handler 所指的线程(android.display ),如果是则直接执行 Runnable.run 方法,如果不是则调用 BlockingRunnable.postAndWait 方法,并将当前线程的 Runnable 作为参数传进去,BlockingRunnableHandler 的内部类,代码如下所示:

// /frameworks/base/core/java/android/os/Handler.java
private static final class BlockingRunnable implements Runnable {
    private final Runnable mTask;
    private boolean mDone;

    public BlockingRunnable(Runnable task) {
        mTask = task;
    }

    @Override
    public void run() {
        try {
            mTask.run(); // 1
        } finally {
            synchronized (this) {
                mDone = true;
                notifyAll();
            }
        }
    }

    public boolean postAndWait(Handler handler, long timeout) {
        if (!handler.post(this)) { // 2
            return false;
        }

        synchronized (this) {
            if (timeout > 0) {
                final long expirationTime = SystemClock.uptimeMillis() + timeout;
                while (!mDone) {
                    long delay = expirationTime - SystemClock.uptimeMillis();
                    if (delay <= 0) {
                        return false; // timeout
                    }
                    try {
                        wait(delay);
                    } catch (InterruptedException ex) {
                    }
                }
            } else {
                while (!mDone) {
                    try {
                        wait(); // 3
                    } catch (InterruptedException ex) {
                    }
                }
            }
        }
        return true;
    }
}

在注释 2 处将当前的 BlockingRunnable 添加到 Handler 的任务队列中。Handler.runWithScissors 方法的第二个参数是 0,也就是 timeout == 0,这样就会到 else 的相关操作中。如果 mDone == false 的话会调用注释 3 处的 wait() 方法使得当前线程(system_server 线程)进入等待状态,这里等待的是 android.display 线程。在注释 1 处执行了 Runnable.run 方法(android.display 线程),执行完毕后,将 finally 代码块中的 mDone 设置为 true,并调用 notifyAll() 方法唤醒处于等待状态的线程(system_server 线程)。

因此,在这段过程中,android.display 的优先级比较高,system_server 线程等待的就是 android.display 线程,一直到 android.display 线程执行完毕再执行 system_server 线程。 android.display 线程内部执行了 WindowManagetService 的创建。

回到 SystemServer.run() 方法中,

// /frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
    ...
    traceBeginAndSlog("StartWindowManagerService");
    // WMS needs sensor service ready
    ConcurrentUtils.waitForFutureNoInterrupt(mSensorServiceStart, START_SENSOR_SERVICE);
    mSensorServiceStart = null;
    wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
                                   new PhoneWindowManager(), 
                                   mActivityManagerService.mActivityTaskManager); // 1
    ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
                              DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO); // 2
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
                              /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); // 3
    traceEnd();

    traceBeginAndSlog("SetWindowManagerService");
    mActivityManagerService.setWindowManager(wm);
    traceEnd();

    traceBeginAndSlog("WindowManagerServiceOnInitReady");
    wm.onInitReady(); // 4
    traceEnd();
	...
    try {
        wm.displayReady(); // 5
    } catch (Throwable e) {
        reportWtf("making display ready", e);
    }
    traceEnd();
    ...
    traceBeginAndSlog("MakeWindowManagerServiceReady");
    try {
        wm.systemReady(); // 6
    } catch (Throwable e) {
        reportWtf("making Window Manager Service ready", e);
    }
    traceEnd();
    ...
}

注释 5 处用来初始化屏幕显示信息。注释 6 处则用来通知 WindowManagerService初始化工作已经完成。在注释 4 处调用了 WindowManagerService.onInitReady() 方法,以下是相关实现:

// /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public void onInitReady() {
    initPolicy(); // 1

    // Add ourself to the Watchdog monitors.
    Watchdog.getInstance().addMonitor(this);

    openSurfaceTransaction();
    try {
        createWatermarkInTransaction();
    } finally {
        closeSurfaceTransaction("createWatermarkInTransaction");
    }

    showEmulatorDisplayOverlayIfNeeded();
}

WindowManagerPolicy mPolicy;
private void initPolicy() {
    UiThread.getHandler().runWithScissors(new Runnable() {
        @Override
        public void run() {
            WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
            mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this); // 2
        }
    }, 0);
}

注释 2 处初始化了窗口的管理策略的接口类 WindowManagerPolicy(WMP),用来定义一个窗口策略所遵循的通用规范。WindowManagerPolicy(WMP).init(...) 方法在 android.ui 线程中,它的优先级要高于 initPolicy() 方法所在的android.display 线程。在这里,android.display 线程要等 WindowManagerPolicy.init(...) 方法执行完毕后才会被唤醒。

3 创建 Window 以及获取WindowManager

我们已经知道,Window 是一个抽象类,它的具体实现类为 PhoneWindow。在 Activity 启动过程中会调用 ActivityThread.performLaunchActivity 方法,在此方法中又会调用 Activity.attach 方法,PhoneWindow 就是在 Activity.attach 方法中创建的。

Window和WindowManager

以下是 Activity.attach(...) 的相关代码:

// /frameworks/base/core/java/android/app/Activity.java
final void attach(Context context, ActivityThread aThread,
              Instrumentation instr, IBinder token, int ident,
              Application application, Intent intent, ActivityInfo info,
              CharSequence title, Activity parent, String id,
              NonConfigurationInstances lastNonConfigurationInstances,
              Configuration config, String referrer, IVoiceInteractor voiceInteractor,
              Window window) {
          attachBaseContext(context);
  
          mFragments.attachHost(null /*parent*/);
  
          mWindow = new PhoneWindow(this, window); // 1
          ...
          mWindow.setWindowManager(
                  (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                  mToken, mComponent.flattenToString(),
                  (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // 2
          if (mParent != null) {
              mWindow.setContainer(mParent.getWindow());
          }
          mWindowManager = mWindow.getWindowManager();
          mCurrentConfig = config;
      }

在注释 1 处创建了 PhoneWindow,在注释 2 出调用了 PhoneWindow.setWindowManager(...) 方法,这个方法在 PhoneWindow 的父类 Window 中实现。如下所示:

// /frameworks/base/core/java/android/view/Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
                             boolean hardwareAccelerated) {
    mAppToken = appToken;
    mAppName = appName;
    mHardwareAccelerated = hardwareAccelerated
        || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); // 1
    }
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); // 2
}

如果传入的 wm == null ,就会在注释 1 处调用 Context.getSystemService 方法,并传入服务名称 Context.WINDOW_SERVICEpublic static final String WINDOW_SERVICE = "window")。Context.getSystemService(...) 方法具体在 ContextImpl 中实现,如下所示:

// /frameworks/base/core/java/android/app/ContextImpl.java
@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

ContextImpl.getSystemService 方法中会调用 SystemServiceRegister.getSystemService 方法:

// /frameworks/base/core/java/android/app/SystemServiceRegistry.java
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
             new HashMap<String, ServiceFetcher<?>>();
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
              new HashMap<Class<?>, String>();

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

static abstract interface ServiceFetcher<T> {
    T getService(ContextImpl ctx);
}

SYSTEM_SERVICE_FETCHERS 是一个 HashMap 类型的数据,它用来存储服务的名称,那么传入的 Context.WINDOW_SERVICE 到底对应着什么?接着往下看:

// /frameworks/base/core/java/android/app/SystemServiceRegistry.java
private SystemServiceRegistry() { }

static {
    ...
    registerService(Context.WINDOW_SERVICE, WindowManager.class,
                    new CachedServiceFetcher<WindowManager>() {
                        @Override
                        public WindowManager createService(ContextImpl ctx) {
                            return new WindowManagerImpl(ctx); // 1
                        }});
    ...
}

private static <T> void registerService(String serviceName, Class<T> serviceClass,
                                        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

SystemServieRegistry 的静态代码块中会调用多个 registerService 方法。registerService 方法内部会将传入的服务的名称存入到 SYSTEM_SERVICE_FETCHERS 中。从注释 1 处可以看出,传入的 Context.WINDOW_SERVICE 对应的就是 WindowManagerImpl 实例,因此得出结论,Context.getSystemService 方法得到的是 WindowManagerImpl 实例。再回到 Window.setWindowManager 方法,在注释 1 处得到 WindowManagerImpl.createLocalWindowManager 方法:

// /frameworks/base/core/java/android/view/WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}

WindowManagerImpl.createLocalWindowManager 方法同样也是创建 WindowManagerImpl,不同的是这次创建 WindowManagerImpl 时将创建它的 Window 作为参数传了进来,这样 WindowManagerImpl 就持有了 Window 的引用,可以对 Window 进行操作。

4 Window 的属性

为了方便的管理 Window,需要制定一些“协议”,这些“协议”就是 Window 的属性,它们被定义在 WindowManager 的内部类 LayoutParams 中。Window 的属性有很多,与开发密切的有 3 种,它分别是 TypeWindow的类型)、FlagWindow 的标志)和 SoftInputMode(软键盘模式)。

4.1 Window 的类型和显示次序

Window 的类型有很多,比如应用程序窗口、系统错误窗口、输入法窗口、PopupWindowToastDialog 等。总的来说,Window 分为三大类型,分别是 Application Window(应用程序窗口)、Sub Window(子窗口)、System Window(系统窗口),每个类型中又包含了很多种类型,它们都定义在 WindowManager 的静态内部类 LayoutParams 中。

4.1.1 应用程序窗口

Activity 是一个典型的应用程序窗口, 应用程序窗口包含的类型如下:

// /frameworks/base/core/java/android/view/WindowManager.java
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    public static final int FIRST_APPLICATION_WINDOW = 1; // 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; // 2
}

应用程序窗口共包含了以上几种 Type 值,其中注释 1 处的 Type 表示应用程序窗口类型初始值,注释 2 处的 Type 表示应用程序窗口类型的结束值,也就是说应用程序窗口的 Type 值范围是 1~99,这个数值的大小涉及窗口的层级。

4.1.2 子窗口

**子窗口不能独立存在,需要附着其他窗口才可以,PopupWindow 就属于子窗口。**子窗口的类型定义如下:

// /frameworks/base/core/java/android/view/WindowManager.java
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    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; // 子窗口类型结束值
}

子窗口的 Type 值的范围为 1000~1999

4.1.3 系统窗口

**Toast、输入窗口、系统音量条窗口、系统错误窗口都属于系统窗口。**系统窗口的类型定义如下所示:

// /frameworks/base/core/java/android/view/WindowManager.java
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    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 TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3; //系统 ALERT 窗口
    public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4; // 锁屏窗口
    public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5; // Toast 窗口
    ...
    public static final int LAST_SYSTEM_WINDOW      = 2999; // 系统窗口欧类型结束值
}

系统窗口的类型值接近 40 个,系统窗口的 Type 值范围为 2000~2999

4.1.4 窗口显示次序

当一个进程向 WindowManagerService 申请一个窗口时,WindowManagerService 会为窗口确定显示次序。为了方便窗口显示次序的管理,手机屏幕可以虚拟地用 XYZ 轴来表示,其中,Z 轴垂直于屏幕,从屏幕内指向屏幕外,这样确定窗口显示次序也就是确定窗口在 Z 轴上的次序,这个次序称为 Z-OrderType 值是 Z-Order 排序的依据。应用程序窗口的 Type 值范围为 1~99,子窗口 1000~1999,系统窗口 2000~2999,在一般情况下,Type 值越大则 Z-Order 排序越靠前,就越靠近用户。

4.2 Window 的标志

Window 的标志也是 Flag,用于控制 Window 的显示,同样被定义在 WindowManager 的内部类 LayoutParams 中,一共有 20 多个,下面是几个常用的:

  • FLAG_ALLOW_LOCK_WHILE_SCREEN_ON:只要窗口可见,就允许在开启状态的屏幕上锁定;
  • FLAG_NOT_FOCUSABLE:窗口不能获得输入焦点,设置该标志的同时,FLAG_NOT_TOUCH_MODAL 也会被设置;
  • FLAG_NOT_TOUCHABLE:窗口不接收任何触摸事件;
  • FLAG_NOT_TOUCH_MODAL:将该窗口区域外的触摸事件传递给其他的 Window,而自己只会处理窗口区域内的触摸事件;
  • FLAG_KEEP_SCREEN_ON:只要窗口可见,屏幕就会一直亮着;
  • FLAG_LAYOUT_NOT_LIMITS:允许窗口超过屏幕之外;
  • FLAG_FULLSCREEN:隐藏所有的屏幕装饰窗口,比如在游戏、播放器中的全屏显示;
  • FLAG_SHOW_WHEN_LOCKED:窗口可以在锁屏的窗口之上显示;
  • FLAG_IGNORE_CHEEK_PRESSES:当用户的脸贴近屏幕时(比如打电话),不会去响应此事件;
  • FLAG_TURN_SCREEN_ON:窗口显示时将屏幕点亮;

设置 WindowFlag3 种方法,第一种是通过 Window.addFlags 方法:

Window mWindow = getWindow();
mWindow.addFlag(WindowManager.LayoutParams.FLAG_FULLSCREEN)

第二种通过 Window.setFlags 方法:

Window mWindow = getWindow();
mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                 WindowManager.LayoutParams.FLAG_SCREEN);

其实,Window.addFlags 方法的内部会调用 Window.setFlags 方法,以下是相关源码:

// /frameworks/base/core/java/android/view/Window.java
public void addFlags(int flags) {
    setFlags(flags, flags);
}

public void setFlags(int flags, int mask) {
    final WindowManager.LayoutParams attrs = getAttributes();
    attrs.flags = (attrs.flags&~mask) | (flags&mask);
    mForcedWindowFlags |= mask;
    dispatchWindowAttributesChanged(attrs);
}

第三种方法是给 LayoutParams 设置 Flag,并通过 WindowManager.addView 方法进行添加,如下所示:

WindowManager.LayoutParams mWindowLayoutParams = new WindowManagerParams.LayoutParams();
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE)TextView mTextView = new TextView(this);
mWindowManager.addView(mTextView, mWindowLayoutParams)
4.3 软键盘相关模式

窗口和窗口的叠加是十分常见的场景,但是,如果其中的窗口是软键盘窗口,可能会出现一些问题,比如典型的用户登录界面,默认的情况弹出的软键盘窗口可能会覆盖住输入框下面的按钮。为了使软键盘窗口能偶按照期望来显示,WindowManager 的静态内部类LayoutParams 中定义了软键盘的相关模式,以下是常用的几种:

  • SOFT_INPUT_STATE_UNSPECIFIED:没有制定状态,系统会选择一个合适的状态或依赖于主题的设置;
  • SOFT_INPUT_STATE_UNCHANGED:不会改变软键盘状态;
  • SOFT_INPUT_STATE_HIDDEN:当用户进入该窗口时,软键盘默认隐藏;
  • SOFT_INPUT_STATE_ALWAYS_HIDDEN:当窗口获取焦点是,软键盘总是隐藏;
  • SOFT_INPUT_ADJUST_RESIZE:当软键盘弹出时,窗口会调整大小;
  • SOFT_INPUT_ADJUST_PAN:当软键盘弹出时,窗口不需要调整大小,要确保输入焦点是可见的;

从以上给出的 SoftInputMode,可以发现,它们与 AndoridManifest 中的 Activity 的属性 android:windowSoftInputMode 是对应的。因此,除了在 AndroidManifest 中为 Activity 设置 android:windowSoftInputMode 还可以在 Java 代码中为 Window 设置 SoftInputMode,如下所示:

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)

5 Window 的操作

WindowManagerWindow 进行管理,其中包括对 Window 的添加、更新和删除的操作。对于 Window 的操作,最终都是交给 WindowManagerService 来进行处理的。窗口的操作分为两大部分,一部分是通过 WindowManager 来处理的,另一部分是通过 WindowManagerService 来处理的。Window 分为三大类,分别是 Application Window(应用程序窗口)、Sub Window(子窗口)和 System Window(系统窗口),对于不同类型的窗口的添加过程会有所不同,但对于 WindowManagerService 处理部分,添加的过程是一样的。

Window的操作

5.1 Window 的添加过程
5.1.1 WindowManager 部分
系统窗口的添加过程

这里以 StatusBar 为例,StatusBarSystemUI 的重要组成部分,具体就是指系统状态栏,用于显示时间、电量和信号等信息。调用 StatusBar.addStatusBarWindow 方法,将 StatusBar 添加到 Window 中:

// /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
protected StatusBarWindowController mStatusBarWindowController;
protected StatusBarWindowView mStatusBarWindow;

public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
    makeStatusBarView(result); // 1
    mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
    mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight()); // 2
}

注释 1 处用于构建 StatusBar 的视图。注释 2 处调用了 StatusBarWindowController.add(..) 方法,并将 StatusBar 的 视图(StatusBarWindowView )和 StatusBar 的高度传进去,StatusBarWindowController.add(...) 方法如下所示:

// /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
private final WindowManager mWindowManager;

public void add(ViewGroup statusBarView, int barHeight) {

    // Now that the status bar window encompasses the sliding panel and its
    // translucent backdrop, the entire thing is made TRANSLUCENT and is
    // hardware-accelerated.
    mLp = new WindowManager.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        barHeight,
        WindowManager.LayoutParams.TYPE_STATUS_BAR, // 1
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
        PixelFormat.TRANSLUCENT);
    mLp.token = new Binder();
    mLp.gravity = Gravity.TOP;
    mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
    mLp.setTitle("StatusBar");
    mLp.packageName = mContext.getPackageName();
    mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
    mStatusBarView = statusBarView;
    mBarHeight = barHeight;
    mWindowManager.addView(mStatusBarView, mLp); // 2
    mLpChanged.copyFrom(mLp);
    onThemeChanged();
}

首先通过创建 LayoutParams 来配置 StatusBar 视图的属性,包括 WidthHeightTypeFlagGravitySoftInputMode 等。关键在注释 1 处,设置了 TYPE_STATUS_BAR,表示 StatusBar 视图的窗口类型是状态栏。在注释 2 处调用了 WindowManager.addView(...) 方法。WindowManager.addView(...) 方法定义在 WindowManager 的父类 ViewManager 中,实现则是在 WindowManagerImpl 中实现的,代码如下所示:

// /frameworks/base/core/java/android/view/WindowManagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

WindowManagerImpl.addView(...) 方法的第一个参数类型为 View,说明窗口也是以 View 的形式存储在,在注释 1 处调用了 WindowManagerGlobal.addView(...) 方法,如下所示:

// /frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
    ...
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams); // 1
    } 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) {
       ...

        root = new ViewRootImpl(view.getContext(), display); // 2

        view.setLayoutParams(wparams);

        mViews.add(view); // 3
        mRoots.add(root); // 4
        mParams.add(wparams); // 5

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView); // 6
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

WindowManagerGlobal 中维护着和 Window 操作相关的 3 个列表,在窗口的添加、更新和删除过程中都会涉及到这 3 个列表,它们分别是 View 列表(ArrayList<View> mViews)、布局参数列表(ArrayList<WindowManager.LayoutParams> mParams)和 ViewRootImpl 列表(ArrayList<ViewRootImpl> mRoots)。

在注释 1 处,如果当前窗口作为子窗口,就会根据父窗口对子窗口的 WindowManager.LayoutParams 类型的 wparams 对象进行调整。在注释 3 处将添加的 View 保存到 View 列表中。在注释 5 处将窗口的参数保存到布局参数列表中。在注释 2 处创建了 ViewRootImpl 并赋值给 root,紧接着在注释 4 处将 root 存入到 ViewRootImpl 列表中。在注释 6 处将窗口和窗口的参数通过 ViewRootImpl.setView 方法设置到 ViewRootImpl 中,可见,我们添加窗口这一操作是通过 ViewRootImpl 来进行的。ViewRootImpl 身负了很多职责,主要是以下几点:

  • View 树的根并管理 View 树;
  • 触发 View 的测量、布局和绘制;
  • 输入事件的中转站;
  • 管理 Surface
  • 负责与 WMS 进行进程间通信;

在了解了 ViewRootImpl 的职责后,接着来看 ViewRootImpl.setView(...) 方法:

// /frameworks/base/core/java/android/view/ViewRootImpl.java
final IWindowSession mWindowSession;

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        ...    
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                                  getHostVisibility(), mDisplay.getDisplayId(), 
                                                  mTmpFrame, mAttachInfo.mContentInsets, 
                                                  mAttachInfo.mStableInsets,
                                                  mAttachInfo.mOutsets, 
                                                  mAttachInfo.mDisplayCutout, mInputChannel,
                                                  mTempInsets); // 1
                setFrame(mTmpFrame);
            } 
         ... 
    }
}

ViewRootImpl.addView(...) 方法中调用了 IWindowSession.addToDisplay(...) 方法,mWindowSessionIWindowSession 类型的,它是一个 Binder 对象,用于进程间通信,IWindowSessionClient 端的代理,它的 Server 端的实现为 Session。此前的代码逻辑是运行在本地进程的,而 Session.addTodisplay(...) 方法则是运行在 WindowManagerServicesystem_server 进程中)。

ViewRootImpl与WMS通信

本地进程的 ViewRootImpl 想要和 WindowManagerService 进行通信需要经过 Session。下面是 Session.addDisplay(...) 方法,如下所示:

// /frameworks/base/services/core/java/com/android/server/wm/Session.java
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    final WindowManagerService mService;
    
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
                            int viewVisibility, int displayId, Rect outFrame, 
                            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
                            DisplayCutout.ParcelableWrapper outDisplayCutout, 
                            InputChannel outInputChannel, InsetsState outInsetsState) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                                  outContentInsets, outStableInsets, outOutsets, 
                                  outDisplayCutout, outInputChannel, outInsetsState);
    }
}

Session.addToDisplay(...) 方法中调用了 WindowManagerService.addView(...) 方法,并将自身也就是 Session 作为参数传了进去,每个应用程序进程都会对应一个 SessionWindowManagerService 会用 ArrayList 来保存这些 Session,剩下的工作就会交给 WindowManagerService 来处理。WindowManagerService 中会为这个添加的窗口分配 Surface,并确定窗口显示次序,可见负责显现界面的是画布 Surface,而不是窗口本身。WindowManagerService 会将它所管理的 Surface 交由 SurfaceFlinger 处理,SurfaceFlinger 会将这些 Surface 混合并绘制到屏幕上。

系统窗口的添加过程

PhoneWindow 继承自 WindowWindow 通过 setWindowManager 方法与 WindowManager 发生关联。WindowManager 继承自接口 ViewManagerWindowManagerImplWindowManager 接口的实现类,但是具体的功能都会委托给 WindowManagerGlobal 来实现。

Activity 的添加过程

无论是哪种窗口,它的添加过程在 WMS 处理部分中基本是类似的,只不过会在权限和窗口显示次序等方面有些不同。但是在 WindowManager 处理部分会有所不同,以最典型的应用程序窗口 Activity 为例,Activity 在启动过程中,如果 Activity 所在的进程不存在则会创建新的进程,创建新的进程之后就会运行代表主线程的实例 ActivityThreadActivityThread 管理着当前应用程序进程的线程,这在 Activity 的启动过程中运用得很明显,当界面要与用户进行交互时,会调用 ActivityThread.handleResumeActivity 方法,如下所示:

// /frameworks/base/core/java/android/app/ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                                 String reason) {
    ...
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); // 1
    ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager(); // 2
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l); // 3
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    } 
    ...
}

注释 1 处的 performResumeActivity 方法最终会调用 Activity.onResume 方法。在注释 2 处得到 ViewManager 类型的 wm 对象,在注释 3 处调用了 ViewManager.addView 方法,而 addView 方法是在 WindowManagerImpl 中实现的,此后的过程在上面的系统窗口 StatusBar 的添加过程中已经讲述过,唯一需要注意的是 ViewManager.addView 方法的第一个参数是 DecorView,这说明 Activity 窗口中会包含 DecorView

Activity的添加过程

5.1.2 WindowManagerService 部分

无论是系统窗口还是 Activity,它们的 Window 的添加过程都会调用 WindowManagerService.addWindow(...) 方法。以下会分为三部分来讲解。

Part I:

// /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, 
                     int viewVisibility, int displayId, Rect outFrame,
                     Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
                     DisplayCutout.ParcelableWrapper outDisplayCutout, 
                     InputChannel outInputChannel,
                     InsetsState outInsetsState) {
    int[] appOp = new int[1];
    int res = mPolicy.checkAddPermission(attrs, appOp); // 1
    if (res != WindowManagerGlobal.ADD_OKAY) {
        return res;
    }
    ...
    synchronized (mGlobalLock) {
        if (!mDisplayReady) {
            throw new IllegalStateException("Display has not been initialialized");
        }
        // 2
        final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

        if (displayContent == null) {
            Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                   + displayId + ".  Aborting.");
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }
        ...

        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { // 3
            parentWindow = windowForClientLocked(null, attrs.token, false); // 4
            if (parentWindow == null) {
                Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                       + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
            }
            if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                       + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
            }
        }

       ...
    }
   ...
    return res;
}

WindowManagerService.addWindow(...) 返回的是addWindow(...) 的各种状态,比如添加 Window 成功,无效的 display 等,这些状态被定义在 WindowManagerGlobal 中。在注释 1 处根据 Window 的属性,调用 WindowManagerProlicy.checkAndPermission(...) 方法来检查权限,具体在 PhoneWindowManager.checkAndPermission(...) 方法中实现,如果没有权限则不会执行后续的代码逻辑。在注释 2 处通过 displayId 来获得窗口要添加到哪个 DisplayContent 上,如果没有找到 DisplayContent,则返回 WindowManagerGlobal.ADD_INVALID_DISPLAY 这一状态,其中,DisplayContent 用来描述一块屏幕。在注释 3 处,type 代表一个窗口的类型,它的数值介于 FIRST_SUB_WINDOWLAST_SUB_WINDOW 之间(1000~1999),这个数值定义在 WindowManager 中,说明这个窗口是一个子窗口。在注释 4 处,attrs.token 是一个 IBinder 类型的对象,windowForClientLocked(...) 方法内部会根据 attrs.token 作为 key 值从 mWindowMap 中得到该子窗口的父窗口。接着对父窗口进行判断,如果父窗口为 null 或者 type 的取值范围不正确则会返回错误的状态。

说明:WindowManagerPolicy 是窗口管理策略的接口类,用来定义一个窗口策略所要遵循的通用规范,并提供了 WindowManager 所有的特定的 UI 行为。它的具体实现类为 PhoneWindowManager,这个实现类在 WindowManagerService 创建时被创建。WindowManagerPolicy 允许定制窗口层级和特殊窗口类型以及关键的调度和布局。

Part II:

// /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, 
                     int viewVisibility, int displayId, Rect outFrame,
                     Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
                     DisplayCutout.ParcelableWrapper outDisplayCutout, 
                     InputChannel outInputChannel,
                     InsetsState outInsetsState) {
        ...
            
        AppWindowToken atoken = null;
        final boolean hasParent = parentWindow != null;
        WindowToken token = displayContent.getWindowToken(
            hasParent ? parentWindow.mAttrs.token : attrs.token); // 1
        final int rootType = hasParent ? parentWindow.mAttrs.type : type; // 2

        boolean addToastWindowRequiresToken = false;

        if (token == null) {
            if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                       + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
            if (rootType == TYPE_INPUT_METHOD) {
                Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                       + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
            if (rootType == TYPE_VOICE_INTERACTION) {
                Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                       + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
            if (rootType == TYPE_WALLPAPER) {
                Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                       + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
            ...
            if (type == TYPE_TOAST) {
                // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                                                   parentWindow)) {
                    Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                           + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            }
            final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
            final boolean isRoundedCornerOverlay =
                (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
            token = new WindowToken(this, binder, type, false, displayContent,
                                    session.mCanAddInternalSystemWindow, 
                                    isRoundedCornerOverlay); // 3
        } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { // 4
            atoken = token.asAppWindowToken(); // 5
            if (atoken == null) {
                Slog.w(TAG_WM, "Attempted to add window with non-application token "
                       + token + ".  Aborting.");
                return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
            } else if (atoken.removed) {
                Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                       + token + ".  Aborting.");
                return WindowManagerGlobal.ADD_APP_EXITING;
            } else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) {
                Slog.w(TAG_WM, "Attempted to add starting window to token with already existing"
                       + " starting window");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
        } else if (rootType == TYPE_INPUT_METHOD) {
            if (token.windowType != TYPE_INPUT_METHOD) {
                Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                       + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } 
        ...

    }
   ...
    return res;
}

在注释 1 处通过 displayContent.getWindowToken(...) 方法得到 WindowToken。在注释 2 处,如果有父窗口就将父窗口的 type 值赋值给 rootType,如果没有将当前窗口的 type 值赋值给 rootType。接下来如果 WindowToken token == null,则根据 rootType 或者 type 的进行区分判断,如果 rootType 值等于 TYPE_INPUT_METHODTYPE_WALLPAPER 等值时,则返回状态值 WindowManagerGlobal.ADD_BAD_APP_TOKEN,说明 rootType 值等于 TYPTE_INPUT_METHODTYPE_WALLPAPER 等值时是不允许 WindowTokennull 的。通过多次的条件判断筛选,最后会在注释 3 处隐式创建 WindowToken,这说明当我们添加窗口时可以不向 WindowManagerService 提供 WindowToken,前提是 rootTypetype 的值不为前面条件判断筛选的值。WindowToken 隐式和显式的创建肯定是要加以区分的,注释 3 处的第 4 个参数为 false 就代表这个 WindowToken 是隐式创建的。接下来的代码逻辑就是 WindowToken 不为 null 的情况,根据 rootTypetype 的值进行判断,比如在注释 4 处判断如果窗口为应用程序窗口,在注释 5 处将 WindowToken 转换为专门针对应用程序窗口的 AppWindowToken,然后根据 AppWindowToken 的值进行后续的判断。

说明:AppWindowTokenWindowToken 的子类,WindowToken 主要有两个作用:

  • 可以理解为窗口令牌,当应用程序想要 WindowManagerService 申请创建一个窗口,则需要向 WindowManagerService 出示有效的 WindowTokenAppWindowToken 作为 WindowToken 的子类,主要用来描述应用程序的 WindowToken 结构,应用程序中每个 Activity 都对应一个 AppWindowToken
  • WindowToken 会将同一个组件(比如同一个 Activity)的窗口 WindowState 集合在一起,方便管理;

Part III:

// /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, 
                     int viewVisibility, int displayId, Rect outFrame,
                     Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
                     DisplayCutout.ParcelableWrapper outDisplayCutout, 
                     InputChannel outInputChannel,
                     InsetsState outInsetsState) {
        ...

        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                                                appOp[0], seq, attrs, viewVisibility, 
                                                session.mUid,
                                                session.mCanAddInternalSystemWindow); // 1
        if (win.mDeathRecipient == null) { // 2
            // Client has apparently died, so there is no reason to
            // continue.
            Slog.w(TAG_WM, "Adding window client " + client.asBinder()
                   + " that is dead, aborting.");
            return WindowManagerGlobal.ADD_APP_EXITING;
        }

        if (win.getDisplayContent() == null) { // 3
            Slog.w(TAG_WM, "Adding window to Display that has been removed.");
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }

        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        displayPolicy.adjustWindowParamsLw(win, win.mAttrs, Binder.getCallingPid(),
                                           Binder.getCallingUid()); // 4
        win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

        res = displayPolicy.prepareAddWindowLw(win, attrs); // 5
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        ...

        win.attach();
        mWindowMap.put(client.asBinder(), win); // 6

        win.initAppOpsState();

        final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),
                                                  UserHandle.getUserId(win.getOwningUid()));
        win.setHiddenWhileSuspended(suspended);
        
        ...
           
        boolean imMayMove = true;

        win.mToken.addWindow(win); // 7
        if (type == TYPE_INPUT_METHOD) {
            displayContent.setInputMethodWindowLocked(win);
            imMayMove = false;
        } else if (type == TYPE_INPUT_METHOD_DIALOG) {
            displayContent.computeImeTarget(true /* updateImeTarget */);
            imMayMove = false;
        } else {
            ...
        }
        ...
    ...
    return res;
}

在注释 1 处创建了 WindowState,它存有窗口的所有的状态信息,在 WindowManagerService 中代表一个窗口。 在创建 WindowState 传入的参数中,this 指的是 WindowManagerServiceclient 指的是 IWindowIWindow 会将 WindowManagerService 中窗口管理的操作回调给 ViewRootImpltoken 指的是 WindowToken。紧接着在注释 2 和注释 3 处分别判断请求添加窗口的客户端是否已经死亡、窗口的 DisplayContent 是否为 null,如果是则不会再执行下面的代码逻辑。在注释 4 处调用了 WindowManagerPolicy.adjustWindowParamLw(...) 方法,该方法在 PhoneWindowManager 中实现,此方法会根据窗口的 type 对窗口的 LayoutParams 的一些成员变量进行修改。在注释 5 处调用 WindowManagerPolicy.prepareAddWindowLw(...) 方法,用于准备将窗口添加到系统中。在注释 6 处将 WindowState 添加的 mWindowMap 中。在注释 7 处将 WindowState 添加到该 WindowState 对应的 WindowToken 中,实际上是保存在 WindowToken 的父类 WindowContainer 中,这样 WindowToken 就包含了同一组件的 WindowState

WindowMangerService.addView(...) 方法总结:

  • 对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会再执行下面的代码逻辑;
  • WindowToken 相关的处理,比如有的窗口类型需要提供 WindowToken,没有提供的话就不会执行下面的代码逻辑,有的窗口类型则需要由 WindowManagerService 隐式创建 WindowToken
  • WindowState 的创建和相关处理,将 WindowTokenWindowState 相关联;
  • 创建和配置 DisplayContent,完成窗口添加到系统前的准备工作;
5.2 Window 的更新过程

Window 的更新过程和 Window 的添加过程是类似的,需要调用 ViewManager.updateViewLayout 方法。WindowManager.updateViewLayout 方法在 WindowManagerImpl 中实现,WindowManagerImpl.updateViewLayout 方法中会调用 WindowManagerGlobal.updateViewLayout 方法,如下所示:

// /frameworks/base/core/java/android/view/WindowManagerImpl.java
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.updateViewLayout(view, params);
}

// /frameworks/base/core/java/android/view/WindowManagerGlobal.java
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); // 1

    synchronized (mLock) {
        int index = findViewLocked(view, true); // 2
        ViewRootImpl root = mRoots.get(index); // 3
        mParams.remove(index); // 4
        mParams.add(index, wparams); // 5
        root.setLayoutParams(wparams, false); // 6
    }
}

注释 1 处将更新的参数设置到 View 中,注释 2 处得到要更新的窗口在 View 列表中的索引,注释 3 处在 ViewRootImpl 列表中根据索引得到窗口 ViewRootImpl ,注释 4 处和注释 5 处用于更新布局参数列表,注释 6 处调用 ViewRootImpl.setLayoutParams 方法将更新的参数设置到 ViewRootImpl 中。ViewRootImpl.setLayoutParams 方法在最后会调用 ViewRootImpl.scheduleTraveesals 方法,如下所示:

// /frameworks/base/core/java/android/view/ViewRootImpl.java
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
    synchronized (this) {
        ...
        scheduleTraversals();
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 1
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

注释 1 处的 Choreographer译为“舞蹈指导”,用于接收显示系统的 VSync 信号,在下一个帧渲染时控制执行一些操作。Choreographer.postCallback 方法用于发起添加回调,这个添加的回调将在下一帧被渲染时执行。这个添加的回调指的是注释 1 处的 TraversalRunnable 类型的 mTraversalRunnable,如下所示:

// /frameworks/base/core/java/android/view/ViewRootImpl.java
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

TraversalRunnable.run() 方法中调用 doTraversal() 方法,如下所示:

// /frameworks/base/core/java/android/view/ViewRootImpl.java
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

doTraversal 方法中又调用了 performTraversals() 方法,performTraversals() 方法使得 ViewTree 开始 View 的工作流程,如下所示:

// /frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
        try {
            ...
            relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 1
			...
        } catch (RemoteException e) {
        }

        if (!mStopped) {
            ...
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 2
        }
            
	...
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        performLayout(lp, mWidth, mHeight); // 3
		,,,
    }

    ...
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

    if (!cancelDraw && !newSurface) {
        if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();
            }
            mPendingTransitions.clear();
        }

        performDraw(); // 4
    } 
    ...
}

注释 1 处的 relayoutWindow 方法内部会调用 IWindowSessionrelayout 方法来更新 Window视图,最终会调用 WMSrelayoutWindow 方法。除此之外,performTraversals 方法还会在注释 234 处分别调用 performMeasureperformLayoutperformDraw 方法,它们内部又会调用 Viewmeasurelayoutdraw 方法,这就完成了 View 的工作流程。在 performTraversals 方法中更新了 Window 视图,又执行了 Window 中的 View 的工作流程,这样就完成了 Window 的更新。

Window更新过程

5.3 Window 的删除过程

Window 的创建和更新过程一样,要删除 Window 需要先调用 WindowManagerImpl.removeView(...) 方法,在 WindowManagerImpl.removeView(...) 方法中又会调用 WindowMangerGlobal.removeView(...) 方法,以下是相关代码:

// /frameworks/base/core/java/android/view/WindowManagerImpl.java
@Override
public void removeView(View view) {
    mGlobal.removeView(view, false);
}

// /frameworks/base/core/java/android/view/WindowManagerGlobal.java
@UnsupportedAppUsage
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); // 1
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate); // 2
        if (curView == view) {
            return;
        }

        throw new IllegalStateException("Calling with view " + view
                                        + " but the ViewAncestor is attached to " + curView);
    }
}

在注释 1 处找到 WindowView 列表中的索引,在注释 2 处调用了 removeViewLocked(...) 方法并将这个索引传进去,如下所示:

// /frameworks/base/core/java/android/view/WindowManagerGlobal.java
private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index); // 1
    View view = root.getView();

    if (view != null) {
        // 2
        InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
        if (imm != null) {
            imm.windowDismissed(mViews.get(index).getWindowToken()); // 3
        }
    }
    boolean deferred = root.die(immediate); // 4
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

在注释 1 处根据传入的索引在 ViewRootImpl 列表中获得 WindowWindowViewRootImpl。在注释 2 处得到 InputMethodManager 实例,如果 InputMethodManager 实例不为 null,则在注释 3 处调用 InputMethodManager.windowDismissed(...) 方法来结束 Window 的输入法相关的逻辑。在注释 4 处调用 ViewRootImpl.die(...) 方法,如下所示:

// /frameworks/base/core/java/android/view/ViewRootImpl.java
boolean die(boolean immediate) {
    // die 方法需要立即执行并且此时 ViewRootImpl 不再执行 performTraversals 方法
    if (immediate && !mIsInTraversal) { // 1
        doDie(); // 2
        return false;
    }

    if (!mIsDrawing) {
        destroyHardwareRenderer();
    } else {
        Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
              "  window=" + this + ", title=" + mWindowAttributes.getTitle());
    }
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

在注释 1 处如果 immediate == true(需要立即执行),并且 mIsInTraversalsfalse 则执行注释 2 处的代码,mIsInTraversal 在执行 ViewRootImpl.performTraversal() 方法时会被设置为 true,在 ViewRootImpl.performTraverasl() 方法执行完时被设置为 false,因此,注释 1 处可以理解为 ViewRootImpl.die(...) 方法需要立即执行并且此时 ViewRootImpl 不再执行 performTraversals() 方法。在注释 2 处的 doDie() 方法如下所示:

// /frameworks/base/core/java/android/view/ViewRootImpl.java
void doDie() { 
    // 1 检查执行 doDie() 方法的线程的额正确性
    checkThread();
    if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
    synchronized (this) {
        if (mRemoved) { // 2
            return;
        }
        mRemoved = true; // 3
        if (mAdded) { // 4
            dispatchDetachedFromWindow(); // 5
        }

        if (mAdded && !mFirst) { // 6
            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) {
                    }
                }

                destroySurface();
            }
        }

        mAdded = false;
    }
    WindowManagerGlobal.getInstance().doRemoveView(this); // 7
}

在注释 1 处用于检查执行 doDie() 方法的线程的正确性,在注释 1 处的内部会判断执行 doDie() 方法线程是否是创建 Window 的原始线程,如果不是就会抛出异常,这是因为只有创建 Window 的原始线程才能够操作 Window。注释 2 和注释 3 处的代码用于防止 doDie() 方法被重复调用。在注释 4Window 有子 View 就会调用注释 5 处的 dispatchDetachedFromWindow() 方法来销毁 View。在注释 6 处,如果 Wiindow 有子 View 并且不是第一次被添加,就会执行后面的代码逻辑。在注释 7 处的 WindowManagerGobal.doRemoveView(...) 方法:

// frameworks/base/core/java/android/view/WindowManagerGlobal.java
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
private final ArrayList<WindowManager.LayoutParams> mParams =
    new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();

void doRemoveView(ViewRootImpl root) {
    synchronized (mLock) {
        final int index = mRoots.indexOf(root); // 1
        if (index >= 0) {
            mRoots.remove(index);
            mParams.remove(index);
            final View view = mViews.remove(index);
            mDyingViews.remove(view);
        }
    }
    if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
        doTrimForeground();
    }
}

WindowManagerGlobal 中维护了和 Window 操作相关的三个列表,doRemoveView(...) 方法会从这三个列表中清除 Window 对应的元素。在注释 1 处找到 Window 对应的 ViewRootImplViewRootImpl 列表中的索引,接着根据这个索引从 ViewRootImpl 列表、布局参数列表和 View 列表中删除与 Window 对应的元素。

回到 ViewRootImpl.doDie() 方法中,在注释 5 处的 ViewRootImpl.dispatchDetachedFromWindow() 方法:

// // /frameworks/base/core/java/android/view/ViewRootImpl.java
void dispatchDetachedFromWindow() {
    ...
    try {
        mWindowSession.remove(mWindow);
    } catch (RemoteException e) {
    }
    ,,,
}

dispatchDetachedFromWindow() 方法中主要调用了 IWindowSession.remove(...) 方法,IWindowSessionServer 端的实现为 SessionSession.remove(...) 方法如下所示:

// /frameworks/base/services/core/java/com/android/server/wm/Session.java
final WindowManagerService mService;
@Override
public void remove(IWindow window) {
    mService.removeWindow(this, window);
}

接着查看 WindowManagerService.removeWindow(...) 方法:

// /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
void removeWindow(Session session, IWindow client) {
    synchronized (mGlobalLock) {
        WindowState win = windowForClientLocked(session, client, false); // 1
        if (win == null) {
            return;
        }
        win.removeIfPossible(); // 2
    }
}

在注释 1 处用于获取 Window 对应的 WindowStateWindowState 用于保存窗口的信息,在 WindowManagerService 中 它用来描述一个窗口。接着在注释 2 出调用 WindowState.removeIfPossible() 方法,如下所示:

// /frameworks/base/services/core/java/com/android/server/wm/WindowState.java
@Override
void removeIfPossible() {
    super.removeIfPossible();
    removeIfPossible(false /*keepVisibleDeadWindow*/);
}


private void removeIfPossible(boolean keepVisibleDeadWindow) {
    ...   
	try {
        // 条件判断过滤,满足其中一个条件就会 return,推迟删除操作
        ...
        removeImmediately(); // 1
        if (wasVisible) {
            final DisplayContent displayContent = getDisplayContent();
            if (displayContent.updateOrientationFromAppTokens()) {
                displayContent.sendNewConfiguration();
            }
        }
        mWmService.updateFocusedWindowLocked(isFocused()
                                             ? UPDATE_FOCUS_REMOVING_FOCUS
                                             : UPDATE_FOCUS_NORMAL,
                                             true /*updateInputWindows*/);
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
}

removeIfPossible(...) 方法和它的名字一样,并不是直接执行删除操作的,而是进行多个条件判断过滤,满足其中一个条件就会 return,推迟删除操作。比如,Window 正在运行一个动画,这时就得推迟删除操作,直到动画完成。通过这些条件判断过滤就会执行注释 1 处的 removeImmediately() 方法:

// // /frameworks/base/services/core/java/com/android/server/wm/WindowState.java
@Override
void removeImmediately() {
    super.removeImmediately();

    if (mRemoved) { // 1
        // Nothing to do.
        if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
                                     "WS.removeImmediately: " + this + " Already removed...");
        return;
    }

    mRemoved = true; // 2

    ...
        
    dc.getDisplayPolicy().removeWindowLw(this); // 3

    disposeInputChannel();

    mWinAnimator.destroyDeferredSurfaceLocked();
    mWinAnimator.destroySurfaceLocked();
    mSession.windowRemovedLocked(); // 4
    try {
        mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
    } catch (RuntimeException e) {
        // Ignore if it has already been removed (usually because
        // we are doing this as part of processing a death note.)
    }

    mWmService.postWindowRemoveCleanupLocked(this); // 5
}

removeImmediately() 方法用于立即进行删除操作。在注释 1 处的 mRemovedtrue 意味着正在执行删除 Window 操作,在注释 1 到注释 2 之间的代码用于防止重复删除操作。在注释 3 处如果当前要删除的 WindowStatusBar 或者 NavigationBar 就会将这个 Window 从对应的控制器中删除。在注释 4 处将 Window 对应的 SessionWindowManagerServiceArraySet<Session> mSessions 中删除并清除 Session 对应的 SurfaceSession 资源(SurfaceSessionSurfaceFlinger 的一个连接,通过这个连接可以创建 1 个或者多个 Surface 渲染到屏幕上)。在注释 5 处调用了 WindowManagerService.postWindowRemoveCleanupLocked(...) 方法用于对 Window 进行一些集中的清理工作。

Window 的删除过程主要有:

  • 检查删除线程的正确性,如果不正确就抛出异常;
  • ViewRootImpl 列表。布局参数列表和 View 列表中删除与 Window 对应的元素;
  • 判断是否可以直接执行删除操作,如果不能就推迟删除操作;
  • 执行删除操作、清理和释放与 Window 相关的一些资源;

Window的删除过程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值