Android WMS基础与原理解析
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android WMS基础与原理解析相关的知识,希望对你有一定的参考价值。
前言
上班第一天摸摸鱼🤣,由于前几天录制了一个关于android WMS核心原理解析的视频,刚好整理了一些关于这个视频的文档。
关注公众号:初一十五a
解锁 《Android十大板块文档》,让学习更贴近未来实战。已形成PDF版
内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
开始进入正题,ღ( ´・ᴗ・` ) 🤔
一丶WMS基础:WMS与activity启动流程
主要通过
Activity
的启动过程来看Activity
,PhoneWindow
,View
,DecoView
,ViewRootImpl
这几者之间的关系
1.1.view的绘制调用示意图
1.2.View的绘制时序图
整个调用过程
1.3.从Activity启动过程来看PhoneWindow,DecoView,ViewRootImpl,View的关系
- 从
Activity
的启动过程可知,Activity
中创建了一个PhoneWindow
(PhoneWindow
是Android中唯一实现抽象类Window的类,除了Activity
创建了PhoneWindow
, 还有Dialog
,和PhoneWindowManage
) 故一个Activtity
至少拥有一个Window setWindowManager
设置的windowManager
是哪个类呢?
//Activity.java中的的attch方法中
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
//注册的地方frameworks/base/core/java/android/app/SystemServiceRegistry.java
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>()
@Override
public WindowManager createService(ContextImpl ctx)
return new WindowManagerImpl(ctx);
);
//看下WindowManagerImpl类
//SystemSerever.java
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore, new PhoneWindowManager());
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
//-------------------------
//IWindowManager.aidl
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public class WindowManagerService extends IWindowManager.Stub
可知WindowManagerService.java
是实现部分
二丶WMS基础:WMS绘制原理
理解 ViewRootImpl
简介
ViewRootImpl
是 View 的最高层级,是所有 View 的根。ViewRootImpl
实现了 View 和 WindowManager
之间所需要的协议。ViewRootImpl
的创建过程是从 WindowManagerImpl
中开始的。View 的测量,布局,绘制以及上屏,都是从 ViewRootImpl
中开始的。
我们通过一张图来认识一下它:
Window
我们知道界面中所有的元素都是由 View 构成的,View 是依附于 Window 上面的。Window 只是一个抽象概念,把界面抽象成一个 窗口,也可以抽象成一个 View。
ViewManange
一个接口,内部定义了三个方法,用来对 VIew 的添加,更新和删除。
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
也是一个接口,继承自 ViewManager
,在应用程序中,通过 WindowManager
来管理 Window,将 View 附加到 Window 上。他有一个实现类 WindowManagerImpl
。
WindowManagerImpl
WindowManager
的实现类,WindowManagerImpl
中的内部方法实现都是通过代理类 WindowManagerGlobal
来完成。
WindowManagerGlobal
WindowManagerGlobal
是一个单例,也就是说一个进程中只有一个 WindowManagerGlobal
对象,他服务与所有页面的 View。
ViewParent
一个接口,定义了将成为 View 父级类的职责。
ViewRootImpl
视图层次结构的顶部。一个 Window 对应着一个 ViewRootImpl
和 一个 VIew。这个 View 就是被 ViewRootImpl
操作的。
一个小栗子,我们都只 Actiivty
中 会创建一个 Window 对象。 setContentView
方法中的 View 最终也会被添加到 Window 对象中的 DecorView
中,也就是说一个 Window 中对应着一个 View。这个 View 是被 RootViewImpl
操作的。
WindowManager
就是入口。通过 WindowManager
的 addView 添加一个 Window(也可以理解为 View),然后就会创建一个 ViewRootImpl,来对 view 进行操作,最后将 View 渲染到屏幕的窗口上。
例如 Activity
中,在 onresume
执行完成后,就会获取 Window 中的 DecorView
,然后通过 WindowManager
把 DecorView
添加到窗口上,这个过程中是由 RootViewImpl
来完成测量,绘制,等操作的。
2.1 RootViewImpl 的初始化
View 的三大流程都是通过 RootViewImpl
来完成的,在 ActivityThread
中,当 Activity
对象被创建完毕后,在 onResume
后,就会通过 WindowManager
将 DecorView
添加到窗口上,在这个过程中会创建 ViewRootImpl
:
ActivityThread.handleResumeActivity
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason)
// .....
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible)
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//设置窗口类型为应用类型
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient)
if (!a.mWindowAdded)
a.mWindowAdded = true;
//添加 decor 到 window 中
wm.addView(decor, l);
else
a.onWindowAttributesChanged(l);
//....
2.2 WindowManagerImpl.addView添加View
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params)
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
2.3 WindowManagerGlobal.addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow)
//检查参数是否合法
if (view == null)
throw new IllegalArgumentException("view must not be null");
if (display == null)
throw new IllegalArgumentException("display must not be null");
if (!(params instanceof WindowManager.LayoutParams))
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//.....
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock)
//创建 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//将 Window 所对应的 View,ViewRootImp,params 顺序添加到列表中,这一步是为了方便更新和删除 View
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try
//把 Window 对应的 View 设置给创建的 ViewRootImpl
//通过 ViewRootImpl 来更新界面并添加到 WIndow中。
root.setView(view, wparams, panelParentView);
catch (RuntimeException e)
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0)
removeViewLocked(index, true);
throw e;
2.4 ViewRootImpl 对 View 进行操作
ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
synchronized (this)
if (mView == null)
mView = view;
attrs = mWindowAttributes;
//请求布局,执行 View 的绘制方法
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)
mInputChannel = new InputChannel();
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//将 Window 添加到屏幕上,mWindowSession 实现了 IWindowSession接口,是 Session 的 Binder 对象。
// addToDisplay 是一次 AIDL 的过程,通知 WindowManagerServer 添加 Window
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
//设置当前 View 的 Parent
view.assignParent(this);
2.5 ViewRootImpl.requestLayout
//请求布局
public void requestLayout()
if (!mHandlingLayoutInLayoutRequest)
checkThread();
mLayoutRequested = true;
scheduleTraversals();
//检测当前线程,如果不是主线程,直接抛出异常
void checkThread()
if (mThread != Thread.currentThread())
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
final class TraversalRunnable implements Runnable
@Override
public void run()
doTraversal();
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void scheduleTraversals()
if (!mTraversalScheduled)
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch)
scheduleConsumeBatchedInput();
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
void doTraversal()
if (mTraversalScheduled)
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile)
Debug.startMethodTracing("ViewAncestor");
performTraversals();
if (mProfile)
Debug.stopMethodTracing();
mProfile = false;
在上面代码中,调用 requestLayout
请求布局,最终会执行到 performTraversals
方法中。在这个方法中会调用 View
的 measure()
,layout()
,draw()
方法。
2.6 ViewRootImpl. performTraversals
View 的测量
ViewRootImpl
调用 performMeasure
执行 Window 对应的 View 测量。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)
if (mView == null)
return;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
finally
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
在 performMeasure
方法中就会执行 View 的 measure 方法,在其中会计算一下约束信息,然后就会调用 onMeasure 方法.
2.7 View 的布局
ViewRootImpl
调用 performLayout
执行 Window 对应 View 的布局
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight)
mScrollMayChange = true;
mInLayout = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try
// 调用 layout 方法进行布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0)
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null)
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i)
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
// 请求对该 View 进行布局,该操作会导致此 View 被重新测量,布局,绘制
view.requestLayout();
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null)
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable()
@Override
public void run()
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i)
final View view = finalRequesters.get(i);
// 请求对该 View 进行布局,该操作会导致此 View 被重新测量,布局,绘制
view.requestLayout();
);
finally
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
mInLayout = false;
通过上面代码可以看出,在 layout 布局期间,有可能会对 View 进行 requestLayout
重新进行测量,布局,绘制。
2.8 View 的绘制
ViewRootImpl
通过调用 performDraw
执行 Window 对应 View 的绘制。
private void performDraw()
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw)
return;
else if (mView == null)
return;
final boolean fullRedrawNeeded =
mFullRedrawNeeded || mReportNextDraw || mNextDrawUseBlastSync;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded();
addFrameCallbackIfNeeded();
try
//绘制
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync)
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
finally
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
//...
2.9.Draw方法
private boolean draw(boolean fullRedrawNeeded)
//........
//通知 View 上注册的绘制事件
mAttachInfo.mTreeObserver.dispatchOnDraw();
boolean useAsyncReport = false;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync)
if (isHardwareEnabled())
//调用 Window 对应 View 的 invalidate 方法
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
else
// 绘制 View
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets))
return false;
if (animating)
mFullRedrawNeeded = true;
scheduleTraversals();
return useAsyncReport;
void invalidate()
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon)
scheduleTraversals();
2.10.drawSoftware方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets)
// Draw with software renderer.
final Canvas canvas;
try
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
try
if (DEBUG_ORIENTATION || DEBUG_DRAW)
Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
// View 绘制
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
finally
return true;
2.11.WMS绘制流程
主角:ViewRootImpl、Choreographer、Surfaceflinfer
WMS扮演了什么角色?
作为协调者,协调view布局,绘制;
-
在
ActivityThread
中创建Actiivty
后,调用activity.attach()
时,创建一个窗体对象PhoneWindow
-
PhoneWindow
创建了一个WMS的代理桥接类WindowManagerImpl
对象,作为WMS在app中的代表; -
WindowManagerImpl
对象中的(mGlobal)WindowManagerGlobal
专门和WMS通信,在mGlobal里面获取了到了WMS的Binder:getWindowSession()->WMS::openSession();
setContentView()
-
调用
PhoneWindow.setContentView(resouseID)
-
PhoneWindow
中:创建mDector
:窗体上的整个View:里面有官方的主题布局+用户自己的布局; -
PhoneWindow
中:创建mContentParent
:官方主题布局中提供给用户装载布局的容器:id为content
; -
调用
mLayoutInflater.inflater(resouseID,mContentParent)
: -
解析用户的布局xml
-
递归调用:解析根布局,通过反射创建根布局;解析子view,通过反射创建view;
-
最后PhoneWindow中的
mContentParent
加载用户的根布局; -
提交view数据
ps:这里递归调用,若嵌套层级太多,会导致栈溢出;因为递归调用不会释放栈;
ViewRootImpl 单例,管理所有View的绘制策略;
注意onCreate.setContentView
后view数据已解析并实例化了;
-
在状态机为
Resume
时: -
调用
WindowManagerImpl
中的mGlobal.addView(view)
-
addView
中创建ViewRootImpl root=new ViewRootImpl()
: -
root.setView(view);
-
在setView总调用
requestLayout()
-
requestLayout()
请求绘制,编舞者出场
帧速率: CPU/GPU出图速率;
刷新率: 屏幕刷新速率;
-
帧速率>刷新率时,出现丢帧(出图好多张了,但是只显示了开头和结尾两张,中间的丢了)
-
帧速率<刷新率,出现卡顿(屏幕刷新好多次了,但是还是显示的第一帧)
Vsync: 垂直同步绘制信号; 因可能硬件帧速率和刷新率不一致,用来同步刷新的问题;
Choreographer编舞者: 负责管理帧率节奏;
-
在内部维护了个Handler和Looper,保证绘制发生在UI主线程:
Looper.myLooper==mLooper
判断是否是主线程,是的话去调同步绘制信号,不是的话发送消息,走主线程去调同步绘制信号 -
走native层请求垂直同步信号,实际是找底层驱动要上次绘制的时间
-
请求到垂直同步信号后回调
onVsync
-
走
doFrame
去逻辑管控, 判断当前时间离上次绘制的时间大于了1帧的时间(16.66毫秒) 就跳帧(卡顿优化有用到),若小于16.66毫秒就再次请求垂直同步信号,防止重叠 -
执行callback,让
ViewRootImpl
去真正绘制,调用ViewRootImpl.performTraversals()
真正的绘制: ViewRootImpl.performTraversals()
-
调用
relayoutWindow()
: -
创建用户java层的
surface
:只有用户提供的画面数据; -
创建native层的
surface
:包含用户提供的画面数据(java层的surface
)+系统的画面数据(状态栏,电池、wifi等等); -
创建完
surface
后:依次调用:performMeasure(对应view的onMeasure)、performLayout(onLayout)、performDraw(onDraw);
在performDraw()
中:
-
将view的数据传至native层的
surface
-
surface
中的canvas
记录数据 -
生成
bitmap
图像数据(此时数据是在surface
中) -
将
surface
放入队列中;生产者消费者模式; -
通知
surfaceflinfer
进程去队列中取surface
数据 -
surfaceflinfer
拿到不同的surface
,进行融合,生成bitmap
数据 -
将
bitmap
数据放入framebuffer
中,进行展示
简单版总结: Activity.setContentView(R.layout.resId)
:
解析xml并实例化;
-
调用
phoneWindow.setContentView(resId)
-
在
setContentView
中调用installDector()
:根据不同的主题,找到系统默认的xml,初始化出mDector
和mContentParent
(反射实例化出对应的ViewGroup
) -
初始化完成后,调用
mLayoutInflater.inflate(resId,mContentParent)
: -
解析
resId
的xml文件,将解析的view反射实例化;递归添加到各节点的viewgroup
中;最后将自己定义的xml根布局view添加到mContentParent;
绘制发生时间: 在AMS回调ActivityThread中的handleResumeActivity
时,也就是Resume时,而不是onCreate()
;
-
获取
PhoneWindow
-
获取
PhoneWindow
中的mDector
布局视图view -
将
mDector
布局视图view
传给ViewRootImpl
-
ViewRootImpl
中调用requestLayout()
-
requestLayout()
中依次调用:performMeasure()、performLayout()、performDraw()
三丶WMS原理:WMS角色与实例化过程
- window:它是一个抽象类,具体实现类为
PhoneWindow
,它对 View 进行管理。Window是View的容器,View是Window的具体表现内容; - windowManager:是一个接口类,继承自接口
ViewManager
,从它的名称就知道它是用来管理 Window 的,它的实现类为WindowManagerImpl;
- WMS:是窗口的管理者,它负责窗口的启动、添加和删除。另外窗口的大小和层级也是由它进行管理的;
三者之间的关系:
3.1 Window
Window分类
1.Application Window(应用窗口):例如Activity,Dialog;【取值范围为1~99】
2.Sub Window(子窗口):不能独立存在,需要依附在其他窗口,例如PopupWindow【取值范围为1000~1999】;
3.System Window(系统窗口):例如Toast、输入法窗口、系统音量条窗口、系统错误窗口等【2000~2999】;
3.2.Window显示层级
窗口相关标志
1.
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
(当Window可见时允许锁屏)2.
FLAG_NOT_FOCUSABLE
(Window 不能获得输入焦点,即不接受任何按键或按钮事件,例如该 Window 上 有 EditView,点击 EditView 是 不会弹出软键盘的,Window 范围外的事件依旧为原窗口处理;例如点击该窗口外的view,依然会有响应。另外只要设置了此Flag,都将会启用FLAG_NOT_TOUCH_MODAL
)3.
FLAG_NOT_TOUCH_MODAL
(设置了该 Flag,将 Window 之外的按键事件发送给后面的 Window 处理, 而自己只会处理 Window 区域内的触摸事件;Window 之外的 view 也是可以响应 touch 事件。4.
FLAG_NOT_TOUCHABLE
(设置了该Flag,表示该 Window 将不会接受任何 touch 事件,例如点击该 Window 不会有响应,只会传给下面有聚焦的窗口)5.
FLAG_KEEP_SCREEN_ON
(只要 Window 可见时屏幕就会一直亮着,视频播放、游戏)6.
FLAG_LAYOUT_NO_LIMITS
(允许 Window 超过屏幕之外)7.
FLAG_IGNORE_CHEEK_PRESSES
(当用户的脸贴近屏幕时(比如打电话),不会去响应此事件 )
FLAG_SHOW_WHEN_LOCKED
(当用户的脸贴近屏幕时(比如打电话),不会去响应此事件 9.FLAG_IGNORE_CHEEK_PRESSES
; 窗口可以在锁屏的 Window 之上显示, 使用Activity#setShowWhenLocked(boolean)
方法代替)
3.3. WindowManager
在了解WindowManager
管理View实现之前,先了解下WindowManager
相关类图以及Activity
界面各层级显示关系;
3.4.addView实现流程
3.5.removeView流程
3.6.重要类简介:
- WindowManagerImpl:
WindowManager
的唯一实现类; - WindowManagerGlobal:窗口管理操作,所有
Activity
都是通过这个进程内唯一的WindowManagerGlobal
对象和WMS通信; - ViewRootImpl:
- View树的树根并管理View树;
- 触发View的测量、布局以及绘制;
- 输入相应的中转站;
- 负责与WMS进行进程间通信;
3.7.TextView.setText刷新流程
ViewRootImpl
会调用scheduleTraversals
准备重绘,但是,重绘一般不会立即执行,而是往Choreographer
的Choreographer.CALLBACK_TRAVERSAL
队列中添加了一个mTraversalRunnable
,同时申请VSYNC
,这个mTraversalRunnable
要一直等到申请的VSYNC
到来后才会被执行;
3.8.小结
android一般60fps,是VSYNC
信号决定的,【每16ms刷新一帧(连续调用setText
只会触发一次重绘)】,VSYNC
信号要客户端主动申请,才会有VSYNC
刷新,UI没更改,不会请求VSYNC
信号进行刷新;
四丶WMS原理:WMS工作原理
4.1.WMS的职责
4.2.WMS中重要的一些属性释义
从WMS类中包含的属性也可以看出WMS的主要职责,窗口管理、窗口动画以及监听输入进行事件派发。
WMS中addWindow源码分析
在分析addWindow之前,先了解几个类;
WindowToken:WindowToken
具有令牌的作用,是对应用组件的行为进行规范管理的一个手段。WindowToken
由应用组件或其管理者负责向WMS声明并持有。应用组件在需要新的窗口时,必须提供WindowToken
以表明自己的身份,并且窗口的类型必须与所持有的WindowToken
的类型一致,同时它将属于同一个应用组件的窗口组织在了一起;
DisplayContent:如果说WindowToken
按照窗口之间的逻辑关系将其分组,那么DisplayContent
则根据窗口的显示位置将其分组。隶属于同一个DisplayContent
的窗口将会被显示在同一个屏幕中。每一个DisplayContent
都对应这一个唯一的ID,在添加窗口时可以通过指定这个ID决定其将被显示在那个屏幕中。DisplayContent
是一个非常具有隔离性的一个概念。处于不同DisplayContent
的两个窗口在布局、显示顺序以及动画处理上不会产生任何耦合。因此,就这几个方面来说,DisplayContent
就像一个孤岛,所有这些操作都可以在其内部独立执行。因此,这些本来属于整个WMS全局性的操作,变成了DisplayContent
内部的操作了。
**WindowState:**表示一个窗口的所有属性,所以它是WMS中事实上的窗口;当向WMS添加一个窗口时,WMS会为其创建一个WindowState
。另外WMS.addWindow()
函数中看到新的WindowState
被保存到mWindowMap
中,键值为IWindow
的Bp端。mWindowMap
是整个系统所有窗口的一个全集。
WMS.addWindow()
流程浅析
.......
//窗口检查,对要添加的窗口进行检查,如果窗口不满足条件,则直接返回
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,appOp);
if (res != WindowManagerGlobal.ADD_OKAY)
return res;
.......
//WindowToken相关处理,有的窗口类型需要提供WindowToken
WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
// If this is a child window, we want to apply the same type checking rules as the
// parent window type.
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
boolean addToastWindowRequiresToken = false;
if (token == null)
if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,
rootType, attrs.token, attrs.packageName))
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
token = new WindowToken(this, binder, type, false, displayContent, session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
else if (rootType >= FIRST_APPLICATION_WINDOW
&& rootType <= LAST_APPLICATION_WINDOW)
......
//创建windowState
final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], seq, attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);
......
//调用WMP的方法,此方法会根据窗口的Type对LayoutParams的一些成员进行修改
displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);
.......
// 将窗口添加到系统中
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
.......
// windowState保存到Map中
mWindowMap.put(client.asBinder(), win);
.......
// 绑定Token和WindowState关系
win.mToken.addWindow(win);
4.3. WMS.removeWindow()流程
void removeWindow(Session session, IWindow client)
synchronized (mGlobalLock)
//获取WindowState
WindowState win = windowForClientLocked(session, client, false);
if (win != null)
//执行删除
win.removeIfPossible();
return;
// Remove embedded window map if the token belongs to an embedded window
mEmbeddedWindowController.remove(client);
win.removeIfPossible
方法和它的名字一样, 并不是直接执行删除操作,而是进行多个条件判断过滤,满足其中一个条件就会return,推迟删除操作。比如View正在运行一个动画,这是就会推迟删除操作直到动画完成。然后调用removeImmediately
方法。
4.4.事件派发
事件输入监听以及分发
EventHub:
1.使用inotify
监听输入设备的添加和移除;
2.使用epoll
机制监听输入设备的数据变化;
3.读取设备文件数据;
4.将原始数据返回给InputReader;
InputReader:不断读取由EventHub
监听到的input
事件,将多个事件组合成一个可供上层消费的事件(比如将一组触摸事件合并成一个action_down
事件),然后交给InputDispatcher
进行事件分发;
InputDispatcher:拿到InputReader
获取的事件后,对事件进行包装,寻找并分发到目标窗口,对应inputChannel
输入;
Android系统是由事件驱动的,而input
是常见的事件之一,点击、滑动、长按等操作,都属于input
事件,核心类就是InputReader
和InputDispatcher;
解释:
ViewRootImpl#WindowInputEventReceiver
:从下面源码可以看出,此类用于InputChannel
输入事件接收以及处理输入事件分发;
final class WindowInputEventReceiver extends InputEventReceiver
//构造方法,接受inputchannel输入事件
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper)
super(inputChannel, looper);
@Override
public void onInputEvent(InputEvent event)
...
if (processedEvents != null)
if (processedEvents.isEmpty())
// InputEvent consumed by mInputCompatProcessor
finishInputEvent(event, true);
else
for (int i = 0; i < processedEvents.size(); i++)
//对输入事件进行分发
enqueueInputEvent(
processedEvents.get(i), this,
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
else
enqueueInputEvent(event, this, 0, true);
ViewRootImpl#InputStage
:抽象类,主要用来将事件的处理分成若干个阶段(stage)进行,如果该事件没有被处理,则该stage
就会调用onProcess
方法处理,然后调用forward
执行下一个stage
的处理;如果该事件被标识为处理则直接调用forward
,执行下一个stage的处理,直到没有下一个stage;
ViewPostImeInputStage:InputStage
的子类,将输入事件传递到上层视图;至此,输入事件一层层向上传递,最终交由具体的view进行处理;
关注公众号:初一十五a
解锁 《Android十大板块文档》,让学习更贴近未来实战。已形成PDF版
内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
以上是关于Android WMS基础与原理解析的主要内容,如果未能解决你的问题,请参考以下文章
Android Binder原理ServiceManager的启动过程
Android Binder原理学习Binder前必须要了解的知识点