深入理解android2-WMS,控件


title: '深入理解android2-WMS,控件-图床版'
date: 2020-03-08 16:22:42
tags:
typora-root-url: ./深入理解android2-WMS-控件
typora-copy-images-to: ./深入理解android2-WMS-控件


WMS

WMS主要负责两个功能, 一是负责窗口的管理,如窗口的增加删除,层级.二是负责全局事件的派发.如触摸点击事件.

先简单介绍几个重要的类

IWindowSession. 进程唯一的.是一个匿名binder.通过他向WMS请求窗口操作

surface 绘画时,canvas会把内容绘制到surface里.surface是有surfaceFlinger提供给客户端的.

WindowManager.LayoutParams 集成自ViewGroup.LayoutParams.用来指明client端的窗口的一些属性.最重要的是type. 根据这属性来对多个窗口进程ZOrder的排序.

windowToken.向WMS添加的窗口令牌.每个窗口都要有一个令牌.

IWindow. 是client提供给WMS的.继承自binder.WMS通过IWindow对象来主动发起client端的事件.

窗口的本周就是进行绘制所使用的surface,客户端向WMS添加窗口的过程,就是WMS为客户端分配surface的过程.

image-20200308202908157

ui框架层就是使用surface上绘制ui元素.及响应输入事件

WMS负责surface的分配.窗口的层级顺序

surfaceFlinger负责将多个Surface混合并输出.

WMS 启动

WMS有SystemServer 进程启动.他和AMS其实是运行于一个进程中的.只是分别有各自的线程.

SystemServer.run

  1. 显示创建两个HandlerThread.和对应的handler.一个叫ui,一个叫windowManager.这两个HandlerThread就会在run中循环接受发过来的消息.
  2. 调用WMS的main.传入两个handler.返回一个WMS对象,加入ServiceManager管理
  3. 初始化显示信息,主要是显示持续相关.完成后WMS会要求AMS进行配置更新
  4. 通知WMS初始化完成,调用windowManagerPolicy的systemReady
     //创建两个 HandlerThread
     HandlerThread uiHandlerThread = new HandlerThread("UI");
      uiHandlerThread.start();
      Handler uiHandler = new Handler(uiHandlerThread.getLooper());
    
     HandlerThread wmHandlerThread = new HandlerThread("WindowManager");
      wmHandlerThread.start();
      Handler wmHandler = new Handler(wmHandlerThread.getLooper());
      //调用WMS.main 返回一个WMS对象,加入ServiceManager管理
      wm = WindowManagerService.main(context, power, display, inputManager,
                    uiHandler, wmHandler,
                    factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
                    !firstBoot, onlyCore);
       ServiceManager.addService(Context.WINDOW_SERVICE, wm);
       //初始化显示信息     
       wm.displayReady();
       //通知WMS初始化完成.  
       wm.systemReady();

WMS.main

上边传入了两个handler.这里就使用windowManager的handler来创建WMS.也就是在一个handerThread线程中创建

   public static WindowManagerService main(final Context context,
            final PowerManagerService pm, final DisplayManagerService dm,
            final InputManagerService im,
            final Handler uiHandler, final Handler wmHandler,
            final boolean haveInputMethods, final boolean showBootMsgs,
            final boolean onlyCore) {
        final WindowManagerService[] holder = new WindowManagerService[1];
        //在名为WindowManager的handlerThread创建WMS,注意,传入WMS了uiHandler.这是另一个handlerThread
        wmHandler.runWithScissors(new Runnable() {
            @Override
            public void run() {
                holder[0] = new WindowManagerService(context, pm, dm, im,
                        uiHandler, haveInputMethods, showBootMsgs, onlyCore);
            }
        }, 0);
        return holder[0];
    }
 WMS构造函数主要有下
1. 初始化了很多东西.包括动画相关屏幕相关
2. 保存输入事件的服务, WMS要管理输入事件
mInputManager = inputManager;
3. 对ui线程进行初始.这里创建的WindowManagerPolicy
initPolicy(uiHandler);   

WMS的重要成员

InputManagerService mInputManager

用来管理每个窗口的事件输入.也就是把输入事件转发到正确的窗口

Choreographer mChoreographer

能获取显示系统的同步信号.用来驱动动画的渲染

WindowAnimator mAnimator

所有窗口动画的总管,在mChoreographer的驱动下渲染所有动画

WindowManagerPolicy mPolicy

只有PhoneWindowManager一个实现.定义了很多窗口相关的策略.是最重要的成员,比如负责窗口的zorder顺序.

zorder就是各个窗口在z轴的值.越大越在屏幕上层.窗口就是根据zorder值一层一层堆在一起.

SparseArray mDisplayContents

可以绘制的屏幕列表.默认是只有1个.

HashMap<IApplicationToken, WindowToken> mTokenMap

管理所以窗口的显示令牌token,每个窗口都要属于一个token.这里的IBinder 是

ArrayList< AppWindowToken> mAppTokens

表示所有Activity的token. AppWindowToken是WindowToken的子类,这个list的顺序和AMS中对mHistory列表中activity的顺序是一样的 .反应了系统中activity的叠加顺序.也就是说.所有窗口都有WindowToken.而Activity对应的窗口则多了AppWindowToken.

HashMap<IWIndow, WindowState> mWindowMap

每个窗口都对应一个WindowState.存储改窗口的状态信息(这就和AMS中对每个activity抽象成ActivityRecord一样)

这里的iBinder 是IWIndow类.

HashSet< Session > mSessions

Session 是WMS提供给客户端来与WMS进行交互的,这是匿名binder.为了减轻WMS的负担.客户端通过IWindowManager.openSession 拿到他的代理.然后通过代理与WMS交互.每个进程唯一.

WMS窗口管理结构

添加窗口

客户端通过IWindowSession.add 来添加窗口. iWindowSession 是同aidl形成的.最终到了WMS.addWindow.

  1. 获取要显示的屏幕

  2. 找到了这个窗口对应的windowToken,可以为空,为空就新建一个并保存.然后是对窗口类型的各种判断.

  3. 为APP的窗口创建WindowState对象.维护窗口的所有信息.

  4. WindowManagerPolicy出现,调整了layoutparams的参数.

  5. 保存这个新窗口的token令牌和生成的WindowState.并调用WindowState的attach

 public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, InputChannel outInputChannel) {
  1.获取要显示的屏幕          
  final DisplayContent displayContent = getDisplayContentLocked(displayId);            
   //attrs 是   LayoutParams, attrs.type表示窗口的类型.attrs.toekn表示窗口属于哪个对象.
   //2.这里找到了这个窗口对应的windowToken
   WindowToken token = mTokenMap.get(attrs.token); 
    if (token == null) {
        token = new WindowToken(this, attrs.token, -1, false);
    }elseif (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
        AppWindowToken atoken = token.appWindowToken;
    }
    //3.为APP的窗口创建WindowState
    win = new WindowState(this, session, client, token,
                    attachedWindow, seq, attrs, viewVisibility, displayContent);
     4.WindowManagerPolicy出现,调整了layoutparams的参数.               
     mPolicy.adjustWindowParamsLw(win.mAttrs);
        win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

        res = mPolicy.prepareAddWindowLw(win, attrs);
    5. 保存这个新窗口的token令牌和生成的WindowState.并调用WindowState的attach
   if (addToken) {
            mTokenMap.put(attrs.token, token);
        }
        win.attach();
        mWindowMap.put(client.asBinder(), win);
}

这里总的来说就是确立了客户窗口的WindowToken.WindowState.和DisplayContent. 并都保存了起来.同时根据layoutparams.type进行了些窗口等级的判断.

理解windowToken

WindowToken将同一个应用组件的窗口安排在一起.一个应用组件可以是Activity,InputMethod.

WindowToken使应用组件在变更窗口时必须与自己的WindowToken匹配.

这里主要是为了处理窗口的层级关系而设立的.

只要是一个binder对象.都可以作为token向wms声明.wms会把这个binder对应起一个WindowToken.其实就是把客户端的binder和wms里的一个WindowToken对象进行了绑定.

因为Activity比较复杂,因此WMS为Activity实现了WindowToken的子类 appwindowtoken.同时在AMS启动Activity的ActivityStack.startActivityLocked里声明token.

  mService.mWindowManager.addAppToken(addPos, r.userId, r.appToken,
          r.task.taskId, r.info.screenOrientation, r.fullscreen,
          (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0);  
          
r 是要启动Activity的ActivityRecord  r.apptoken就是这个binder

然后在activityStack.realStartActivityLocked里在发给用户进程,然后用户在通过这个binder和WMS交互时带过来.

 app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                    System.identityHashCode(r), r.info,
                    new Configuration(mService.mConfiguration),
                    r.compat, r.icicle, results, newIntents, !andResume,
                    mService.isNextTransitionForward(), profileFile, profileFd,
                    profileAutoStop);
  r.appToken 就是这个binder

activity则在 activityStack 线程的handleResumeActivity 里把Activity 对应的窗口,加入到wMS中

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
            boolean reallyResume) {
            
ActivityClientRecord r = performResumeActivity(token, clearHide);
ViewManager wm = a.getWindowManager();
      WindowManager.LayoutParams l = r.window.getAttributes();
      a.mDecor = decor;
      l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
      l.softInputMode |= forwardBit;
      if (a.mVisibleFromClient) {
          a.mWindowAdded = true;
          wm.addView(decor, l);
      }
}

取消token 也是在AMS中 ,也就是说, AMS负责avtivity的token向WMS的添加和删除.

当然.Activity的 r.appToken 是 IApplicationToken.Stub ,他里边有一系列的窗口相关的通知回调.

这里总结下. AMS在创建Activity的ActivityRecord时,创建了他的appToken,有把appToken传送给WMS.WMS对应匹配为APPWindowToken,最后还把这个appToken发送给activity.因此AMS就通过ActivityRecord就可有直接操作WMS对该窗口的绘制.如图.

image-20200308224158968

理解WindowState

每个window在WMS里都抽象成了WindowState.他包含一个窗口的所有属性.WindowState在客户端对应的则是iWidow.stub类.iWidow.stub有很多窗口通知的回调.

WindowState被保存在mWindowMap里.这是整个系统所有窗口的一个全集.

HashMap<IBinder, WindowToken> mTokenMap .这里是 IApplicationToken(客户端)和WindowToken的映射

HashMap<IBinder, WindowState> mWindowMap 这里是IWidow(客户端)和WindowState的映射,并且WMS通过这个IWindow 来回调客户端的方法.

image-20200308225135573

上图可以看出.每个activity 只有一个ActivityRecord.也只有一个AppToken,也就只有一个WindowToken.而一个acitvity可能有多个窗口.每个窗口对应一个WindowState.

WindowToken用来和AMS交换. 而WindowState对应的iWindow则是WMS来与客户端交互的.

窗口显示次序

窗口显示次序就是窗口在Z轴的排了.因为窗口是叠加在一起的.因此就需要知道哪些显示在上边,哪些在下边.这个由WindowState构造时确定

 窗口类型是子窗口
if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
            mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
            mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
  } else {
窗口类型是普通窗口
      mBaseLayer = mPolicy.getWindowLayerLw(this)
              * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
      mSubLayer = 0;
  }

可见.分配规则是由WindowManagerPolicy mPolicy来决定的.产生 mBaseLayer和mSubLayer. mBaseLayer决定该窗口和他的子窗口在所有窗口的显示位置. mSubLayer决定子窗口在同级的兄弟窗口的显示位置.值越高.显示约靠上.

image-20200308230730423

WindowState 产生了他自己这个窗口的layer值后.在添加窗口的时候就会把所有窗口按layer排序插入mWindows列表中,在通过 adjustWallpaperWindowsLocked();进行层级调整.

窗口的布局

当客户端通过IWindowsession.add后,客户端还没有获得Surface.只有在执行IWindowsession.relayout后.客户端才获得了一块Surface. IWindowsession.relayout根据客户端提供的参数,为客户端提供surface.具体实现是WMS.relayoutWindow

WMS.relayoutWindow

  1. 方法参数的介绍
image-20200308232324270
  1. 所在mWindowMap.防止多线程竞争
  2. 找到需要进行relayout 的窗口对应的windowState
  3. 遍历所有DispalyContent的所有窗口,计算布局尺寸,并把尺寸设置给Surface
  4. 把布局结果返回给用户端.
public int relayoutWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int requestedWidth,
            int requestedHeight, int viewVisibility, int flags,
            Rect outFrame, Rect outContentInsets,
            Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
      参数里的outSurface最后就是传递给客户端的surface.他会和客户端的surface进行绑定.然后客户端就可以绘制了.
   
  synchronized(mWindowMap) {
        //获得要relayout的窗口   
        WindowState win = windowForClientLocked(session, client, false); 
        //下边是对win各种属性的更新.
        ...
        //遍历所有DispalyContent的所有窗口,计算布局尺寸,并把尺寸设置给Surface
         performLayoutAndPlaceSurfacesLocked();
         //把布局结果返回给用户端
           outFrame.set(win.mCompatFrame);
            outContentInsets.set(win.mContentInsets);
            outVisibleInsets.set(win.mVisibleInsets);
    }
   
  }

总的来说就是根据用户传入的参数,更新WindowState.然后遍历所有窗口布局.在设置合适的Surface尺寸,在返回给用户端

performLayoutAndPlaceSurfacesLocked 会循环调用6次.里边的逻辑大概如下

  1. 清除已退出或已隐藏的僵尸窗口的surface
  2. 又调用其他方法进行布局
  3. 如果需要.再次重复布局操作.
performLayoutAndPlaceSurfacesLockedLoop{
    1清除已退出或已隐藏的僵尸窗口的surface
     for (int i=0; i<mForceRemoves.size(); i++) {
      WindowState ws = mForceRemoves.get(i);
      removeWindowInnerLocked(ws.mSession, ws);
  }
  
  performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
  //如果需要.再次重复布局
  if (needsLayout()) {
        if (++mLayoutRepeatCount < 6) {
            requestTraversalLocked();
        }
  }
}

这里主要下,因为之前加了锁.requestTraversalLocked他又会重复执行performLayoutAndPlaceSurfacesLocked();因此会重复循环执行布局.

布局这部分就记个原理吧

image-20200308234415722

布局完成后.客户端的尺寸和surface都得到了.就可以绘制 了.WMS会通知客户端布局发送变化

总结,WMS 负责管理所有的窗口.包括系统窗口和APP窗口,而窗口必须有一个WindowToken所为标识符.同时WMS为每个窗口创建一个WindowState类,这是窗口在服务端的抽象.WindowState则绑定了一个客户端的IWindow类,WMS通过这个IWindow 向APP发送消息.

AMS在启动Activity的时候.把ActivityRecord.token 通过wms.addtoken 注册到WMS.又把这个token发送到APP端.因此三方可以通过这个token正确找到对应的数据.

WMS负责给所以窗口按ZOrder排序,确定窗口的尺寸,提供绘画用的surface.

Activity的窗口是先wms.addtoken 建立windowToken关系 . wms.addWindow. 添加串口, WMS.relayout获取surface. 完成 .

一个windowToken对应一个Activity. 但是可能对应多个windowSatate.也就是对应多个窗口.

image-20200309193604451

安卓控件系统

基础类介绍

ViewRoot

是view树的根实现类是viewRootImpl.但是他不是view.他是用来和WMS进行交流的管理者.viewRootImpl内部有个IWindowSession,是WMS提供的匿名binder,同时还有个iWindow子类,用来让WMS给viewr发消息. view通过ViewRoot向WMS发消息.WMS在通过IWIndow 向APP发消息. 每个View树只有一个ViewRoot,每个Activity也只有一个ViewRoot. UI绘制,事件传递.都是通过ViewRoot.

Window

.实现类是PhoneWindow . Activity和View的沟通就是通过Window.Activity实现window的各种回调.一个Activity也对应一个PhoneWindow.也对应一个View树.

Docerview 就是View树的根.这是一个View. 他由PhoneWindow管理. 下文的WindowManager也由phoneWindow管理.

他还管理window的属性 WindowManager.layoutparams.

windowManager

他是一个代理类.他集成自ViewManager.他的实现是WindowManagerImpl.这是每个Activity都有一个.但是他只是把工作委托给了 WindowManagerGlobal来实现. 他负责添加删除窗口,更新窗口.并控制窗口的补件属性WindowManager.Layoutparams.

image-20200310123836737

WindowManagerGlobal

是进程唯一的.负责这个进程的窗口管理.他里边有三个集合.保存这个进程所有窗口的数据.这里的每个数据根据index得到的是同一个Activity属性.所有的WindowManager的操作都转到他这里来.

private final ArrayList<View> mViews 每个view是个跟节点

private final ArrayList<ViewRootImpl> mRoots view对应的viewRoot

private final ArrayList<WindowManager.LayoutParams> mParams 窗口的layoutparams属性.每个窗口一个

image-20200309202306828

对于一个acitivity对象永远对应一个PhoneWindow,一个WindowManagerImpl,一个WMS端的APPWindowToken,一个AMS里的ActivityRecord(但是如果一个activity在栈里有多个对象,就有多个ActivityRecord和AppWindowToken),acitvity 的默认窗口的view树是DocerView.

一个窗口 对应一个ViewRoot,一个View树.一个WindowManager.LayoutParams,一IWindow(WMS回调app).一个WSM端的WindowSatate.

但是一个Activity可以有多个窗口,因此对应WMS里可能有多个WindowSatate.这些WindowState都对应一个AppWindowToken.

一个Activity可能被加载多次.因此在AMS中可能有多个ActivityRecord对应这个activit的多个对象.

但是一个进程则对应一个WindowManagerGlobal.一个ActivityThread(主线程).一个ApplicationThread(AMS调用app).一个iWindowSession(viewroot向WMS发消息)

这里的区别就是 app与AMS 的交互是以进程之间进行通信.而App与WMS的交互.则是以窗口作为通信基础.

窗口添加过程

当Activity由AMS启动时.ActivityThread 通过handleResumeActivity执行resume相关的操作.这个函数首先是执行activity.resume, 此时activity 对应的view树已经建立完成(oncreate中建立,PhoneWindow也创建了).需要把activity的窗口添加到WMS中去管理.

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
            boolean reallyResume) {
                //执行onresume
        ActivityClientRecord r = performResumeActivity(token, clearHide);
   ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (a.mVisibleFromClient) {
            a.mWindowAdded = true;
            //添加daoWMS中
            wm.addView(decor, l);
        }
}

这里的wm是WindowManager.是每个activity一个.他内部会调用WindowManagerGlobal.addView

WindowManagerGlobal.addView

这里会为窗口创建ViewRootImpl. 并把view.ViewRootImpl.WindowMa.LayoutParams都保存在WindowManagerGlobal中, 并通过ViewRootImpl向WMS添加窗口

如果这个窗口是子窗口(wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW)

就把子窗口的token设为父窗口的token否则就是所属activity的token.

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    
     root = new ViewRootImpl(view.getContext(), display);
         mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            }
  }

在来个图

image-20200310151303616

在这里我们看到.我们通过mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 拿到的并不是远程的WMS.而是本地的WindowManagerImpl. 他又把请求转发给WindowManagerGlobal ,而WindowManagerGlobal作为进程单实例.又是吧请求转给对应窗口的ViewRootImpl.ViewRootImpl通过WMS的IWindowSession 把数据发给WMS.

理解ViewRootImpl

ViewRootImpl用来沟通View和WMS.并接受WMS的消息.这是双向的binder通信.作为整个空间树的根部,控件的测量,布局,绘制,输入时间的派发都由ViewRootImpl来触发.

ViewRootImpl由WindowManagerGlobal创建,是在activityThread.handleResumeActivity时,先执行activity.resume.在调用wm.addView. 就会执行WindowManagerGlobal.addView里.创建ViewRootImpl,此时是在ui线程中.

ViewRootImpl里的mView属性.host属性,就是view树

ViewRootImpl 创建

  1. 得到WMS里的IWindowSession代理.
  2. 保存display.表示一块屏幕.之后view绘制在这上.
  3. 保存当前线程,也就是ui线程
  4. 保存当前窗口尺寸.
  5. W类是WMS回调APP的binder
  6. attachInfo表示view树所依附的窗口的信息,会传递给每个view
  7. 主线程中创建Choreographer,用于接收重绘信号.
  8. 用于向主线程发消息的mHandler
  9. 用于绘制的surface. 会和WMS返回的surface绑定.
public ViewRootImpl(Context context, Display display) {
        得到WMS里的IWindowSession代理.
        mWindowSession = WindowManagerGlobal.getWindowSession();
        保存绘制的屏幕
        mDisplay = display;
      
        保存ui线程
        mThread = Thread.currentThread();
        mDirty = new Rect();
        //窗口尺寸
        mTempRect = new Rect();
        //WMS回调APP的binder
         mWindow = new W(this);
        //表示当前view树所依附的窗口的信息,会给每个view
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
       //主线程中创建Choreographer,用于接收重绘信号.
        mChoreographer = Choreographer.getInstance();
        }
        ///依附于主线程.发消息到主线程执行.
        final ViewRootHandler mHandler = new ViewRootHandler();
        
    public final Surface mSurface = new Surface();

ViewRootImpl.setView 注册窗口

  1. 保存view树
  2. 添加到WMS前的重绘操作,这次的结果可能会被WMS的回调进行修改.但是重绘必不可少
  3. 向WMS注册窗口.
  4. ui线程注册完成输入事件的注册.
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        //保存控件树
         mView = view;
         //添加到WMS前的重绘操作
      requestLayout();
      inputChannel是一个接受输入事件的管道.用来接收输入事件.
       mInputChannel = new InputChannel();
      //向WMS注册窗口
       res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
    //这里是输入事件的接收者,传入的looper.myLooper是ui线程的looper.因此输入事件在ui线程触发.这里了解吧
   
   if (mInputChannel != null) {
        if (mInputQueueCallback != null) {
            mInputQueue = new InputQueue();
            mInputQueueCallback.onInputQueueCreated(mInputQueue);
        }
        mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                Looper.myLooper());
    }
 }
image-20200310162246302

ViewRootImpl.performTraversals 重绘

添加窗口时通过requestLayout();向ui线程发送消息.最后回调到ViewRootImpl.performTraversals.他是整个ui控件树,measure.layout.draw的集合.

这里分为五个阶段.

预测量阶段.进行第一次测量,获得view.getMeasuredWitdh/Height,此时是控件树期望的尺寸.会执行View的onMeasure

布局阶段,根据预测量的结果,通过IWindowSession.relayout向WMS请求调整窗口的尺寸这会使WMS对窗口重新布局,并把结果返回给ViewRootImpl.

最终测量阶段, 预测量的结果是view树期望的结果.WMS可能会进行调整,在这里WMS已经把结果通知了ViewRootImpl.因此这里会窗口实际尺寸performTraversals进行布局.view及子类的onMeasure会被回调

布局阶段. 测量完成后获得空间的尺寸,布局要确定控件的位置,View及子类的onLayout会被回调.

绘制阶段,使用WMS提供的surface.进行绘制,View及子类的onDraw会被回调.

通常我们看到的都是 先measure,在layout在draw. 这里看到.其实measure先得到期望值,在和WMS沟通.WMS在调整后,返回确定值,在根据确定值进行mesure.

预测量

 private void performTraversals() {
        窗口的最新尺寸
     Rect frame = mWinFrame;
     
     if (mFirst) {//第一次遍历.窗口刚添加到WMS.还没有有效的窗口尺寸.
                //使用屏幕的尺寸.
                mDisplay.getRealSize(size);
            desiredWindowWidth = size.x;
            desiredWindowHeight = size.y;
         //通过view书跟节点.执行 attach 每个view都会回调onAttachedToWindow();  
         host.dispatchAttachedToWindow(mAttachInfo, 0);
     }else {
        //不是第一次.并且窗口尺寸和Viewrootimpl不一样.表现需要重新测量.
        desiredWindowWidth = frame.width();
        desiredWindowHeight = frame.height();
       
      }
     //处理view.post(runnable) 也就是借着ViewRootimpl的hanlder发送给ui线程执行.
     这个RunQue是进程唯一的.
     getRunQueue().executeActions(mAttachInfo.mHandler); 
     
     //进行预测量.
     measureHierarchy(host, lp, mView.getContext().getResources(),
                          desiredWindowWidth, desiredWindowHeight);
 }

measureHierarchy里会通过三次协商.执行performMeasure 来确认合适的尺寸.

performMeasure 会调用view 的measure 优会调用onMeasure. 我们可重写onMeasure来实现测量.而measure 方法是final的.onMeasure 的结果通过setMeasuredDimension方法保存.

对于view. onMeasure.比较容易. 对于ViewGroup.则还要遍历调用他所以子view的measure. 并且需要考虑padding和子view 的margin. padding是控件外内边距. margin 是控件外边距.

ViewGroup需要先测量完子view.在根据子view的测量值得到自己的宽高.举例,如果只有一个子view.那么ViewGroup的宽= 子view的宽+子view的margin+viewg的padding. 至少是这个值.

image-20200310205221080

继续回到performTraversals

...
boolean windowShouldResize  //决定是否窗口要改变尺寸.至此.预测量完成

这里就是提前测量了一下.得到控件树希望的尺寸大小,

布局窗口和最终测量

通过relayoutWindow来布局窗口. ViewRootImpl 通过IWindowSession 来通知WMS进行窗口布局.

 private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending){
     int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,mPendingMergedConfiguration, mSurface);          
  }
            

这里主要下. 调用WMS后.WMS会调整窗口的尺寸. 同时会生成surface返回给ViewRootImpl. 因此后续的绘画就有了画布了.可以看到最后的参数是mSurface.这是本地的surface. 这里会和wms的进行绑定.

接下来继续performTraversals,绑定WMS返回的surface.然后更新尺寸.

最后进行最终测量. 上边过程太乱了. 了解下就行.还是看常见的控件绘制流程.

image-20200310213030039

viewRootImpl.performTraversals

绘制由viewRootImpl.performTraversals触发, 抽取出来后,就是这样

 private void performTraversals(){
        performConfigurationChange()  //会触发onConfigurationChange事件
        //这时候传入的是屏幕的尺寸
   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
    
    //用测量完的高度.在进行补件  host是view树的跟布局
     int width = host.getMeasuredWidth();
    int height = host.getMeasuredHeight();
     performLayout(lp, mWidth, mHeight);    
     
     //绘制流程. 此时WMS返回的surface已和ViewRootImpl的surface绑定了起来.可以进行绘制了.
      performDraw();
 }

viewRootImpl.performMeasure

就是直接调用view树的根的measure方法. 传入到View

image-20200311122109134

View.measure

该方法是final .意味着无法重写.这里又会调用onMeasure.

因此.对于view.在onMeasure中调整好高度,通过setMeasuredDimension设置好自己的测量宽高就可以了.

对应ViewGroup.则在onMeasure中,先要遍历子view.调用他们的measure(注意一定是调用子类的measure,measure又会调用onMeasure), 子view宽高都知道后,在根据子view的宽高来设置自己.也就是ViewGroup的宽高受子view影响.

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        onMeasure(widthMeasureSpec, heightMeasureSpec);
 }
 
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  }
 
 这里的widthMeasureSpec由两部分组成,模式mode和值size.都由父view提供
 模式有三种
 unspecified 父view不限制ziview大小.多用于系统内部
 exactly    精确值,子view的大小就是widthMeasureSpec的size,对应子view的match_parent 和确定值这两种
 at_most    父view提供可用大小,既widthMeasureSpec的size,子view不超过这个值就可以.对应子view的wrap_content
 

可以看到view的measure又调用了onMeasure, 如果是view 则可以直接重新onMeasure来设定大小.而对于ViewGroup, 则需要重写onMeasure来先遍历子view.设定大小.然后再设定viewGroup的大小. ViewGroup并没有重写onMeasure.因为每个ViewGroup要实现的效果不同,需要自己完成.但ViewGroup提供了几个方法供ViewGroup的继承类来遍历子view.

view的宽高由自己的layoutParams和父view提供的 widthMeasureSpec|heightMeasureSpec共同决定.

View 自己的宽高,是保存在LayoutParams中对,以宽举例 LayoutParams.width 有三种情况,精确值(就是指定大小),MATCH_PARENT. WRAP_CONTENT,模式则有fuview提供.有 unspecified,exactly,at_most三种.

匹配如下.

image-20200311122806999

其实这个很好理解. 如果子view自己指定了宽高.就用他的值就可以.如果子view是match_parent.那就使用父view提供的宽高. 如果子view是wrap_content,那就不能超过父view的值.

看下ViewGroup为子view绘制而提供的方法,可以看到.ViewGroup会减去padding和margin,来提供子view的宽高.

viewgroup.measureChildWithMargins

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

Framelayout.onMeasure

  1. 遍历子view.通过ViewGroup提供的measureChildWithMargins拿到子view的宽高.并用最大值作为framelayout的宽高
  2. 最大值在加上framelayout的padding
  3. 通过setMeasureDemision 来设置framelayout的宽高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();


        int maxHeight = 0;
        int maxWidth = 0;
//遍历子view.通过ViewGroup提供的measureChildWithMargins拿到子view的宽高.
并用最大值作为framelayout的宽高
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                
            }
        }
//最大值在加上framelayout的padding
        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

通过setMeasureDemision 来设置framelayout的宽高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

       
    }

ViewRootImpl.performLayout

上步measure过程未完成后,整个view书的 测量宽高都得到了.也就是view.getMeasuredWidth()和getMeasuredHeight()

performLayout中会调用mView.layout. 这样就把事件从ViewRootImpl传递到了view.而layout中又会调用onLayout.ViewGroup需要重写onLayout为子view进行布局,遍历调用子view的layout.因此就完成整个view树的laylut过程.

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
   //host就是view书的根view.对与activity就是docerView
   //这里传入的参数就是measure 过程测量出的view的宽高.作为左上右下的左边传给layout
   host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
                      
}

view.layout

  1. 通过setframe设定view本身的布局位置,这里都是以fuview左上角作为标点.
  2. 调用onLayout. 整个方法在view中是空的.这是给ViewGroup用来布局子view的.
  3. onLayout 和具体布局有关.viewGroup也没有实现,而是具体的ViewGroup的集成类自己实现.ViewGroup的onLayout是抽象方法.必须要子类实现
    public void layout(int l, int t, int r, int b) {
    
          boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        onLayout(changed, l, t, r, b);
    }

LinearLayout.onLayout

竖向的实现, 竖向的就行把view从上到下一次排开

void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        // ViewGroup的宽度
        final int width = right - left;
        int childRight = width - mPaddingRight;
        final int count = getVirtualChildCount();
        childTop = mPaddingTop;

                //因为是从上到下排开,计算每个view的top值,top值就是view.getMeasureHeigh+view.      topMargin+view.bottomPargin +LinearLayout的paddingTop
                所以这里的子view的top值是不断变大.因此.view就依次从上到下摆开了.
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
             if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();

                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();
                childLeft = paddingLeft + lp.leftMargin;

                childTop += lp.topMargin;
                设置ziview的四个角的坐标.调用子view的layout.重复layout过程.
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            }
        }
    }

这里注意区分.measure过程是先得到子view的测量值,在设定父ViewGroup的值.而layout过程则是先传入父view的左上右下值,来计算子view的左上右下的位置值.这里应该具有普遍性.但不知道是否绝对.

image-20200311123219084

ViewRootImpl.performDraw

performDraw 中的调用draw.又调用mView.draw.然后就进入view树的绘制了.

view的draw 又会调用onDraw ,viewGroup又调用dispatchDraw()把draw分发到子view里 绘制的画布就是canvas. 这是从surface.lockCanvas中获得的一个区域.

而在ViewGroup.dispatchDraw中.重要的一点是getChildDrawingOrder 表示子view的绘制顺序.默认是与ziview的添加顺序一样.我们也可以改变他.最后绘制的会显示在最上边,而这也影响view的事件传递顺序.

view.draw. 就是一层一层的画内容.先画北京,在onDraw.在画装饰什么的.

控件树绘制讲解

canvas.translate(100,300)通过平移坐标系.使之后的内容可以直接在新坐标系中绘制.

这就是ViewGroup在向子view传递canvas的时候.方便多了. 会之前先对其ziview的左上角.那么子view就可以直接从自己坐标轴的(0,0)开始绘制, 绘制完成后ViewGroup在还原原有坐标系.

canvas.save. canvas.restore 用来保存还原坐标系.

image-20200311142226071

view.invalidate.

当某个view发送变化需要重绘时,通过view.invalidate向上通知到ViewRootImpl.从这个view到ViewRootImpl的节点都标记为藏区域.dirty area. ViewRootimpl再次从上到下重绘时,只绘制这些脏区域.效率高.

输入事件派发

触摸模式

本来安卓兼容使用键盘,也支持,触摸.二者的输入事件派发不一样.使用键盘时会有个控件处于获得焦点状态.处于触摸模式则由用户决定. 因此控件分为两类.任何情况下都能获得焦点.如输入文本框.只有在键盘操作时才能获得焦点.如菜单,按钮.

安卓里有触摸模式.当发送任意触摸时进入触摸模式.当发送方向键和键盘或者执行View.requestRocusFromTouch时,退出触摸模式.

View.requestFocus

获取焦点. view.request.

先检查是否能获取焦点,

然后设置获取简单的标记,

向上传递到ViewRootimpl.保证只能有一个控件获取焦点.

通知焦点变化的监听者.

更新view的drawable状态,

 void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {

        if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
            mPrivateFlags |= PFLAG_FOCUSED;

            View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

            if (mParent != null) {
                mParent.requestChildFocus(this, this);
                updateFocusedInCluster(oldFocus, direction);
            }

            if (mAttachInfo != null) {
                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
            }

            onFocusChanged(true, direction, previouslyFocusedRect);
            refreshDrawableState();
        }
    }

requestChildFocus会把焦点事件层层上报取消原来有焦点的控件.最后的效果就是从viewrootimpl中.到最终有焦点的view.构成一条 mFoucued 标识的链条.来个图就明白了.每个view的mFocused总是指向他的直接下级.

image-20200311150906174

获取focus的传递是从底层view到顶层的ViewRootImpl.而取消focus测试从顶层的ViewRootimpl到底层原来那个获得焦点的view.

ViewGroup.requestFocus

而如果是ViewGroup请求获取焦点,会根据FLAG_MASK_FOCUSABILITY特性来做不同方式,分别有先让自己获取焦点,或者安卓view的索引递增或者递减来匹配view.

事件派发

ViewRootImpl 中的.WindowInputEventReceiver接受输入事件.他会把事件包装成一个QueuedInputEvent.然后追加到一个单链表的末尾.接着重头到尾的处理输入事件,并通过deliverInputEvent完成分发.这里会把单链表所有事件都处理完.

deliverInput中又会把触摸事件执行到通过 ViewPreImeInputStage.processKeyEvent. 转入mView.dispatchPointerEvent(event).这里又进入 dispatchTouchEvent

MotionEvent是触摸事件的封装.getAction可以拿到动作的类型和触控点索引号.

getX(),getY().拿到动作的位置信息.通过getPointID拿到触控点的id. 动作以down 开头.跟多个move.最后是up.

view.dispatchTouchEvent

,当事件返回true.表示事件被消费掉了.

public boolean dispatchTouchEvent(MotionEvent event) {
    //安全模式.不允许窗口被遮挡.
    if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //回调listener的ontouch
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
                //回调onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }


}

ViewGroup.dispatchTouchEvent

因为有多点触控.ViewGroup可能把motionevent拆成多个事件,分发给多个ziview.因此他内部的mFirstTouchTarget是单向链表.存储所有要处理事件的子view.

public boolean dispatchTouchEvent(MotionEvent ev) {
        //同样.对窗口遮挡进行检查.
     if (onFilterTouchEventForSecurity(ev)) {
             down是事件开始.清楚原有的状态.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
          cancelAndClearTouchTargets(ev);
          resetTouchState();
      }
      //是否子view阻止ViewGroup进行事件拦截,不阻止的话,ViewGroup判断是否拦截.
      //ViewGroup可以拦截事件.但是里边的子view也可以不然ViewGroup拦截.并且子view的权限更高.
         final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false;
        }
       if (!canceled && !intercepted) {
                    事件派发.获取触控点和索引号
          final int actionIndex = ev.getActionIndex();
            final View[] children = mChildren;
            //遍历子view.找到适合派发事件的view.进行派发
          for (int i = childrenCount - 1; i >= 0; i--) {
          //进行事件派发,如果成功,就把这个子view加入到mFirstTouchTarget中.之后的move等事件直接派发.
           if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
              mLastTouchDownX = ev.getX();
              mLastTouchDownY = ev.getY();
              newTouchTarget = addTouchTarget(child, idBitsToAssign);
              alreadyDispatchedToNewTouchTarget = true;
              break;
              }
          }
       
       } 
       
       如果最后子view没有处理事件.则要把时间交给ViewGroup自己.这样又层层上报了回来.
     }
}

dispatchTransformedTouchEvent这又会调用子view的dispatchTouchEvent.进行派发.这样事件就从ViewRootImpl逐渐派发到底了.同时会保存执行down事件的view.之后的move.up事件都有他来处理.

理解PhoneWindow

phonewindow是与activity 一对一匹配的.是在ActivityThread执行 performLaunchActivity.activity创建后执行activit.attach的时候生成的.他管理view树,各种事件回调.和WindowManager.layoutparams. 用来提供简单的方法创造activity的外观. activity.setcontentView. 就是调用PhoneWindow.setContentView. 而我们传入的layout. 只是作为view树的一个子view. PhoneWindow管理的view书是DocerView. ViewRootImpl里的mView也是他.他作为跟控件.把很多回调交给了PhoneWindow.

比如在dispatcTochEvent 中

final Window.Callback cb = mWindow.getCallback();final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event): super.dispatchKeyEvent(event);

直接交给. Window.callback处理.这个callback就是activity了.看看activity中的处理

  public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //这里又调用了DockerView的super.dispatchTouchEvent.也就是交还控件树处理.
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        如果控件树没有处理.则返回给activity在处理.
        return onTouchEvent(ev);
    }

因此.activity的dispatchTouchEvent 是先于控件树触发的.我们可以重写这个方法来实现我们的功能.

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) {
        先创建PhoneWindow.     
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        //设置activity作为事件回调.
         mWindow.setCallback(this);
         //和WMS交流的token
          mToken = token;
         //拿到windowManager
         mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        
 }       

attach之后.activity就有了和WMS交流的 PhoneWindow.WindowManager.和WMS交互的token记着这个token来自AMS. 但是在这之前AMS已经把这个token注册到WMS中.因此此时可直接使用.

Activity的显示发送在onResume之后,由ActivithThread.handlerResumeActivity触发.

  final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
    boolean reallyResume) {
            //执行activity.onresume
         ActivityClientRecord r = performResumeActivity(token, clearHide);
         
         
          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;
                l.softInputMode |= forwardBit;
                //这里通过WindowManager-windowmanagerglobal-viewrootImp,把窗口注册到WMS
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
        }
            //wms里注册完成.让页面展示出来.
          if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                    
                 if (r.activity.mVisibleFromClient) {
                    r.activity.makeVisible();
                }   
          }
    }

Activity 启动整体流程

startActivity开始时会通过ActivityManagerNative.getDefault().startActivity调用AMS开始.又跳转到了ActivityStack.startActivityMayWait.我们从这里分析.

ActivityStack.startActivityMayWait

  1. 通过packageManager解析intent得到ResolveInfo

ResolveInfo rInfo =AppGlobals.getPackageManager().resolveIntent()

  1. 得到acitivity的 source和result 也就是从哪里来,结束后返回到那里

  2. 创建activityRecord.

他在AMS里代表一个activity的记录.每次启动一个activity就会生成一个ActivityRecord. ActivityRecord有个token变量.这个token会传给WMS.这样一个activity在AMS.WMS里就连在一起了.

  1. 处理activityRecord 的launchMode和flag.

  2. 通过WMS.addAppToken.把AMS里这个ActivityRecord的appToken关联到WMS里的WindowToken.这样activity.AMS.WMS.里就都可以通过这个token来识别对方.也就都能定位到这个activity了.这个apptoken会随着activity的启动.由AMS在发给Activity

  3. 为ActivityRecord 创建一个新的TaskRecord. 每个ActivityRecord都会属于一个TaskRecord.表示他所属的任务栈.

image-20200311220332590
  1. 找到栈顶正在运行的ActivityRecord. 暂停他.好为我们启动新的avtivity腾出空间.

    ActivityRecord next = topRunningActivityLocked(null);

    startPausingLocked(userLeaving, false); 这里会调用要pause的那个ActivityRecord对应的thread.来执行pause,这里的thread是ApplicationThread. 是APP进程传递给AMS的binder.用于AMS向APP发信息.

  2. AMS 和ApplicationThrad通过binder通信.把消息带给旧activity进程ActivityThread. 然后执行ActivityThread.handlePauseActivity ->ActivityThread.ActivityThread ->AMS.activityPaused,这里回调AMS表示pause完成.然后AMS继续执行新activity的启动.

  3. WMS 隐藏前一个activity的窗口

mService.mWindowManager.setAppVisibility(prev.appToken, false);

  1. 旧activity执行pause后.新activity的 ProcessRecord还始终是空, 这时会为他创建ProcessRecord,这时通过activity所在的APP的包名和他的uid创建.

​ ProcessRecord app = mService.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid);

mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,

​ "activity", r.intent.getComponent(), false, false);

  1. 然后通过AMS.启动ActivityThread线程.这时通过socket想Zygote进行发消息.然后zygote进程fork出来的app进程.然后执行ActivityThread.main函数

Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",

​ app.processName, uid, uid, gids, debugFlags, mountExternal,

​ app.info.targetSdkVersion, null, null);

11.ActivityThread就是APP的住线程.他初始化了Lopper.并向AMS执行AttachApplication方法表示自己启动成功.

12.AMS接受 ActivityThread的信息后.包装这个进程对应的ProcessRecord.此时Activity对应的进程已经启动.要开始启动Activity了. AMS找到顶端要启动的activity.命令APP进程开始启动

app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,

​ System.identityHashCode(r), r.info,

​ new Configuration(mService.mConfiguration),

​ r.compat, r.icicle, results, newIntents, !andResume,

​ mService.isNextTransitionForward(), profileFile, profileFd,

​ profileAutoStop);

  1. ActivityThread开始启动activity. 先根据package信息创建Application对象.也就是androidmainfest里Application标签的信息来创建的,然后反射创建activity对象.执行activity.attach(),attach里.会为activity绑定一个phoneWindow对象,一个WindowManager对象. phoneWindow是窗口的抽象.他负责管理view树,窗口属性LayoutParams.是activity和view树的桥梁.

Application app = r.packageInfo.makeApplication(false, mInstrumentation);

activity.attach(appContext, this, getInstrumentation(), r.token,

​ r.ident, app, r.intent, r.activityInfo, title, r.parent,

​ r.embeddedID, r.lastNonConfigurationInstances, config);

  1. 继续执行activity.onCreate,这里又会执行setContentView .而setContentView是执行的PhoneWindow的同名方法,这里会初始化view树的跟布局Docerview.并把我们的布局加入他的ziview中.这时整个控件树就完成了.这也就看到.整个空间树的根是DocerView并且由Phonewindow管理.

    mDecor = generateDecor();

    mLayoutInflater.inflate(layoutResID, mContentParent);

  2. 继续执行activity.onrestart. 然后在继续有AcitvityThread处理activity的resume,这里是先执行activity的onResume.然后要把activity的窗口注册到WMS中.并先隐藏窗口.等WMS窗口注册成功,再把窗口显示出来.

    performResumeActivity,(token, clearHide);

    View decor = r.window.getDecorView();

​ decor.setVisibility(View.INVISIBLE);

​ ViewManager wm = a.getWindowManager();

​ wm.addView(decor, l);

  1. 这里的wm 就是之前activity执行attach时创建的WindowManager.每个activity对应一个WindowManager.不过这个WindowManager只是个代理.会把请求转发给WindowManagerGlobal.这是个进程唯一的管理类.而WindowManagerGlobal又创建ViewRoomImpl,这是每个view树一个的对象,有viewrootimpl来addView.

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

    root.setView(view, wparams, panelParentView);

  2. viewrootimpl里 ,通过IWindowSession调用WMS来添加窗口,获得surface,同时进行第一次视图绘制.

mWindowSession.addToDisplaymWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(),

​ mAttachInfo.mContentInsets, mInputChannel)

18.第一次绘制只会协商窗口的尺寸和view树期望的尺寸. 这里又调用mWindowSession.relayout来同WM协商窗口的宽高.同时WMS会生成surface返回给APP.此后app的绘制都是在这个canvas的画板上.

int relayoutResult = mWindowSession.relayout(

​ mWindow, mSeq, params,

​ (int) (mView.getMeasuredWidth() * appScale + 0.5f),

​ (int) (mView.getMeasuredHeight() * appScale + 0.5f),

​ viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,

​ mWinFrame, mPendingContentInsets, mPendingVisibleInsets,

​ mPendingConfiguration, mSurface);

  1. 之后viewrootImpl 通过performMeasure 调用view.measure 又遍历调用view.onMeasure进行控件树的测量

    viewrootimpl在通过performLayout调用view.layout又遍历调用view.onLayout 进行控件树的布局

    viewrootimpl在通过performDraw调用view.draw又遍历调用view.onDraw 进行控件树的布局

    这里.控件树的绘制就都完成了. 此时activity已经正确显示出来.

这里再列出出现的一些对象

ProcessRecord. AMS里对APP进程的抽象对象.代表一个进程

TaskRecord   AMS里对APP启动栈的抽象.这个和启动模式相关.如singleTop.singleInstance

ActivityRecord  AMS里对APP启动一个Activity的抽象.一个Activity在如果在栈中有多个对象,就有多个ACtivityRecord

ACtivityStack. AMS里操作APP启动的类. AMS把启动的很多功能都交给他来完成

ActivityRecord.appToken.   这个token是联系一个activity在AMS和WMS里的指令令牌.由AMS创建.WMS会把这个令牌和一个WindowToken进行匹配. 从而表示一个activity

ApplicationThread. 这是一个binder.提供给AMS 的 属于app进程.AMS通过他来调用App进程

ActivityThread APP的主线程. ApplicationThread就是属于ActivityThread的.ApplicationThread接到消息后发送到ActivityThread的小弟队列中.等待ActivityThread处理.

AppWindowToken  是WindowToken的子类.用来标识一个activity在WMS端的记录.一个启动了的activity对应一个AppwindowToken. 

WindowState 是WMS用来标识一个窗口.每个WindowState 有一个W的binder. 这个W是iWindow类.是APP进程提供给WMS的回调binder. 每个WindowState 标识一个窗口.而一个activity可能有多个窗口,因此,就是一个activity在WMS里对应多个WindowState.也对应多个IWindow的回调. 但是他们通过同一个WindowToken来对应APP端的一个activity.

PhoneWindow.每个activity里一个.标识一个窗口,他持有窗口的属性layoutparams.控件树,和控件触摸事件的回调.是Activity和控件树的中间管理.

Viewrootimpl. 每个控件树的管理者.每个控件树有一个. 真正和WMS的交互是通过Viewrootimpl.他同时负责绘制流程的传递. 触摸事件的传递.activity的默认控件树是DocerView. ViewRootImpl里有内部类IWindow,也就是他接受WMS的回调. Viewrootimpl接受触摸实际发生后.会传给控件树. 对应activity则是docerview,而Docerview会先把时间传递给对应的activity.如果activity不处理,在传递给控件树,如果控件树还不处理.最后还是返回给activity.

IWindowSession. 这是APP端和WMS通信的binder. 每个进程唯一.

WindowManager.每个activity一个.用来管理窗口的添加更新等.他只是一个代理.把事件会传递给WindowManagerGlobal

WindowManagerGlobal .每个进程一个. 负责管理该进程所有窗口的添加等.ViewRootImpl就是由他为每个view控件树创建的

最后附一张总图

activityqwerqwe
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275

推荐阅读更多精彩内容