Android面试官:Window连环十二问你顶得住吗?,kotlin实例化

可以看到,在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。

  • 垃圾回收相关操作;

  • 通过Session的remove()在WMS中删除Window;

  • 通过Choreographer移除监听器

Activity、PhoneWindow、DecorView、ViewRootImpl 的关系?

看完上面的流程,我们再来理理这四个小伙伴之间的关系:

  • PhoneWindow 其实是 Window 的唯一子类,是 Activity 和 View 交互系统的中间层,用来管理View的,并且在Window创建(添加)的时候就新建了ViewRootImpl实例。

  • DecorView 是整个 View 层级的最顶层,ViewRootImpl是DecorView 的parent,但是他并不是一个真正的 View,只是继承了ViewParent接口,用来掌管View的各种事件,包括requestLayout、invalidate、dispatchInputEvent 等等。

Android面试官:Window连环十二问你顶得住吗?,kotlin实例化

Window中的token是什么,有什么用?

token?又是个啥呢?刚才window操作过程中也没出现啊。

token其实大家应该工作中会发现一点踪迹,比如application的上下文去创建dialog的时候,就会报错:

unable to add window --token null

所以这个token跟window操作是有关系的,翻到刚才的addview方法中,还有个细节我们没说到,就是adjustLayoutParamsForSubWindow方法。

//Window.java

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {

if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&

wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

//子Window

if (wp.token == null) {

View decor = peekDecorView();

if (decor != null) {

wp.token = decor.getWindowToken();

}

}

} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&

wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {

//系统Window

} else {

//应用Window

if (wp.token == null) {

wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;

}

}

}

上述代码分别代表了三个Window的类型:

  • 子Window。需要从decorview中拿到token。

  • 系统Window。不需要token。

  • 应用Window。直接拿mAppToken,mAppToken是在setWindowManager方法中传进来的,也就是新建Window的时候就带进来了token。

然后在WMS中的addWindow方法会验证这个token,下次说到WMS的时候再看看。

所以这个token就是用来验证是否能够添加Window,可以理解为权限验证,其实也就是为了防止开发者乱用context创建window。

拥有token的context(比如Activity)就可以操作Window。没有token的上下文(比如Application)就不允许直接添加Window到屏幕(除了系统Window)。

Application中可以直接弹出Dialog吗?

这个问题其实跟上述问题相关:

  • 如果直接使用Application的上下文是不能创建Window的,而Dialog的Window等级属于子Window,必须依附与其他的父Window,所以必须传入Activity这种有window的上下文。

  • 那有没有其他办法可以在Application中弹出dialog呢?有,改成系统级Window:

//检查权限

if (!Settings.canDrawOverlays(this)) {

val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)

intent.data = Uri.parse(“package:$packageName”)

startActivityForResult(intent, 0)

}

dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG)

  • 另外还有一种办法,在Application类中,可以通过registerActivityLifecycleCallbacks监听Activity生命周期,不过这种办法也是传入了Activity的context,只不过在Application类中完成这个工作。

关于事件分发,事件到底是先到DecorVi

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

ew还是先到Window的?

经过上述一系列问题,是不是对Window印象又深了点呢?最后再看一个问题,这个是wanandroid论坛上看到的,

这里的window可以理解为PhoneWindow,其实这道题就是问事件分发在Activity、DecorView、PhoneWindow中的顺序。

当屏幕被触摸,首先会通过硬件产生触摸事件传入内核,然后走到FrameWork层(具体流程感兴趣的可以看看参考链接),最后经过一系列事件处理到达ViewRootImpl的processPointerEvent方法,接下来就是我们要分析的内容了:

//ViewRootImpl.java

private int processPointerEvent(QueuedInputEvent q) {

final MotionEvent event = (MotionEvent)q.mEvent;

//mView分发Touch事件,mView就是DecorView

boolean handled = mView.dispatchPointerEvent(event);

}

//DecorView.java

public final boolean dispatchPointerEvent(MotionEvent event) {

if (event.isTouchEvent()) {

//分发Touch事件

return dispatchTouchEvent(event);

} else {

return dispatchGenericMotionEvent(event);

}

}

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

//cb其实就是对应的Activity

final Window.Callback cb = mWindow.getCallback();

return cb != null && !mWindow.isDestroyed() && mFeatureId < 0

? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);

}

//Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {

if (ev.getAction() == MotionEvent.ACTION_DOWN) {

onUserInteraction();

}

if (getWindow().superDispatchTouchEvent(ev)) {

return true;

}

return onTouchEvent(ev);

}

//PhoneWindow.java

@Override

public boolean superDispatchTouchEvent(MotionEvent event) {

return mDecor.superDispatchTouchEvent(event);

}

//DecorView.java

public boolean superDispatchTouchEvent(MotionEvent event) {

return super.dispatchTouchEvent(event);

}

事件的分发流程就比较清楚了:

ViewRootImpl——>DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup

(这其中就用到了getCallback参数,也就是之前addView中传入的callback,也就是Activity本身)

但是这个流程确实有些奇怪,为什么绕来绕去的呢,光DecorView就走了两遍。

参考链接中的说法我还是比较认同的,主要原因就是解耦。

  • ViewRootImpl并不知道有Activity这种东西存在,它只是持有了DecorView。所以先传给了DecorView,而DecorView知道有AC,所以传给了AC。

  • Activity也不知道有DecorView,它只是持有PhoneWindow,所以这么一段调用链就形成了。

Android面试官:Window连环十二问你顶得住吗?,kotlin实例化

面试复习路线

多余的话就不讲了,接下来将分享面试的一个复习路线,如果你也在准备面试但是不知道怎么高效复习,可以参考一下我的复习路线,有任何问题也欢迎一起互相交流,加油吧!

这里给大家提供一个方向,进行体系化的学习:

1、看视频进行系统学习

前几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。

另外,我自己也珍藏了好几套视频,有需要的我也可以分享给你。

2、进行系统梳理知识,提升储备

客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

系统学习方向:

  • **架构师筑基必备技能:**深入Java泛型+注解深入浅出+并发编程+数据传输与序列化+Java虚拟机原理+反射与类加载+动态代理+高效IO

  • **Android高级UI与FrameWork源码:**高级UI晋升+Framework内核解析+Android组件内核+数据持久化

  • **360°全方面性能调优:**设计思想与代码质量优化+程序性能优化+开发效率优化

  • **解读开源框架设计思想:**热修复设计+插件化框架解读+组件化框架设计+图片加载框架+网络访问框架设计+RXJava响应式编程框架设计+IOC架构设计+Android架构组件Jetpack

  • **NDK模块开发:**NDK基础知识体系+底层图片处理+音视频开发

  • **微信小程序:**小程序介绍+UI开发+API操作+微信对接

  • **Hybrid 开发与Flutter:**Html5项目实战+Flutter进阶

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

3、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

主要内含微信 MMKV 源码、AsyncTask 源码、Volley 源码、Retrofit源码、OkHttp 源码等等。

Android面试官:Window连环十二问你顶得住吗?,kotlin实例化

上一篇:2020年最新字节跳动Android开发者常见面试题及详细解析


下一篇:kotlin调用终端命令