Android面试官:Window连环十二问你顶得住吗?,2024年Android岗位BAT大厂面试题知识点小结

WindowManager是什么?和WMS的关系?

WindowManager就是用来管理Window的,实现类为WindowManagerImpl,实际工作会委托给WindowManagerGlobal类中完成。

而具体的Window操作,WM会通过Binder告诉WMS,WMS做最后的真正操作Window的工作,会为这个Window分配Surface,并绘制到屏幕上。

怎么添加一个Window?

var windowParams: WindowManager.LayoutParams = WindowManager.LayoutParams()

windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE

windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG

var btn = Button(this)

windowManager.addView(btn, windowParams)

简单贴了下代码,加了一个Button。

有的朋友可能会疑惑了,这明明是个Button,是个View啊,咋成了Window?

刚才说过了,View是Window的表现形式,在实际实现中,添加window其实就是添加了一个你看不到的window,并且里面有View才能让你感觉得到这个是一个Window。

所以通过windowManager添加的View其实就是添加Window的过程。

这其中还有两个比较重要的属性:flags和type,下面会依次说到。

Window怎样可以显示到锁屏界面


Window的flag可以控制Window的显示特性,也就是该怎么显示、touch事件处理、与设备的关系、等等。所以这里问的锁屏界面显示也是其中的一种Flag。

// Window不需要获取焦点,也不接受各种输入事件。

public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

// @deprecated Use {@link android.R.attr#showWhenLocked} or

// {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an

// unintentional double life-cycle event.

// 窗口可以在锁屏的 Window 之上显示

public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;

Window三种类型都存在的情况下,显示层级是怎样。

Type表示Window的类型,一共三种:

  • 应用Window。对应着一个Activity,Window层级为1~99,在视图最下层。

  • 子Window。不能单独存在,需要附属在特定的父Window之中(如Dialog就是子Window),Window层级为1000~1999。

  • 系统Window。需要声明权限才能创建的Window,比如Toast和系统状态栏,Window层级为2000-2999,处在视图最上层。

可以看到,区别就是有个Window层级(z-ordered),层级高的能覆盖住层级低的,离用户更近。

Window就是指PhoneWindow吗?

如果有人问我这个问题,我肯定心里要大大的疑惑了🤔。

可不就是PhoneWindow吗?都唯一实现类了,净问些奇怪问题。

但是面试的时候遇到这种问题总要答啊?这时候就要扯出Window的概念了。

  • 如果指的Window类,那么PhoneWindow作为唯一实现类,一般指的就是PhoneWindow。

  • 如果指的Window这个概念,那肯定不是指PhoneWindow,而是存在于界面上真实的View。当然也不是所有的View都是Window,而是通过WindowManager添加到屏幕的view才是Window,所以PopupWindow是Window,上述问题中添加的单个View也是Window。

PhoneWindow什么时候被创建的?

熟悉Activity启动流程的朋友应该知道,启动过程会执行到ActivityThread的handleLaunchActivity方法,这里初始化了WindowManagerGlobal,也就是WindowManager实际操作Window的类,待会会看到:

public Activity handleLaunchActivity(ActivityClientRecord r,

PendingTransactionActions pendingActions, Intent customIntent) {

//…

WindowManagerGlobal.initialize();

//…

final Activity a = performLaunchActivity(r, customIntent);

//…

return a;

}

然后会执行到performLaunchActivity中创建Activity,并调用attach方法进行一些数据的初始化(伪代码):

final void attach() {

//初始化PhoneWindow

mWindow = new PhoneWindow(this, window, activityConfigCallback);

mWindow.setWindowControllerCallback(mWindowControllerCallback);

mWindow.setCallback(this);

//和WindowManager关联

mWindow.setWindowManager(

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),

mToken, mComponent.flattenToString(),

(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

mWindowManager = mWindow.getWindowManager();

}

可以看到,在Activity的attach方法中,创建了PhoneWindow,并且设置了callback,windowManager

这里的callback待会会说到,跟事件分发有关系,可以说是当前Activity和PhoneWindow建立联系。

要实现可以拖动的View该怎么做?

还是接着刚才的btn例子,如果要修改btn的位置,使用updateViewLayout即可,然后在ontouch方法中传入移动的坐标即可。

btn.setOnTouchListener { v, event ->

val index = event.findPointerIndex(0)

when (event.action) {

ACTION_MOVE -> {

windowParams.x = event.getRawX(index).toInt()

windowParams.y = event.getRawY(index).toInt()

windowManager.updateViewLayout(btn, windowParams)

}

else -> {

}

}

false

}

Window的添加、删除和更新过程。

Window的操作都是通过WindowManager来完成的,而WindowManager是一个接口,他的实现类是WindowManagerImpl,并且全部交给WindowManagerGlobal来处理。下面具体说下addView,updateViewLayout,和removeView。

1)addView

//WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,

Display display, Window parentWindow) {

if (parentWindow != null) {

parentWindow.adjustLayoutParamsForSubWindow(wparams);

}

ViewRootImpl root;

View panelParentView = null;

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

view.setLayoutParams(wparams);

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

try {

root.setView(view, wparams, panelParentView);

}

}

}

  • 这里可以看到,创建了一个ViewRootImpl实例,这样就说明了每个Window都对应着一个ViewRootImpl。

  • 然后通过add方法修改了WindowManagerGlobal中的一些参数,比如mViews—存储了所有Window所对应的View,mRoots——所有Window所对应的ViewRootImpl,mParams—所有Window对应的布局参数。

  • 最后调用了ViewRootImpl的setView方法,继续看看。

final IWindowSession mWindowSession;

mWindowSession = WindowManagerGlobal.getWindowSession();

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

//

requestLayout();

res = mWindowSession.addToDisplay(mWindow,);

}

setView方法主要完成了两件事,一是通过requestLayout方法完成异步刷新界面的请求,进行完整的view绘制流程。其次,会通过IWindowSession进行一次IPC调用,交给到WMS来实现Window的添加。

其中mWindowSession是一个Binder对象,相当于在客户端的代理类,对应的服务端的实现为Session,而Session就是运行在SystemServer进程中,具体就是处于WMS服务中,最终就会调用到这个Session的addToDisplay方法,从方法名就可以猜到这个方法就是具体添加Window到屏幕的逻辑,具体就不分析了,下次说到屏幕绘制的时候再细谈。

2)updateViewLayout

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {

//…

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

view.setLayoutParams(wparams);

synchronized (mLock) {

int index = findViewLocked(view, true);

ViewRootImpl root = mRoots.get(index);

mParams.remove(index);

mParams.add(index, wparams);

root.setLayoutParams(wparams, false);

}

}

这里更新了WindowManager.LayoutParamsViewRootImpl.LayoutParams,然后在ViewRootImpl内部同样会重新对View进行绘制,最后通过IPC通信,调用到WMS的relayoutWindow完成更新。

3)removeView

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);

View curView = mRoots.get(index).getView();

removeViewLocked(index, immediate);

if (curView == view) {

return;

}

throw new IllegalStateException("Calling with view " + view

  • " but the ViewAncestor is attached to " + curView);

}

}

private void removeViewLocked(int index, boolean immediate) {

ViewRootImpl root = mRoots.get(index);

View view = root.getView();

if (view != null) {

InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);

if (imm != null) {

imm.windowDismissed(mViews.get(index).getWindowToken());

}

}

boolean deferred = root.die(immediate);

if (view != null) {

view.assignParent(null);

if (deferred) {

mDyingViews.add(view);

}

}

}

该方法中,通过view找到mRoots中的对应索引,然后同样走到ViewRootImpl中进行View删除工作,通过die方法,最终走到dispatchDetachedFromWindow()方法中,主要做了以下几件事:

  • 回调onDetachedFromeWindow。

  • 垃圾回收相关操作;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

去过华为、OPPO等大厂,18年进入阿里一直到现在。**

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-t8arpDgC-1710921956195)]
[外链图片转存中…(img-ai1mHRhx-1710921956195)]
[外链图片转存中…(img-CrL0Vlgz-1710921956196)]
[外链图片转存中…(img-UClbq8P2-1710921956197)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-PtT4J4rC-1710921956197)]

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值