首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Window源码分析之Activity篇

Window源码分析之Activity篇

作者头像
open
发布2020-03-19 16:53:46
6280
发布2020-03-19 16:53:46
举报

前言

Window表示一个窗口的概念,在日常开发中直接接触Window的机会并不多,但是在某些特殊时候我们需要在桌面上显示一个类似悬浮窗的东西,那么这种效果就需要用到Window来实现。Window是一个抽象类,它的具体实现是PhoneWindow。本文主要整理了Window有关源码的部分分析记录, 可能有差错的地方,请批评指正。

提示:文中链接需要点击文章末尾处阅读原文才能点击。

1

先看结论

  1. Window是一个抽象类,具体的实现是PhoneWindow;
  2. android系统中,每个界面,对应着一个Window;但其实在android系统中Window也是一个抽象的概念,它是以View的形式存在;在使用中, 无法直接访问Window,只能通过WindowManager才能访问Window;每个Window都对应一个View和一个ViewRootImpl,ViewRootImpl是连接Window和WMS的桥梁,WMS的一些消息,通过ViewRootImpl转发给View;
  3. WindowManager继承自ViewManager(间接证明Window其实对应的是View?),常用的只有三个方法:addView、updateView和removeView;
  4. 各种Window的不同,主要是 token及type的不同;
  5. app中控制Window,是通过WindowManager.LayoutParams去控制,eg: 通过x,y,gravity去控制位置。

2

剖析内部

第一步

在Activity#attach中,可以看出创建了一个PhoneWindow对象,并且通过context.getSystemService(Context.WINDOW_SERVICE)设置了WindowManager对象,还通过Window#getWindowManager对Activity的mWindowManager赋值,这样Activity就与Window#WindowManager就关联起来了。

 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) {
   attachBaseContext(context);
   ....
   //创建对应的Window 并设置callback, 其实为PhoneWindow
   mWindow = new PhoneWindow(this);
   mWindow.setCallback(this);
   mWindow.setOnWindowDismissedCallback(this);
   ....
   // 设置Window的WindowManager, 对Window的mWindowManager赋值,
   // 事实上, Window中 并未使用传递进去的windowManager, 而是在此方法中 调用WindowManagerImpl.createLocalWindowManager 重新创建了一个
   mWindow.setWindowManager(
           (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
           mToken, mComponent.flattenToString(),
           (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
   if (mParent != null) {
       mWindow.setContainer(mParent.getWindow());
   }
   // 把window对象中的 windowManager 关联到 Activity的 mWindowManager
   mWindowManager = mWindow.getWindowManager();
}

第二步

接着看下面代码,里面会通过context.getSystemService(Context.WINDOWSERVICE)拿到一个windowManager对象,这个Window对象是什么呢?如果对Context有过研究的话,可以知道Context的实际对象是ContextImpl,其中所有通过getSystemService返回的对象,都是通过static的代码块静态添加的,只需找WINDOWSERVICE注册的地方(在6.0中 注册的代码 已经提取到android.app.SystemServiceRegistry#registerService中了)。

registerService(Context.WINDOW_SERVICE, WindowManager.class,
              new CachedServiceFetcher() {
          @Override
          public WindowManager createService(ContextImpl ctx) {
              return new WindowManagerImpl(ctx.getDisplay());
          }});

可以看见,实际的对象是WindowManagerImpl;(另外 Activity本身重写了getSystemService方法,如果使用android.app.Activity#getSystemService,返回的其实不是这个对象,下面会说到)。

第三步

下面代码说到,android.view.Window#setWindowManager中,并未使用传递进去的WindowManager,而创建了一个新对象,可以看一下代码:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
           boolean hardwareAccelerated) {
       // activity把token传进来, 保存在Window.mAppToken
       mAppToken = appToken;
       mAppName = appName;
       mHardwareAccelerated = hardwareAccelerated
               || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
       if (wm == null) {
           wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
       }
       //创建了一个新的WindowManager, 并且把this 传递给了WindowManager
       mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
   }

而在android.view.WindowManagerImpl#createLocalWindowManager中,可以明显看出:

// Context.getSystemService 获得的WindowManager实例, 是没有parentWindow的
public WindowManagerImpl(Display display) {
       this(display, null);
   }private WindowManagerImpl(Display display, Window parentWindow) {
   mDisplay = display;
   mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
   // 将传递的 window对象保存, 对于activity来说,会将PhoneWindow对应的对象传入, 而对于 Context.getSystemService 获得的WindowManager实例, 是没有parentWindow的
   return new WindowManagerImpl(mDisplay, parentWindow);
}

第四步

Window时我们看见的窗口,接下来看在Activity的Window是怎么显示出来,看Activity.makeVisiable:

void makeVisible() {
   if (!mWindowAdded) {
       ViewManager wm = getWindowManager();
       // windowManger中 添加window的根view( 即 decorView)
       wm.addView(mDecor, getWindow().getAttributes());
       mWindowAdded = true;
   }
   mDecor.setVisibility(View.VISIBLE);
}

代码很简单, 最后调用的是 WindowManagerGlobal#addView, 并且WindowManagerGlobal是一个单例。

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
   applyDefaultToken(params);
   mGlobal.addView(view, params, mDisplay, mParentWindow);
}

第五步

接着看一下android.view.ViewRootImpl#setView:

public void addView(View view, ViewGroup.LayoutParams params,
           Display display, Window parentWindow) {
             ...
        // 首先获得 layoutParams
       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
       if (parentWindow != null) {
           // parentWindow是 WindowManagerImpl的成员变量mParentWindow;
           // 对于 activity来说, 即在mWindow.setWindowManager中 调用WindowManagerImpl.createLocalWindowManager(Window)去创建 mWindowManager实例时, 传入的phoneWindow对象
           // 调用下面这个方法, 会调整 WindowManager.LayoutParams, 主要是 设置 token 和硬件加速的标志位
           parentWindow.adjustLayoutParamsForSubWindow(wparams);
       } else {
           final Context context = view.getContext();
           if (context != null
                   && (context.getApplicationInfo().flags
                           & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
               wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
           }
       }       ViewRootImpl root;
       View panelParentView = null;
       ....
       // ViewRootImpl是一个很重要的类, 是ViewParent的子类, 可以说是Window和WMS之间的桥梁, 所有和View有关的Evnet,layout,draw,都在底层产生后通过WMS->ViewRootImpl传递给view
       root = new ViewRootImpl(view.getContext(), display);
       view.setLayoutParams(wparams);
       mViews.add(view);
       mRoots.add(root);
       mParams.add(wparams);
       ...
       root.setView(view, wparams, panelParentView);
       ...
}

在WindowManagerGlobal中,会为每一个Window创建对应的ViewRootImpl,并把对应的decorView,ViewRootImpl 和 WindowManager.LayoutParams保存起来。 最后调用ViewRootImpl#setView,把decorView通过android.view.ViewRootImpl#setView添加进WMS。

第六步

接着看一下android.view.ViewRootImpl#setView:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     ...
     // 把Window对应的View保存下来, 对应的其实就是 PhoneWindow的 mDecorView
     mView = view;
     ...
     // 调用WMS后返回的结果
    int res; /* = WindowManagerImpl.ADD_OKAY; */    // Schedule the first layout -before- adding to the window
    // manager, to make sure we do the relayout before receiving
    // any other events from the system.
    // 请求 layout
    requestLayout();
   ....   // 调用 WMS添加window, 并返回一个结果 用于判定添加的结果
   res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                           getHostVisibility(), mDisplay.getDisplayId(),
                           mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                           mAttachInfo.mOutsets, mInputChannel);
       ......                                 // 根据返回的结果 判断是否添加成功, 没成功 就throw exception, 经常看见的services打开dialog的异常 就在这儿产生
   if (res < WindowManagerGlobal.ADD_OKAY) {
       mAttachInfo.mRootView = null;
       mAdded = false;
       mFallbackEventHandler.setView(null);
       unscheduleTraversals();
       setAccessibilityFocus(null, null);
       switch (res) {
           case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
           case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
               throw new WindowManager.BadTokenException(
                       "Unable to add window -- token " + attrs.token
                       + " is not valid; is your activity running?");
           case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
               throw new WindowManager.BadTokenException(
                       "Unable to add window -- token " + attrs.token
                       + " is not for an application");  
           .....
         }                          
    ......

在ViewRootImpl里面,首先会把Window对应的decorView存一份,然后通过mWindowSession向WMS发消息,mWindowSession是通过WindowManagerGlobal的静态方法获得,前面说过,WindowManagerGlobal这玩意是一个单例,在哪儿都可以拿到;

android本身是一个CS架构,调用WMS,需要先获取WMS对应的client, 而client端的获取,就是同 WindowManagerGlobal.initialize()进行,连通WMS的服务端;

mWindowSession.addToDisplay最终调用的会是com.android.server.wm.Session#addToDisplay。

最终会还是会调用com.android.server.wm.WindowManagerService#addWindow;这一段在WMS Server端的代码也比较多,就不贴代码了;最后在WindowManagerService#addWindow中会对WindowManager.LayoutParams做一些检验并返回值。

上面说过事件是通过WMS传递给ViewRootImpl,然后传递给View,Activity, 具体事件在ViewRootImpl的分发过程, 可以看这篇博客:Android中MotionEvent的来源和ViewRootImpl。

第七步

这里整理了一个简单的流程图,希望能够帮助我们理解。

到此Activity的Window,就完成了创建,显示。

小贴士

本文版权归Open软件开发小组所有,如需转载请联系主编申请授权。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Open软件开发小组 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档