WindowManager源码分析-从悬浮窗说起

时间:2021-02-14 17:29:53

在android开发中,有时候我们需要一个悬浮在所有activity之上的悬浮窗口来展示某些重要信息,例如360安全卫士的加速球就是这样一个悬浮窗口。那么悬浮窗口是怎么实现的呢?这里就轮到我们今天的主角WindowManager出场了。

悬浮窗口实例

我们使用一个例子来说明如何创建一个悬浮窗,该悬浮窗可以在屏幕上*拖动,并且点击中心区域后悬浮窗自动消失。想亲自尝试的同学,将这些代码复制到自己的工程中,启动该service就可以了。

我们在一个Service中使用了WindowManager创建了ui,没有使用我们平时常用的Activity,Fragment等。在该服务中我们演示了WindowManager的3个最重要的方法,addView,removeView和updateViewLayout,分别用来添加窗口,移除窗口,更新窗口布局。

    public class FloatService extends Service{
//悬浮窗口布局
private View vMain;
//点击停止按钮
private View btnStop;
//我们的主角,WindowManager
private WindowManager wm;

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
//1,获取WindowManager服务
wm = (WindowManager) getSystemService(WINDOW_SERVICE);
//创建悬浮窗口的布局
createView();
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//显示悬浮窗口
showFloatWindow();
return super.onStartCommand(intent, flags, startId);
}

private void showFloatWindow(){
if(vMain.getParent()==null) {
//创建布局参数对象,宽高的布局参数均为wrap_content
WindowManager.LayoutParams param = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT
,WindowManager.LayoutParams.WRAP_CONTENT);
//设置窗口的类型,这里设置为了Toast类型,该类型的好处是不用额外的权限声明,坏处是无法获取焦点
//所以有edittext这样的控件,是不适合使用这种类型的
//个人建议不需要获取焦点的悬浮窗都使用Toast类型。
//还可以设置TYPE_SYSTEM_ERROR类型,该类型的好处是显示优先级非常高,可以显示在锁屏等界面的上面
//缺点是需要申请SYSTEM_ALERT_WINDOW权限。该权限在android6.0之后需要动态在代码中申请,比较麻烦
//还有TPYE_SYSTEM_ALERT,TYPE_PHONE等类型,和TYPE_SYSTEM_ERROR一样需要SYSTEM_ALERT_WINDOW权限,
//不过显示优先级没有那么高,显示在状态栏和锁屏界面之下。
param.type = WindowManager.LayoutParams.TYPE_TOAST;
//设置对齐方式,这里我们设置在中间,相当于以屏幕中心为原点
param.gravity = Gravity.CENTER;
param.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;
2,addView方法,将窗口添加到WindowManager中
wm.addView(vMain, param);
}
}

private void createView(){
//加载悬浮窗的布局
vMain = LayoutInflater.from(this).inflate(R.layout.view_float,null,false);
//为停止按钮设置点击监听器
btnStop = vMain.findViewById(R.id.btnStop);
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//3,removeView方法,将窗口从WM中移除
if(vMain!=null && vMain.getParent()!=null) {
wm.removeView(vMain);
}
}
});
//为主布局添加触摸监听器
vMain.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch(motionEvent.getAction()){
case MotionEvent.ACTION_MOVE:
//获取窗口的布局
WindowManager.LayoutParams params = (WindowManager.LayoutParams) vMain.getLayoutParams();
//将x,y坐标设置为当前触摸点坐标
params.x = (int) motionEvent.getRawX()-vMain.getWidth()/2;
params.y = (int) motionEvent.getRawY()-vMain.getHeight()/2;
//设置对齐方式为左上
params.gravity = Gravity.LEFT | Gravity.TOP;
//4,updateViewLayout方法更新布局
wm.updateViewLayout(vMain,params);

}
return true;
}
});
}
}

布局文件如下,为一个FrameLayout加上TextView,Layout区域可以拖动,TextView区域点击悬浮窗消失。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="32dp"
android:background="#FF4081"
>

<TextView
android:id = "@+id/btnStop"
android:layout_width="32dp"
android:layout_height="32dp"
android:text= "点我消失"
android:background="#ffffff"
/>

</FrameLayout>

context的getSystemService方法

从上面的示例我们可以看到,要使用WindowManager,我们首先要使用Context获取它的服务,那我们就来分析一下。

Context.getSystemService(WINDOW_SERVICE);

这个语句获取到的究竟是什么,查看代码,我们发现Context的getSystemService方法是一个抽象方法。

public abstract Object getSystemService(@ServiceName @NonNull String name);

那么我们只有去Context类的实际实现类ContextImpl中查看究竟了。

contextImpl的getSystemService方法

@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}

可以看到该类调用SystemServiceRegistry类的getSystemService方法来获取结果。

SystemServiceRegistry类的getSystemService方法

该方法从全局的HashMap结构SYSTEMSERVICERETCHERS中取出一个ServiceFetcher对象,然后调用ServiceFetcher类的getService方法获取实际的服务。

//静态全局HashMap,存储了所有的ServiceFetcher对象。
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();

public static Object getSystemService(ContextImpl ctx, String name) {
//获取对应名称的ServiceFetcher对象
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
//调用该对象的getService方法来实际获取服务
return fetcher != null ? fetcher.getService(ctx) : null;
}

ServiceFetcher内部静态接口

之前我们看到服务实际通过ServiceFetcher类来获取,那么它是何方圣神呢?可以看到,原来它仅仅是一个接口。

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

SystemServiceRegistry类的registerService方法

从ServiceFetcher接口的定义中,我们看不出来getSystemService方法究竟是怎么工作的,那我们换个思路,还记得我们的getSystemService方法中使用到的静态全局变量SYSTEMSERVICEFETCHERS么,我们从这个变量中根据名称取出了ServiceFetcher接口,那么是谁将ServiceFetcher接口放到它里面的呢?我们搜索SYSTEMSERVICEFETCHERS变量,发现了registerService方法将名称和ServiceFetcher接口一一对应了起来。

//建立实际服务的class对象和服务名称的对应关系
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
new HashMap<Class<?>, String>();
//建立名称和类的class与真正服务的对应关系。
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);
}

SystemServiceRegistry类的静态初始化代码块

我们来查找是谁调用registerService方法注册了服务的信息,在静态初始化代码块中,我们找到了注册WINDOW_SERVICE的语句,其他常用服务,如ActivityManager,LayoutInflater等等,都是在这里静态注册的。

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

从这段代码中,我们可以看到,我们实际获得的所谓WINDOW_SERVICE服务,原来是一个WindowManagerImpl类的对象,不过那个CachedServiceFetcher类是什么鬼,我们来看看。

CachedServiceFetcher内部类

我们可以看到,该类的主要逻辑就是为系统service的实际对象提供一个缓存机制,由于service实际对象的创建比较消耗系统资源,而且它们在一个进程中也只需要存在一个,所以我们使用CachedServiceFetcher类来达到缓存系统服务,节约资源的目的。

//这个静态变量保存了缓存的服务的数量
private static int sServiceCacheSize;

static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
//该局部变量保存的是当前ServiceFetcher产生的服务在缓存列表中的位置
private final int mCacheIndex;

public CachedServiceFetcher() {
//在构造方法中,我们将要缓存的服务数量加1,
//并记录下我们在缓存中的位置
mCacheIndex = sServiceCacheSize++;
}

@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
//从ContextImpl类中获取我们的真实服务缓存
//该缓存就是一个size为sServiceCacheSize的静态objcet数组
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
//根据index获取服务
Object service = cache[mCacheIndex];
//如果该服务为空,我们创建该服务,并将它加入index位置,也就是将服务缓存下来
if (service == null) {
service = createService(ctx);
cache[mCacheIndex] = service;
}
return (T)service;
}
}
//抽象方法,创建服务
public abstract T createService(ContextImpl ctx);
}

WindowManagerImpl类

从上面的分析中,我们知道,我们最终获得的是一个WindowManagerImpl类的对象,那么我们来看看该对象的构成。

public final class WindowManagerImpl implements WindowManager {
//使用一个进程唯一的WindowManagerGlobal单例来代理执行请求
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
//保存context对象
private final Context mContext;
//保存父Window
private final Window mParentWindow;

//我们使用getSystemService方法获取的服务,最终调用的就是这个构造方法
public WindowManagerImpl(Context context) {
this(context, null);
}

private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}


@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
//addView方法,没有实际内容,委托WindowManagerGlobal来实际执行操作。
//其他removeView,updateViewLayout方法也同样如此。
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);
}

}

可以看到该类继承自WindowManager接口,并实现了该接口的所有方法,不过该类自身没有任何功能性代码,而是将所有的功能都委托给进程唯一的WindowManagerGlobal类进行处理。

WindowManager接口

WindowManager接口继承自ViewManager接口,ViewManager接口中定义了我们之前说的addView,removeView,updateViewLayout三个重要方法。ViewManager还有一个实现者那就是ViewGrorp,由此我们猜测,android的设计者觉得ViewGroup和WindowManager还是比较相似的,都具备添加View,移除View和修改view布局的功能。只不过一个是操作父ViewGroup,一个是操作窗口管理者。

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接口,该接口除了新增几个方法外,最重要的功能是定义了一个LayoutParams的内部类,该类继承自ViewGroup.LayoutParms,用来表示Window的布局参数。

下面我们重点说说type参数,该参数决定了窗口的类型。在android系统中,一共有3种类型的窗口。

  • 应用程序窗口,我们平时使用的Activity就是该窗口类型,type的取值范围是0-99。
  • 子窗口,显示时需要依附应用程序窗口,例如AlertDialog,popWindow都是这种类型。它的取值范围是1000-1999,它们必须依赖主窗口而存在,可以显示在应用程序窗口前面或后面。
  • 系统窗口,代表为系统状态栏,导航条,输入法,Toast,以及系统对话框等,type的取值范围是2000-2999,显示在应用程序窗口之上。

在系统的窗口管理服务WindowManagerService中,对于每个窗口都有一个层级,我们把它叫做Z或者layer,窗口的这个值越大,表示它越显示在屏幕的前面。也就是说Z值大的窗口会遮挡住Z值在后面的窗口。一般来说 系统窗口Z值>应用窗口的Z值。而子窗口的Z值可以大于或者小于它依附的应用程序窗口。因为Toast是一个系统窗口,所以我们使用Toast时,它会显示在我们自己的Activity之上。

我们在来看看flags参数,该参数决定了窗口的一些重要的行为,通过它我们可以控制窗口全屏显示,显示在锁屏界面上,保存屏幕开启,解锁屏幕等效果。下面的代码中,我们注释了一些常用的效果,还有其他更多效果没有列出,当我们想要控制窗口的行为时,可以到该类中查看,有许多有用的行为。这些flag之间可以通过 | 让几个效果共同起作用。

最后还有一个softInputMode标志,用来控制该窗口中系统软键盘的行为,具体含义可以直接看代码中的注释,我们一般混合使用STATE和ADJUST,来同时指定软键盘的弹出行为和窗口对软键盘弹出的调整。

public interface WindowManager extends ViewManager {

public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//窗口的类型
public int type;
//下面是应用程序窗口,范围是1-99
//第一个应用程序窗口
public static final int FIRST_APPLICATION_WINDOW = 1;
//应用程序基础窗口
public static final int TYPE_BASE_APPLICATION = 1;
//应用程序窗口,我们自己的App的Activity的窗口类型通常都是这个
public static final int TYPE_APPLICATION = 2;
//启动窗口,在一个应用程序的窗口真正启动之前,通常会显示这个窗口
public static final int TYPE_APPLICATION_STARTING = 3;
//最后一个应用程序窗口
public static final int LAST_APPLICATION_WINDOW = 99;

//下面是子窗口,范围是1000-1999,例如各类app的dialog等
//第一个子窗口
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 LAST_SUB_WINDOW = 1999;

//下面是系统类型,范围是2000-2999
//第一个系统窗口
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;
//系统来电页面窗口,需要显示在所有APP窗口之上
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//系统警告对话框窗口,类似低电量关机等
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//系统锁屏窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//系统Toast窗口,Toast的窗口类型就是它
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
//还是来电窗口,不过该类型可以显示在锁屏之上
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
//系统提示框窗口,例如低电量关机等
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
//系统错误窗口,优先级非常高
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
//系统软键盘输入法窗口
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
//系统壁纸窗口
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
//系统导航条窗口
public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
//最后一个系统窗口编号
public static final int LAST_SYSTEM_WINDOW = 2999;


//窗口标志
public int flags;
//所有在这个窗口之后的窗口都会变暗
public static final int FLAG_DIM_BEHIND = 0x00000002;
//这个窗口没有焦点
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
//这个窗口不能触摸
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
//这个窗口将保持亮屏
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
//这个窗口将全屏显示
public static final int FLAG_FULLSCREEN = 0x00000400;
//该窗口可以显示在锁屏界面上
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
//使用壁纸作为当前窗口的背景
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
//该窗口显示时,将会点亮屏幕
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
//该窗口显示时将会自动解锁屏幕,如果有锁屏密码,则跳到密码解锁界面
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;

//系统输入法模式
public int softInputMode;

/*
下面这些标志决定了软键盘是否弹出
*/

//软键盘的状态并没有指定,系统将选择一个合适的状态或依赖于主题的设置
public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
//这个窗口出现时,软键盘将一直保持在上一个窗口状态,无论是隐藏还是显示
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
//这个窗口出现时,软键盘将被隐藏(和下面的标志的区别是,如果该窗口有焦点,那么软键盘还是会弹出)
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
//即使该窗口有焦点,软键盘也会隐藏
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
//这个窗口出现时,软键盘将会弹出
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
//这个窗口收到焦点时,软键盘总是弹出
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;

/*
下面这些标志决定了弹出的软键盘要怎么显示
*/

//默认设置,软键盘由系统自行决定是隐藏还是显示
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
//该窗口总是调整自身的大小以便留出软键盘的空间
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
//该窗口的内容将自动移动以便当前焦点不被软键盘覆盖
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
//该窗口不对软键盘的弹出做任何调整
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;

}
}

WindowManagerGlobal类

前面我们分析过了WindowManagerImpl类,该类内部使用了WindowManagerGlobal类作为它的方法的具体实现者,我们来看看WindowMangerGlobal类的getInstance方法。该方法是一个单例模式,也就是说一个进程中只存在一个WindowManagerGlobal对象。

//WindowManagerService(WMS)的binder代理对象,其服务端就是WMS。
private static IWindowManager sWindowManagerService;
//IWindowSession的binder代理对象,其服务端就是Session。
private static IWindowSession sWindowSession;
//静态单例对象
private static WindowManagerGlobal sDefaultWindowManager;
//典型的单例模式,由于可能在多线程中使用,需要同步
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}

在看看WindowManagerGlobal类的私有构造方法,为什么构造方法要是私有的呢,当然是为了防止外界绕过getInstance方法,直接使用构造方法创建该类的实例了,这也是单例模式的一部分啦。该类的构造方法为空。

private WindowManagerGlobal() {
}

上面我们说过了android中窗口的概念,现在我们来说说android系统中的一个核心服务,WindowManagerService(WMS),它是系统中所有窗口的管理者,之前我们说过窗口层级的概念,WMS的作用就是就管理窗口层级,计算窗口大小并布局,管理和分配窗口的Surface。我们通过WindowManager添加,删除或者修改窗口布局的操作最终都将调用到WMS中的相关方法,才算完成了窗口相关的操作。IWindowManager就是WMS在客户端的binder代理对象。

那么IWindowSeesion是什么呢?我们固然可以直接获取WMS的binder客户端IWindowManager来使用WMS的服务。但是由于WMS是一个重要的系统服务,每一个窗口都需要和它打交道,android可能认为它的负担太重了,于是就使用了IWondowSession binder客户端来和一个匿名binder服务端Session进行通信,一般来说我们为每一个客户端进程创建了一个Seesion服务端进行通信。该服务端Session再将信息转给WMS。关于匿名binder,实名binder以及binder通信的信息,不熟悉的同学请查阅binder的相关介绍。

WindowManagerGlobal类的addView方法

在WindowManager类中,使用了View,LayoutParams,和ViewRootImpl三个全局的列表来存储该进程中所有添加到WindowManager中管理的View的信息,该类的执行过程如下。

  • 如果要添加的窗口还有父窗口,那么对它的布局参数进行调整,主要的目的是使用父窗口的Token作为子窗口布局参数的Token。
  • 查找要添加的View在mViews全局列表中的位置,如果该View已经存在,且没有在mDyingViews之中,则抛出异常。这里的实际含义是我们不能使用addView重复添加一个View到WindowManager中。
  • 创建ViewRootImpl对象,并将View,布局参数和ViewRootImpl加入全局列表中存储。
  • 调用ViewRootImpl类的setView方法继续进行下面的处理。

这里我们大概说一下Token的概念,我们使用WindowManager添加,删除窗口的操作时,实际上通过WindowManagerGlobal中转到ViewRootImpl,最后会调用WindowManagerService(WMS)中的相关方法,来完成对窗口的添加和删除等操作。

这里的Token对于WMS来说相当于一个令牌,WMS需要使用它来区分各个窗口的信息。我们的应用程序窗口,例如Activity,AlertDialog等窗口要添加到WMS上之前,都需要先添加Token信息到WMS中,这部分的工作是由framework层自动完成的,以后我们分析WMS的时候再说。

//这个变量作为一个同步锁来使用
private final Object mLock = new Object();
//这个列表保存了本进程中所有添加到WindowManager管理的根View
private final ArrayList<View> mViews = new ArrayList<View>();
//这个列表保存了所有添加到WindowManager管理的ViewRootImpl类,该类非常重要,我们之后分析。
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//这个列表保存了所有添加到WM管理的布局参数。这3个列表使用下标来保证View,ViewRootImpl和LayoutParams之间的一一对应关系。
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
//这个列表表示我们正在移除,但是还没有移除完成的view(即调用了removeView方法移除的View)
private final ArraySet<View> mDyingViews = new ArraySet<View>();


public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//这里省略一些对View,display等参数的判空语句
//另外特别注意ViewGroup.LayoutParams参数,这里为了接口的统一,使用的是ViewGroup的param类型。
//但是实际上在这里判断如果传入的参数不是WindowManager.LayoutParams或者其子类型,会直接抛出异常。

//获取布局参数
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//1,如果我们要添加的窗口还有父窗口,那么对布局参数进行调整。主要目的是将父窗口的Id(IWindow)作为布局参数的Token
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}

//ViewRootImpl类型的变量,很重要,我们之后分析。
ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
//这里省略一段对系统属性变化进行监听的代码。

//2,查找该view在全局列表mViews中的位置
int index = findViewLocked(view, false);
//如果该view已经保存在列表中了
if (index >= 0) {
//如果我们已经使用过removeView方法移除过该view,该view会保存到mDyingViews全局列表中
//我们调用ViewRootImpl的doDie方法,让它立即死亡。
if (mDyingViews.contains(view)) {
mRoots.get(index).doDie();
}
//否则抛出异常,因为我们不能将一个同样的View多次添加到WindowManger中
else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}

//如果该窗口是一个子窗口,则我们遍历ViewRootImpl查找该子窗口的父View。
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
//因为子窗口必须使用父窗口的IWindow对象(Id)作为Token,所以我们查找token就能确定父窗口
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//创建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
//为根View设置布局参数
view.setLayoutParams(wparams);
//分别将View,ViewRootImpl和LayoutParam添加到对应的列表中。
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}

try {
//3,调用ViewRootImpl类的setView方法继续下面的操作
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
//添加失败,则我们将找出该View所在的位置,并清理资源
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}

Window类的adjustLayoutParamsForSubWindow方法

这个方法主要就是对我们之前说的token值进行调整,具体来说,将子窗口的布局参数中的token变量设置为父窗口的token,这样在WMS中,就可以凭借这个token找到该子窗口的父窗口了。

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
//省略部分无关代码

//如果布局参数是子窗口类型
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
//如果布局参数的token为空(默认的情况下,子窗口不会设置布局参数的token,也就是这里为空)
if (wp.token == null) {
//获取父窗口的DecorView(DecorView是Activity中的根布局View,它继承自frameLayout,我们以后讨论)
View decor = peekDecorView();
//使用父窗口的Token作为我们布局参数中的token,这样我们就可以通过这个token值找到父窗口了。
//如果不是子窗口的情况下,布局参数中的token值将会为空。
if (decor != null) {
wp.token = decor.getWindowToken();
}
}

}
}

WindowManagerGlobal类的findViewLocked方法

该方法的实现很简单,就是从mViews列表中找到当前View的位置。

private int findViewLocked(View view, boolean required) {
//从全局列表中查找当前View的位置
final int index = mViews.indexOf(view);
//如果没有找到当前View且需要抛出异常,那么我们就抛出异常。
if (required && index < 0) {
throw new IllegalArgumentException("View=" + view + " not attached to window manager");
}
return index;
}

WindowManagerGlobal类的updateViewLayout方法

该方法的实现比较简单,就是更新之前我们说的3个全局列表的信息,然后调用ViewRootImpl类的setLayoutParams来进行之后的工作。

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//一些判断参数的语句,参数为空,就抛出异常。
//设置布局参数
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);

synchronized (mLock) {
//查找View的位置,如果找不到View那么就会抛出异常。也就是说必须先addView之后,才能updateViewLayout。
int index = findViewLocked(view, true);
//获取对应的ViewRootImpl对象
ViewRootImpl root = mRoots.get(index);
//更新mParams列表的布局参数。
mParams.remove(index);
mParams.add(index, wparams);
//调用ViewRootImpl类的setLayoutParams方法进行下一步操作。
root.setLayoutParams(wparams, false);
}
}

removeView方法我们就不分析了,大致思路都差不多,也是先对3个全局的列表进行操作,然后调用ViewRootImpl类的die方法完成之后的操作。

这里我们总结一下ViewManager,WindowManager,WindowManagerImpl,WindowManagerGlobal这几个类之间的关系,它们之间的关系如下图所示。注意实际干活的WindowManagerGlobal类并没有继承这一系列的接口,个人猜测主要还是最初始的接口ViewManager需要保持和ViewGroup兼容的原因。
WindowManager源码分析-从悬浮窗说起

ViewRootImpl类

ViewRootImp类实现了ViewParent接口,该接口定义了很多我们熟悉的方法,例如requestLayout,createContextMenu等。ViewRootImpl和ViewGroup都实现了该接口。

ViewRootImpl作为整个控件树的根部,是控件树正常运转的动力所在,我们熟悉的控件树的measure,layout,draw过程最终都是由ViewRootImpl类的performTraversals方法所触发。注意该类虽然是控件树的根部,但是它自身并不是View类的子类,而仅仅是将控件树真正的根部View保存在自身的变量mView中。

另一方面,它是WindowManagerGlobal工作的实际实现者,因此它还要负责与WMS通信以调整窗口的大小和位置,以及对来自WMS的事件(如窗口大小改变)做出处理。

关于该类的详细信息,我们以后介绍,这里就仅仅简单的看一下setView方法是怎样最终和WMS产生关联的。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//保存控件树的根到mView中
mView = view;
//省略大段代码。
//请求进行布局,该方法最后会调用到performTraversals方法
//并最终完成measure,layout,draw这个过程
requestLayout();

try {
//与WindowSession通信,最终将窗口添加到WMS中
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {

}
}
}
}

我们重点看一下mWindowSession这个变量,它是在ViewRootImpl类的构造方法中初始化的,它的类型就是我们之前介绍过的IWindowSession。

//IWindowSession类型,通过它与服务端的Session通信,并最终传递到WMS中。
final IWindowSession mWindowSession;

public ViewRootImpl(Context context, Display display) {
mContext = context;
//从WindowManagerGlobal中获取IWindowSession实例,该实例是进程唯一的。
mWindowSession = WindowManagerGlobal.getWindowSession();
}

WindowManagerGlobal的getWindowSession方法

可以看到,我们先获取WMS服务的binder代理对象,然后通过该对象跨进程调用WMS的openSession方法,让WMS给我们返回一个Session的binder代理对象。以后本进程就通过这个sWindowSession的binder代理对象和Session通信,Session在将信息转给WMS。

public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
//单例模式,这个静态变量我们之前介绍过了
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
//获取WMS的binder代理对象
IWindowManager windowManager = getWindowManagerService();
//调用IWindowManager的openSession方法,让WMS返回一个Session的
//binder代理对象,以后我们的进程就和这个Session的代理对象进行通信
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale());
} catch (RemoteException e) {
}
}
return sWindowSession;
}
}

我们在看看getWindowManagerService方法,该方法同样是一个单例模式,通过ServiceManager取出系统中名称为window的服务,该服务就是WMS,然后将它转换为客户端的接口。可以看到WMS是从ServiceManager中通过名称获取的,所以我们叫它实名binder服务,而上面的Session就是匿名binder服务了。

public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}

Session类的addToDisplay方法

可以看到,Session类的addToDiaplay就是调用了WMS的addWindow方法将窗口添加到了WMS中,这也是窗口能够显示出来的关键一步。到此,我们的分析就结束了,关于WMS和ViewRootImpl的更多细节,我们以后分析。

final class Session extends IWindowSession.Stub
implements IBinder.DeathRecipient {
//Session类中保存的WMS的实例
final WindowManagerService mService;

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
//可以看到,该方法就是调用WMS的addWindow方法,最终将窗口添加到WMS中
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
}

总结

  • 在android framework中,所有显示在屏幕上的界面都是窗口,例如activity,状态栏,软键盘等。窗口分为系统窗口,应用窗口,子窗口三种。窗口的层级(Z)越高,越显示在上面。
  • WMS是对窗口进行管理的系统服务,由于所有的窗口管理都需要它来进行,android怕和它通信的进程太多,忙不过来。所以让所有的app都和Session通信,再由Session将信息转给WMS。
  • WindowManager并不是WMS或者Session的binder客户端,考虑到WMS是很重要的系统服务,android并不打算将里面的大多数方法开放给客户端使用。因此就有了我们的WindowManager,WindowManagerImpl以及WindowManagerGlob,它们仅仅开放了WMS很小一部分的方法。
  • ViewRootImpl类是android的View系统的核心,也是app和WMS交互的中转站。

下面这张图说明了WindowManager,WindowManagerImpl,WindowManagerGlobal,ViewRootImpl和WMS,Session之间的关系。
WindowManager源码分析-从悬浮窗说起

下面这张图是WindowManager的addView和removeView方法的流程,我们分析过的方法基本都包含在这个时序图中,大家也可以根据这个图分析。
WindowManager源码分析-从悬浮窗说起