2024年Android 深入了解 Window 、Activity、 View 三者关系,面试官打击人的套路

总结

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下~

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

@Override

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

applyDefaultToken(params);

mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,

mContext.getUserId());

}

WindowManagerImpl.addView也是一个空壳,它调用了 WindowManagerGlobaladdView 方法。

WindowManagerGlobal.addView

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

Display display, Window parentWindow, int userId) {

ViewRootImpl root;

View panelParentView = null;

synchronized (mLock) {

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

//注释1

view.setLayoutParams(wparams);

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

//最后执行此操作,因为它会发出消息开始执行操作

try {

//注释2

root.setView(view, wparams, panelParentView, userId);

} catch (RuntimeException e) {

}

}

}

注释1:WindowMangerGlobal 是一个单例 在 addView 方法中,创建了一个最关键的 ViewRootImpl 对象。

注释2:然通过 root.setView 方法将 view 添加到 WMS 中。

ViewRootImpl.setView


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

int userId) {

synchronized (this) {

if (mView == null) {

mView = view;

int res; /* = WindowManagerImpl.ADD_OKAY; */

//注释1

requestLayout();

InputChannel inputChannel = null;

if ((mWindowAttributes.inputFeatures

& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {

inputChannel = new InputChannel();

}

mForceDecorViewVisibility = (mWindowAttributes.privateFlags

& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;

try {

//注释2

res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mDisplayCutout, inputChannel,

mTempInsets, mTempControls);

setFrame(mTmpFrame);

} catch (RemoteException e) {

} finally {

if (restore) {

attrs.restore();

}

}

}

}

注释1: requestLayout 是刷新布局的操作,调用此方法后 ViewRootImpl 所关联的 View 也执行 measure -> layout -> draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。

注释2:调用 mWindowSession 的 addToDisplay 方法将 View 添加到 WMS 中。

mWindowSession哪里来的?他是new RootViewlmpl对象是传入进来的,调用WindowManagerGlobal.getWindowSession()生成的。

new ViewRootImpl(context,display);


public ViewRootImpl(Context context, Display display) {

this(context, display, WindowManagerGlobal.getWindowSession(),false);

}

WindowManagerGlobal.getWindowSession()


WindowSession 是 WindowManagerGlobal 中的单例对象,初始化代码如下:

public static IWindowSession getWindowSession() {

synchronized (WindowManagerGlobal.class) {

if (sWindowSession == null) {

try {

InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();

IWindowManager windowManager = getWindowManagerService();

//注释

sWindowSession = windowManager.openSession(

new IWindowSessionCallback.Stub() {

@Override

public void onAnimatorScaleChanged(float scale) {

ValueAnimator.setDurationScale(scale);

}

});

} catch (RemoteException e) {

throw e.rethrowFromSystemServer();

}

}

return sWindowSession;

}

}

注释:sWindowSession 实际上是 IWindowSession 类型,是一个 Binder 类型,真正的实现类是 System 进程中的 Session。用 AIDL 获取 System 进程中 Session 的对象。

Session.addToDisplay


@Override

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,

int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,

Rect outStableInsets,

DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,

InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {

return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,

outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,

outInsetsState, outActiveControls, UserHandle.getUserId(mUid));

}

return mService.addWindow(…);

其中的 mService 就是 WMS。至此,Window 已经成功的被传递给了 WMS。剩下的工作就全部转移到系统进程中的 WMS 来完成最终的添加操作。

又回到Activity

===========

addView 成功有一个标志就是能够接收触屏事件,通过对 setContentView 流程的分析,可以看出添加 View 的操作实质上是 PhoneWindow 在全盘操作,背后负责人是 WMS,反之 Activity 自始至终没什么参与感。但是我们也知道当触屏事件发生之后,Touch 事件首先是被传入到 Activity,然后才被下发到布局中的 ViewGroup 或者 View(Touch事件分发了解一下)。那么 Touch 事件是如何传递到 Activity 上的呢?

ViewRootImpl 中的 setView 方法中,除了调用 IWindowSession 执行跨进程添加 View 之外,还有一项重要的操作就是设置输入事件的处理:

ViewRootImpl.setView


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

int userId) {

res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mDisplayCutout, inputChannel,

mTempInsets, mTempControls);

// 注释:设置输入管道。

CharSequence counterSuffix = attrs.getTitle();

mSyntheticInputStage = new SyntheticInputStage();

InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);

InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,

“aq:native-post-ime:” + counterSuffix);

InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);

InputStage imeStage = new ImeInputStage(earlyPostImeStage,

“aq:ime:” + counterSuffix);

InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);

InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,

“aq:native-pre-ime:” + counterSuffix);

}

注释:设置了一系列的输入通道。一个触屏事件的发生是由屏幕发起,然后经过驱动层一系列的优化计算通过 Socket 跨进程通知 Android Framework 层(实际上就是 WMS),最终屏幕的触摸事件会被发送到代码中的输入管道中。

这些输入管道实际上是一个链表结构,当某一个屏幕触摸事件到达其中的 ViewPostImeInputState 时,会经过 onProcess 来处理,如下所示:

ViewRootImpl.ViewPostImeInputStage


final class ViewPostImeInputStage extends InputStage {

public ViewPostImeInputStage(InputStage next) {

super(next);

}

@Override

protected int onProcess(QueuedInputEvent q) {

if (q.mEvent instanceof KeyEvent) {

return processKeyEvent(q);

} else {

final int source = q.mEvent.getSource();

if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {

return processPointerEvent(q);

} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {

return processTrackballEvent(q);

} else {

return processGenericMotionEvent(q);

}

}

}

private int processPointerEvent(QueuedInputEvent q) {

final MotionEvent event = (MotionEvent)q.mEvent;

boolean handled = mView.dispatchPointerEvent(event);

maybeUpdatePointerIcon(event);

maybeUpdateTooltip(event);

mAttachInfo.mHandlingPointerEvent = false;

return handled ? FINISH_HANDLED : FORWARD;

}

}

processPointerEvent 在 ViewPostImeInputStage 中别找错了。可以看到在 onProcess 中最终调用了一个 mView的dispatchPointerEvent 方法,mView 实际上就是 PhoneWindow 中的 DecorView,而 dispatchPointerEvent 是被 View.java 实现的,如下所示:

View.dispatchPointerEvent

public final boolean dispatchPointerEvent(MotionEvent event) {

if (event.isTouchEvent()) {

return dispatchTouchEvent(event);

} else {

return dispatchGenericMotionEvent(event);

}

}

DecorView.dispatchTouchEvent

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

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

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

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

}

最好调用了 Window.Callback 中 cb.dispatchTouchEvent(ev) 方法,那这个 Callback 是不是 Activity 呢?

Activity.attach


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, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {

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

mWindow.setWindowControllerCallback(mWindowControllerCallback);

//this:Activity

mWindow.setCallback(this);

}

Activity 将自身传递给了 PhoneWindow,再接着看 Activity的dispatchTouchEvent。

Activity.dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {

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

//该方法是用户交互,每当向Activity分派按键、触摸或轨迹球事件时调用。

//在这里仅用于ACTION_DOWN的判断

onUserInteraction();

}

//返回true

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

//Activity.dispatchTouchEvent()就返回true,则方法结束。

//该点击事件停止往下传递&事件传递过程结束

return true;

}

return onTouchEvent(ev);

}

PhoneWindow.superDispatchTouchEvent

@Override

public boolean superDispatchTouchEvent(MotionEvent event) {

return mDecor.superDispatchTouchEvent(event);

}

DecorView.superDispatchTouchEvent

public boolean superDispatchTouchEvent(MotionEvent event) {

return super.dispatchTouchEvent(event);

}

Touch 事件在 Activity 中只是绕了一圈最后还是回到了 PhoneWindow 中的 DecorView 来处理。 剩下的就是从 DecorView 开始将事件层层传递给内部的子 View 中了

ViewGroup. dispatchTouchEvent

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

}

到这里就不做多的重复了。感兴趣的可以看看事件分发机制

小结

==

通过 setContentView 的流程,分析了 Activity、Window、View 之间的关系。整个过程 Activity 表面上参与度比较低,Activity持有Window的对象,View在Window上的增删等操作又是通过WindowManager来管理的,而WindowManager又是通过Binder机制获取到的WMS的映射,WMS把View真正显示到屏幕上。

三者关系:

  1. 一个 Activity 中有一个 window,也就是 PhoneWindow 对象,在Activity中调用attach,创建了一个PhoneWindow。
  1. 在 PhoneWindow 中有一个 DecorView,Activity在 调用setContentView 中会将 layout 填充到此 DecorView 中。
【附】相关架构及资料

源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,和技术大牛一起讨论交流解决问题。

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ty 表面上参与度比较低,Activity持有Window的对象,View在Window上的增删等操作又是通过WindowManager来管理的,而WindowManager又是通过Binder机制获取到的WMS的映射,WMS把View真正显示到屏幕上。

三者关系:

  1. 一个 Activity 中有一个 window,也就是 PhoneWindow 对象,在Activity中调用attach,创建了一个PhoneWindow。
  1. 在 PhoneWindow 中有一个 DecorView,Activity在 调用setContentView 中会将 layout 填充到此 DecorView 中。
【附】相关架构及资料

源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,和技术大牛一起讨论交流解决问题。

[外链图片转存中…(img-MGHaXhRL-1715585507980)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值