WindowManager#addView_1

关联文章:12,可综合阅读便于理解。
在详解WindowManager的addView方法之前还需要的单独看下一些基础概念:

1、 WindowState:

WindowState是在WMS侧,窗口的体现,前面已经说过了,在应用侧,窗口的体现是PhoneWindow。可以查看WindowState.java文件。

class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
        InsetsControlTarget, InputTarget {
final Session mSession;//每个WindowState都有专门的和WMS会话的session,一个进程的所有window公用一个session;
final IWindow mClient;//这里是addView方法时传进来的ViewRootImpl中定义的IWindow的binder,用于WMS访问应用侧的窗口。
  @NonNull WindowToken mToken;//窗口的WindowToken属性,下面会介绍
  ActivityRecord mActivityRecord;//看定义是和mToken相关联的一个属性
  final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();//窗口的参数
  private boolean mIsChildWindow;//是否是子窗口
final int mBaseLayer;//窗口在wms中的排序依据
final int mSubLayer;//子窗口专属的排序依据
}

2、 WindowToken:

WindowToken就是一个集合,元素是WindowState。其定义如下:

class WindowToken extends WindowContainer<WindowState> {
	final IBinder token;
 	final int windowType;
}

在这里可以看到两个属性,token和windowType,其中token属性是一个binder对象,具有系统唯一性,所以在WMS的mWindowMap中以此变量作为key,作为WindowState的索引。token属性其实就是viewRootImpl中的IWindow接口(一个AIDL接口)在WMS的远端代理,WMS可以通过IWindow访问应用侧的窗口属性,并通过ViewRootImpl进行操作。
windowType就是Windowtoken这个集合的窗口的类型了,和WindowManager.LayoutParams相同。
中的

3、 Token:

上面已经解释了token是什么了。这里讲下Binder的大概关系吧,继承关系如下图:后面可能会开一个专栏讲binder,这个类太复杂了。
在这里插入图片描述

IBinder是基类,Binder类存在于IPC通信的server端,BinderProxy则处于Client端,在上面的WMS和ViewRootImpl中,如果以WMS为client的话,那么BinderProxy就是token,而对应的Binder就是ViewRootImpl中的W。

4、 mActivityRecord:

这个属性和WindowToken之间能相互转化,如果WindowState的类型是应用窗口,那么该属性就和mToken相同。如果是非应用窗口的话,这里就是null。

5、 mBaseLayers:这里可以看到构造函数内的赋值:

mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
        * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;

/
default int getWindowLayerLw(WindowState win) {
    return getWindowLayerFromTypeLw(win.getBaseType(), win.canAddInternalSystemWindow());
}

//此函数主要功能就是返回一个基数,用于计算窗口在Z轴上的参数
default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
        boolean roundedCornerOverlay) {
    // 这里就是把某类窗口直接放在了最顶层.
    if (roundedCornerOverlay && canAddInternalSystemWindow) {
        return getMaxWindowLayer();
    }
//下面就是将应用窗口类型的窗口返回一个基数
    if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
        return APPLICATION_LAYER;
    }
//为不同的type的窗口返回不同的基数
    switch (type) {
        case TYPE_WALLPAPER:
            // wallpaper is at the bottom, though the window manager may move it.
            return  1;
………………………………………………………………………
这里省略了一堆的type的case,简单解释就是根据不同的系统窗口类型返回不同的基数
………………………………………………………………………
        default:
            Slog.e("WindowManager", "Unknown window type: " + type);
            return 3;
    }
}

窗口在屏幕上显示,肯定会有重叠,那谁遮住谁呢?谁能在顶层显示呢?就靠z轴参数,就是这里的mBaseLayer,通过该参数可确定窗口在z轴的高度,从而决定谁能遮住谁,或者输入事件分发给谁。
当然了窗口类型分为四类:
应用窗口:顾名思义,应用的界面显示窗口,这类窗口用来显示Activity和对话框,是通过在onCreate方法中的setContentView创建的。分类对应ActivityRecord。
子窗口 :就是其他窗口的子窗口,通常用于显示一些特定的功能,比如弹出菜单,对话框,浮动窗口等,属性啥的都是依赖他的父窗口。可通过AlertDialog的实例化对象通过show()方法添加,该对象可通过AlertDialog.Builder 的实例调用create()方法获取。
系统窗口:系统窗口用于显示系统级信息、控制界面元素和系统通知。它们不属于应用程序的一部分,而是由Android系统管理。这类窗口还有个特点就是系统会直接给他们添加token。分类对应WindowToken
一些其他窗口:toast、悬浮窗、键盘窗口等。
在上面就可得出,子类的mBaseLayers就是其父类的mBaseLayers。

6、 mSubLayer:

该参数只针对子窗口,父窗口已经进行高度的排序了,那子窗口不也得有个高低贵贱之分?所以就通过该参数进行计算。

mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);

//函数比较简单,直接通过几个case就搞定了
default int getSubWindowLayerFromTypeLw(int type) {
    switch (type) {
        case TYPE_APPLICATION_PANEL:
        case TYPE_APPLICATION_ATTACHED_DIALOG:
            return APPLICATION_PANEL_SUBLAYER;
        case TYPE_APPLICATION_MEDIA:
            return APPLICATION_MEDIA_SUBLAYER;
        case TYPE_APPLICATION_MEDIA_OVERLAY:
            return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
        case TYPE_APPLICATION_SUB_PANEL:
            return APPLICATION_SUB_PANEL_SUBLAYER;
        case TYPE_APPLICATION_ABOVE_SUB_PANEL:
            return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;
    }
    Slog.e("WindowManager", "Unknown sub-window type: " + type);
    return 0;
}

这样就计算出了子窗口高度。

7、 子窗口的排序:

子窗口既然是属于父窗口的,那么就得有个添加的入口,所以找到了在WindowState构造函数中的这个地方:
parentWindow.addChild(this, sWindowSubLayerComparator);
这里可以看到addChild函数,发现WindowState中没有?那怎么办?那不就 得找他爹了呗,继续看到WindowContainer类中,找到了addChild函数:

protected void addChild(E child, Comparator<E> comparator) {
//这里是处理异常,暂时不看
    if (!child.mReparenting && child.getParent() != null) {
        throw new IllegalArgumentException("addChild: container=" + child.getName()
                + " is already a child of container=" + child.getParent().getName()
                + " can't add to container=" + getName());
    }
//这里才是排序的核心
/*
循环遍历下父类WindowState的所有mChildren,然后通过对比comparator
决定子窗口的排序,这里的排序方法不用说要么是比谁大,要么是比谁小
*/
    int positionToAdd = -1;
    if (comparator != null) {
        final int count = mChildren.size();
        for (int i = 0; i < count; i++) {
            if (comparator.compare(child, mChildren.get(i)) < 0) {
                positionToAdd = i;
                break;
            }
        }
    }
//如果发现上面的遍历都没找到合适的位置插入该子窗口,那就放到最后
    if (positionToAdd == -1) {
        mChildren.add(child);
    } else {
//如果找到了合适的位置,就将子窗口放到positionToAdd这个为止
        mChildren.add(positionToAdd, child);
    }

    //将子窗口与父窗口相关联
    child.setParent(this);
}

这里要说下,WindowState的mChildren是一个存放窗口的ArrayList。然后再看下比较器的实现:

private static final Comparator<WindowState> sWindowSubLayerComparator =
        new Comparator<WindowState>() {//构建一个WindowState的比较器
            @Override
//重写对比函数
            public int compare(WindowState w1, WindowState w2) {
//在这里可以看出其实就是对比子窗口的SubLayer参数的大小,
//如果1小于2就返回-1
                final int layer1 = w1.mSubLayer;
                final int layer2 = w2.mSubLayer;
                if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
                    // We insert the child window into the list ordered by
                    // the sub-layer.  For same sub-layers, the negative one
                    // should go below others; the positive one should go
                    // above others.
                    return -1;
                }
                return 1;
            };
        };

从上面就可以看出,父窗口中还维护了一个ArrayList,专门用来存放子窗口,其中子窗口在ArrayList中还是根据mSubLayer进行排序的。当然子窗口中也会有个属性指向他们的父窗口。非子窗口就只有mBaseLayer了,其mSubLayer就直接是0。

8、 非子窗口的排序:

看到了子窗口的存放就会想到那非子窗口呢?非子窗口也会有他的排序场景,想象下,我们在之前说到过,同类的window会放进同一个WindowToken中,那他们是不是也要排序? 所以子窗口是在初始化WindowState的时候就进行了排序,非子窗口会稍晚一点,在WMS的addWindow()方法中的这里进行排序:
win.mToken.addWindow(win);
所以看到WindowToken中的addWindow函数,这里可以发现也会调用到addChild函数(WindowToken也是继承自WindowContainer的),只不过比较器的实现不同:

void addWindow(final WindowState win) {
    ProtoLog.d(WM_DEBUG_FOCUS,
            "addWindow: win=%s Callers=%s", win, Debug.getCallers(5));

    if (win.isChildWindow()) {
        // 如果是子窗口已经放进了父窗口的ArrayList中了。
        return;
    }
    // 为该窗口创建一个新的surface用于绘制窗口
    if (mSurfaceControl == null) {
        createSurfaceControl(true /* force */);
        reassignLayer(getSyncTransaction());
    }
//如果不是子窗的话,就会调用addChild方法,将其放入WindowToken中
    if (!mChildren.contains(win)) {
        ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);
        addChild(win, mWindowComparator);
        mWmService.mWindowsChanged = true;
        // TODO: Should we also be setting layout needed here and other places?
    }
}
//这里是比较器的实现
private final Comparator<WindowState> mWindowComparator =
        (WindowState newWindow, WindowState existingWindow) -> {
    final WindowToken token = WindowToken.this;
//检查需要对比的两个窗口是否属于当前WindowToken的集合,如果不是就会报错
    if (newWindow.mToken != token) {
        throw new IllegalArgumentException("newWindow=" + newWindow
                + " is not a child of token=" + token);
    }
    if (existingWindow.mToken != token) {
        throw new IllegalArgumentException("existingWindow=" + existingWindow
                + " is not a child of token=" + token);
    }
//比较器的核心算法,不过他外包了
    return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
};
//核心算法的实现
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
        WindowState existingWindow) {
    //其实就是比较哪个窗口的mBaseLayer更大
    return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
}

这里addChild方法和上面一样就不再看了。

9、 应用窗口的排序:

WindowToken会有排序,那万一这里是他的子类ActivityRecord,那不就也会排序嘛?我们看到ActivityRecord中也有重写isFirstChildWindowGreaterThanSecond方法,所以需要对应用窗口再进行不同规则的排序。看到方法的实现:

protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
        WindowState existingWindow) {
    final int type1 = newWindow.mAttrs.type;
    final int type2 = existingWindow.mAttrs.type;
//添加的窗口是BaseApplication,且被比较的不是BaseApplication的话,就将添加的窗口放在当前位置 
    if (type1 == TYPE_BASE_APPLICATION && type2 != TYPE_BASE_APPLICATION) {
        return false;
//添加窗口不是BaseApplication,比较的窗口是的话,就继续向后比较
    } else if (type1 != TYPE_BASE_APPLICATION && type2 == TYPE_BASE_APPLICATION) {
        return true;
    }

    // 对于启动窗口的处理
    if (type1 == TYPE_APPLICATION_STARTING && type2 != TYPE_APPLICATION_STARTING) {
        return true;
    } else if (type1 != TYPE_APPLICATION_STARTING && type2 == TYPE_APPLICATION_STARTING) {
        return false;
    }
    return true;
}

总结就是基础窗口放在下面,启动窗口放在上面。这里的windowList的排序规则就是越靠后的窗口显示在越上层。

10、后话:

这里就是我总结的一些必备知识点,可能也会对Android源码有些误解读,如果大佬们发现了,也麻烦帮忙指正。最后也放一张这里比较复杂的几个类之间的关系图:
在这里插入图片描述

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值