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的启动过程来看ActivityPhoneWindow,View,DecoView,ViewRootImpl这几者之间的关系

1.1.view的绘制调用示意图

1.2.View的绘制时序图

整个调用过程

1.3.从Activity启动过程来看PhoneWindow,DecoView,ViewRootImpl,View的关系

  1. Activity的启动过程可知,Activity 中创建了一个PhoneWindow(PhoneWindow是Android中唯一实现抽象类Window的类,除了Activity创建了PhoneWindow, 还有Dialog,和PhoneWindowManage) 故一个Activtity至少拥有一个Window
  2. 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,然后通过 WindowManagerDecorView 添加到窗口上,这个过程中是由 RootViewImpl 来完成测量,绘制,等操作的。

2.1 RootViewImpl 的初始化

View 的三大流程都是通过 RootViewImpl 来完成的,在 ActivityThread 中,当 Activity 对象被创建完毕后,在 onResume 后,就会通过 WindowManagerDecorView 添加到窗口上,在这个过程中会创建 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 方法中。在这个方法中会调用 Viewmeasure()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布局,绘制;

  1. ActivityThread中创建Actiivty后,调用activity.attach()时,创建一个窗体对象PhoneWindow

  2. PhoneWindow创建了一个WMS的代理桥接类WindowManagerImpl对象,作为WMS在app中的代表;

  3. WindowManagerImpl对象中的(mGlobal)WindowManagerGlobal专门和WMS通信,在mGlobal里面获取了到了WMS的Binder:getWindowSession()->WMS::openSession();

setContentView()

  1. 调用PhoneWindow.setContentView(resouseID)

  2. PhoneWindow中:创建mDector:窗体上的整个View:里面有官方的主题布局+用户自己的布局;

  3. PhoneWindow中:创建mContentParent:官方主题布局中提供给用户装载布局的容器:id为content

  4. 调用mLayoutInflater.inflater(resouseID,mContentParent):

  5. 解析用户的布局xml

  6. 递归调用:解析根布局,通过反射创建根布局;解析子view,通过反射创建view;

  7. 最后PhoneWindow中的mContentParent加载用户的根布局;

  8. 提交view数据

ps:这里递归调用,若嵌套层级太多,会导致栈溢出;因为递归调用不会释放栈;

ViewRootImpl 单例,管理所有View的绘制策略;

注意onCreate.setContentView后view数据已解析并实例化了;

  1. 在状态机为Resume时:

  2. 调用WindowManagerImpl中的mGlobal.addView(view)

  3. addView中创建ViewRootImpl root=new ViewRootImpl()

  4. root.setView(view);

  5. 在setView总调用requestLayout()

  6. requestLayout()请求绘制,编舞者出场

帧速率: CPU/GPU出图速率;

刷新率: 屏幕刷新速率;

  1. 帧速率>刷新率时,出现丢帧(出图好多张了,但是只显示了开头和结尾两张,中间的丢了)

  2. 帧速率<刷新率,出现卡顿(屏幕刷新好多次了,但是还是显示的第一帧)

Vsync: 垂直同步绘制信号; 因可能硬件帧速率和刷新率不一致,用来同步刷新的问题;

Choreographer编舞者: 负责管理帧率节奏;

  1. 在内部维护了个Handler和Looper,保证绘制发生在UI主线程:Looper.myLooper==mLooper判断是否是主线程,是的话去调同步绘制信号,不是的话发送消息,走主线程去调同步绘制信号

  2. 走native层请求垂直同步信号,实际是找底层驱动要上次绘制的时间

  3. 请求到垂直同步信号后回调onVsync

  4. doFrame去逻辑管控, 判断当前时间离上次绘制的时间大于了1帧的时间(16.66毫秒) 就跳帧(卡顿优化有用到),若小于16.66毫秒就再次请求垂直同步信号,防止重叠

  5. 执行callback,让ViewRootImpl去真正绘制,调用ViewRootImpl.performTraversals()

真正的绘制: ViewRootImpl.performTraversals()

  1. 调用relayoutWindow()

  2. 创建用户java层的surface:只有用户提供的画面数据;

  3. 创建native层的surface:包含用户提供的画面数据(java层的surface)+系统的画面数据(状态栏,电池、wifi等等);

  4. 创建完surface后:依次调用:performMeasure(对应view的onMeasure)、performLayout(onLayout)、performDraw(onDraw);

performDraw()中:

  1. 将view的数据传至native层的surface

  2. surface中的canvas记录数据

  3. 生成bitmap图像数据(此时数据是在surface中)

  4. surface放入队列中;生产者消费者模式;

  5. 通知surfaceflinfer进程去队列中取surface数据

  6. surfaceflinfer拿到不同的surface,进行融合,生成bitmap数据

  7. bitmap数据放入framebuffer中,进行展示

简单版总结: Activity.setContentView(R.layout.resId):

解析xml并实例化;

  1. 调用phoneWindow.setContentView(resId)

  2. setContentView中调用installDector():根据不同的主题,找到系统默认的xml,初始化出mDectormContentParent(反射实例化出对应的ViewGroup

  3. 初始化完成后,调用mLayoutInflater.inflate(resId,mContentParent):

  4. 解析resId的xml文件,将解析的view反射实例化;递归添加到各节点的viewgroup中;最后将自己定义的xml根布局view添加到mContentParent;

绘制发生时间: 在AMS回调ActivityThread中的handleResumeActivity时,也就是Resume时,而不是onCreate()

  1. 获取PhoneWindow

  2. 获取PhoneWindow中的mDector布局视图view

  3. mDector布局视图view传给ViewRootImpl

  4. ViewRootImpl中调用requestLayout()

  5. requestLayout()中依次调用:performMeasure()、performLayout()、performDraw()

三丶WMS原理:WMS角色与实例化过程

  1. window:它是一个抽象类,具体实现类为 PhoneWindow ,它对 View 进行管理。Window是View的容器,View是Window的具体表现内容;
  2. windowManager:是一个接口类,继承自接口 ViewManager ,从它的名称就知道它是用来管理 Window 的,它的实现类为 WindowManagerImpl;
  3. 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(当用户的脸贴近屏幕时(比如打电话),不会去响应此事件 )

  1. 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.重要类简介:

  1. WindowManagerImplWindowManager的唯一实现类;
  2. WindowManagerGlobal:窗口管理操作,所有Activity都是通过这个进程内唯一的WindowManagerGlobal对象和WMS通信;
  3. ViewRootImpl:
  • View树的树根并管理View树;
  • 触发View的测量、布局以及绘制;
  • 输入相应的中转站;
  • 负责与WMS进行进程间通信;

3.7.TextView.setText刷新流程

ViewRootImpl会调用scheduleTraversals准备重绘,但是,重绘一般不会立即执行,而是往ChoreographerChoreographer.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之前,先了解几个类;

WindowTokenWindowToken具有令牌的作用,是对应用组件的行为进行规范管理的一个手段。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事件,核心类就是InputReaderInputDispatcher;

解释:
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原理系统服务的注册过程

Android Binder原理ServiceManager的启动过程

Android Binder原理学习Binder前必须要了解的知识点

Android 重学系列 WMS在Activity启动中的职责 计算窗体的大小(四)

Android系统显示原理

Android WMS工作原理浅析