Android 浮窗开发之窗口层级

qndf4328 8年前
   <p>最近在项目中遇到了这样的需求:需要在特定的其他应用之上悬浮自己的UI交互(拖动、输入等复杂的UI交互),和九游的浮窗类似,不过我们的比九游的体验更好,我们越过了很多授权的限制。</p>    <p><img src="https://simg.open-open.com/show/462b1a7ea916bebf8e650dda14335a1d.gif"></p>    <p>很多人都知道如何去实现一个简单的浮窗,但是却很少有人去深入的研究背后的流程机制,由于项目中浮窗交互比较复杂,遇到了些坑查看了很多资料,故总结浮窗涉及到的知识点:</p>    <ul>     <li>窗口层级关系(浮窗是如何“浮”的)?</li>     <li>浮窗有哪些限制,如何越过用户授权实现浮窗功能?</li>     <li>窗口与用户输入系统(Activity是如何接收到touch事件?)。</li>    </ul>    <p>本章我们来研究第一个问题:浮窗为何会浮。 浮窗之所以叫浮窗,是因为它能悬浮于应用或者桌面窗口之上,能脱离Activity而存在。为了研究其中区别,我们先来看看我们最熟悉的Activity是怎么显示出来的。</p>    <h2>Activity是怎么显示出来的?</h2>    <p>要弄清这个问题答案,我们先从Activity的setContentView()这个方法的源码开始找起,在Activity中看到setCententView的源码:</p>    <pre>  <code class="language-java">public void setContentView(int layoutResID) {          getWindow().setContentView(layoutResID);          initWindowDecorActionBar();      }</code></pre>    <p>getWindow是返回返回Activity的mWindow变量,指向一个Window的对象,Window是一个抽象类,这里返回的是PhoneWindow对象(PhoneWindow是Window的子类),PhoneWindow中有一个DecorView对象,decorView成员,这是一个FrameLayout,setContentView的子布局最终会添加到decorView中,这个decorView就是当前窗口的根视图,这个根视图是如何最终被绘制出来的?在ActivityThread中有这样一段代码:</p>    <pre>  <code class="language-java">l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;                  l.softInputMode |= forwardBit;                  if (a.mVisibleFromClient) {                      a.mWindowAdded = true;                      wm.addView(decor, l);                  }</code></pre>    <p>这个decorView,最终会被WindowManager.addView添加到绘制系统中,并类型是WindowManager.LayoutParams.TYPE_BASE_APPLICATION,这个参数决定了要绘制的窗口的z轴层次,为了避免思维栈过深,这里就不贴出详细的源码跟踪过程了,直接给结论。</p>    <p>先来看看Activity和window的关系:</p>    <p><img src="https://simg.open-open.com/show/255a2207bec2ec415cc2bbc33ac5f29d.jpg"></p>    <p>再来window和View的关系:</p>    <p><img src="https://simg.open-open.com/show/f04dde05ea6e56c6f8d59f239a28ed6d.jpg"></p>    <p>Activity窗口显示过程:</p>    <p><img src="https://simg.open-open.com/show/c3caebb7ccc50a0e408460ec28fe1f2a.png"></p>    <p>说Activity是怎么显示出来的,其实是说Activity管理的View是怎么显示出来的。最后再来总结一下:</p>    <p>一、Activity通过setContentView设置的视图是添加到PhoneWindow的根视图decor中。</p>    <p>二、Window是一个抽象的概念,Window关了了一个View(根视图),最终被WindowManager管理的还是一个View(根视图)和它的LayoutParams,视图绘制刷新都是通过WindowManager(WindowManagerGlobal)与WindowManagerServiceIPC交互调用底层绘制的。</p>    <p>三、Activity是四大组件中唯一和窗体紧密联系的组件(这是为什么会有初学者把Activity直接理解为绘制界面的原因),所有掌管的视图只不过是一种window和Dialog、Toast、墙纸所掌管的Window类型不一样。</p>    <h2>浮窗为什么会“浮”?</h2>    <p>上面讲到Activity的显示过程其实已经揭示了通用界面的显示过程,浮窗的显示过程更为简单:</p>    <p><img src="https://simg.open-open.com/show/74406b76b81a2ebdea756a4b88cc4483.png"></p>    <p>做过浮窗的同学应该都明白了,为啥浮窗能脱离Activity而显示,本质上我们是把一个View交给WindowManager来管理了,LayoutParams.type类型决定了这个View显示窗口的类型,不同类型显示的窗口层次(z轴)是不一样的。大方面来讲可以分为应用窗口(APPLICATION_WINDOW)、子窗口(SUB_WINDOW)、系统窗口(SYSTEM_WINDOW)三种类型,应用窗口z轴范围是1~99,子窗口的范围是1001~1999,系统窗口是(2000~2999),所以要实现浮动窗口我们只能在系统窗口范围中实现。</p>    <table align="left">     <tbody>      <tr>       <td>类型</td>       <td>常量范围</td>       <td>子类</td>       <td>常量值</td>       <td>说明</td>       <td>例子</td>      </tr>      <tr>       <td>APPLICATION_WINDOW</td>       <td>1~99</td>       <td>TYPE_BASE_APPLICATION</td>       <td>1</td>       <td> </td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_APPLICATION</td>       <td>2</td>       <td>应用窗口</td>       <td>大部分的应用程序窗口</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_APPLICATION_STARTING</td>       <td>3</td>       <td>应用程序的Activity显示之前由系统显示的窗口</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>LAST_APPLICATION_WINDOW</td>       <td>99</td>       <td> </td>       <td> </td>      </tr>      <tr>       <td>SUB_WINDOW</td>       <td>1000~1999</td>       <td>FIRST_SUB_WINDOW</td>       <td>1000</td>       <td> </td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_APPLICATION_PANEL</td>       <td>1000</td>       <td>显示在母窗口之上,遮挡其下面的应用窗口。</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_APPLICATION_MEDIA</td>       <td>1001</td>       <td>显示在母窗口之下,如果应用窗口不挖洞,即不可见。</td>       <td>SurfaceView,在小窗口显示时设为MEDIA, 全屏显示时设为PANEL</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_APPLICATION_SUB_PANEL</td>       <td>1002</td>       <td> </td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_APPLICATION_ATTACHED_DIALOG</td>       <td>1003</td>       <td> </td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_APPLICATION_MEIDA_OVERLAY</td>       <td>1004</td>       <td>用于两个SurfaceView的合成,如果设为MEDIA,则上面的SurfaceView 挡住下面的SurfaceView</td>       <td> </td>      </tr>      <tr>       <td>SYSTEM_WINDOW</td>       <td>2000~2999</td>       <td>TYPE_STATUS_BAR</td>       <td>2000</td>       <td>顶部的状态栏</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_SEARCH_BAR</td>       <td>2001</td>       <td>搜索窗口,系统中只能有一个搜索窗口</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_PHONE</td>       <td>2002</td>       <td>电话窗口</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_SYSTEM_ALERT</td>       <td>2003</td>       <td>警告窗口,在所有其他窗口之上显示</td>       <td>电量不足提醒窗口</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_KEYGUARD</td>       <td>2004</td>       <td>锁屏界面</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_TOAST</td>       <td>2005</td>       <td>短时的文字提醒小窗口</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_SYSTEM_OVERLAY</td>       <td>2006</td>       <td>没有焦点的浮动窗口</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_PRIORITY_PHONE</td>       <td>2007</td>       <td>紧急电话窗口,可以显示在屏保之上</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_SYSTEM_DIALOG</td>       <td>2008</td>       <td>系统信息弹出窗口</td>       <td>比如SIM插上后弹出的运营商信息窗口</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_KEYGUARD_DIALOG</td>       <td>2009</td>       <td>跟KeyGuard绑定的弹出对话框</td>       <td>锁屏时的滑动解锁窗口</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_SYSTEM_ERROR</td>       <td>2010</td>       <td>系统错误提示窗口</td>       <td>ANR 窗口</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_INPUT_METHOD</td>       <td>2011</td>       <td>输入法窗口,会挤占当前应用的空间</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_INPUT_METHOD_DIALOG</td>       <td>2012</td>       <td>弹出的输入法窗口,不会挤占当前应用窗口空间,在其之上显示</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_WALLPAPER</td>       <td>2013</td>       <td>墙纸</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_STATUS_BAR_PANEL</td>       <td>2014</td>       <td>从状态条下拉的窗口</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_SECURE_SYSTEM_OVERLAY</td>       <td>2015</td>       <td>只有系统用户可以创建的OVERLAY窗口</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_DRAG</td>       <td>2016</td>       <td>浮动的可拖动窗口</td>       <td>360安全卫士的浮动精灵</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_STATUS_BAR_PANEL</td>       <td>2017</td>       <td> </td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_POINTER</td>       <td>2018</td>       <td>光标</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_NAVIGATION_BAR</td>       <td>2019</td>       <td> </td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_VOLUME_OVERLAY</td>       <td>2020</td>       <td>音量调节窗口</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_BOOT_PROGRESS</td>       <td>2021</td>       <td>启动进度,在所有窗口之上</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_HIDDEN_NAV_CONSUMER</td>       <td>2022</td>       <td>隐藏的导航栏</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_DREAM</td>       <td>2023</td>       <td>屏保动画</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_NAVIGATION_BAR_PANEL</td>       <td>2024</td>       <td>Navigation bar 弹出的窗口</td>       <td>比如说应用收集栏</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_UNIVERSAL_BACKGROUND</td>       <td>2025</td>       <td> </td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_DISPLAY_OVERLAY</td>       <td>2026</td>       <td>用于模拟第二显示设备</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_MAGNIFICATION</td>       <td>2027</td>       <td>用于放大局部</td>       <td> </td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>TYPE_RECENTS_OVERLAY</td>       <td>2028</td>       <td>当前应用窗口,多用户情况下只显示在用户节目</td>       <td> </td>      </tr>     </tbody>    </table>    <p>到这里我们对Android系统的窗口层次有个大致的了解了,Activity是Android应用的四大组件之一,描述的是应用的活动状态和周期,受ActivityManagerService的管理;Window/View是图形窗口的抽象模型,描述的是窗口的绘制信息,受WindowManagerService的管理;Activity聚合Window来和图形窗口产生联系。文章旨在理解一下Android窗体系统的一个雏形,能力有限不能详细跟踪整个窗口体系的源码,有兴趣的可以自己深入,下一篇文章:《越过授权使用浮窗》。</p>    <p>来源:<a href="/misc/goto?guid=4959672295290713904">tuicool</a></p>