android悬浮窗口的实现

摘要:
LocalWindowManager:在源代码的活动类中,有一个重要的成员变量mWindow,还有一个成员变量mWindowManager。在PhoneWindow中,还有一个与活动同名的mWindowManager成员变量。此外,“活动”中的mWindowManager是通过初始化Window类中的setWindowManager函数获得的。LocalWindowManager摘要:1.此类是Window的内部类。它的父类是CompatModelWrapper。它还实现了WindowManager界面。此变量通过调用Window的setWindowManager方法初始化。它实际上是一个LocalWindowManger对象。CompatModelWrapper:这个类是浮动窗口的一个重要类。然后,可以在“活动”或“服务”中实现特定的实现操作。

当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口)。那么这种不受Activity界面影响的悬浮窗口是怎么实现的呢?

竟然它能悬浮在手机桌面,且不受Activity界面的影响,说明该悬浮窗口是不隶属于Activity界面的,也就是说,他是隶属于启动它的应用程序所在进程。如360App所在的应用进程,当杀掉它所在的应用进程时,它才会消失。

悬浮窗口的实现涉及到WindowManager(基于4.0源码分析),它是一个接口,实现类有WindowManagerImplCompatModeWrapper(WindowManagerImpl的内部类),LocalWindowManagerWindow的内部类),它们之间的关系如下图的类图:

android悬浮窗口的实现第1张

WindowManagerImpl:

1.是WindowManager的实现类,windowmanager的大部分操作都在这里实现,但是并不会直接调用,而是作为LocalWindowManager和WindowManagerImpl.CompatModeWrapper的成员变量来使用。

2.在WindowManagerImpl中有3个数组View[],ViewRoot[],WindowManager.LayoutParams[],分别用来保存每个图层的数据。

3.WindowManagerImpl最重要的作用就是用来管理View,LayoutParams, 以及ViewRoot这三者的对应关系。

LocalWindowManager:

在源码的Activity类中,有一个重要的成员变量mWindow(它的实现类为PhoneWindow),同时也有一个成员变量mWindowManager(跟踪源码可知它是一个LocalWindowManager),而在PhoneWindow中同时也有和Activity相同名字的mWindowManager成员变量而且Activity中的mWindowManager是通过Window类中的setWindowManager函数初始化获取的。

所以,在Activity中的LocalWindowManager的生命周期是小于Activity的生命周期的而且在ActivityThread每创建一个Activity时都有该Activity对应的一个属于它的LocalWindowManager

LocalWindowManager的小结:

1.该类是Window的内部类,父类为CompatModeWrapper,同样都是实现WindowManager接口。

2.每个Activity中都有一个mWindowManager成员变量,Window类中 也有相应的同名字的该成员变量。该变量是通过调用Window的setWindowManager方法初始化得到的,实际上是一个LocalWindowManger对象。

3.也就说,每生成的一个Activity里都会构造一个其相应LocalWindowManger来管理该Activity承载的图层。(该对象可以通过Activity.getWindowManager或getWindow().getWindowManager获取)

4.LocalWindowMangers 的生命周期小于Activity的生命周期,(因为mWindowManager是Window的成员变量,而mWindow又是Activity的成员变量),所以,如果我们在一个LocalwindowManager中手动添加了其他的图层, 在Activity的finish执行之前, 应该先调用LocalwindowManager的removeView, 否则会抛出异常。

CompatModeWrapper:

该类就是实现悬浮窗口的重要类了。

跟踪源码可知:

1.CompatModeWrapper相当于是一个壳,而真正实现大部分功能的是它里面的成员变量mWindowManager(WindowManagerImpl类)。

2.该对象可以通过getApplication().getSystemService(Context.WINDOW_SERVICE)得到。(注:如果是通过activity.getSystemService(Context.WINDOW_SERVICE)得到的只是属于Activity的LocalWindowManager)。

3.这个对象的创建是在每个进程开始的时候, 通过ContextImpl中的静态代码块创建的, 它使用了单例模式, 保证每个application只有一个。

4.通过该类可以实现创建添加悬浮窗口,也就是说,在退出当前Activity时,通过该类创建的视图还是可见的,它是属于整个应用进程的视图,存活在进程中,不受Activity的生命周期影响。

ok,在通过上面对WindowManager接口的实现类做一些简要的介绍后,接下来就动手编写实现悬浮窗口的App。既然我们知道可以通过getApplication().getSystemService(Context.WINDOW_SERVICE)得到CompatModeWrapper,然后实现应用添加悬浮窗口视图。那么,具体的实现操作可以在Activity或者Service中(这两者都是可以创建存活在应用进程中的android重要组件)实现。

下面的App程序代码实现通过主Activity的启动按钮,启动一个Service,然后在Service中创建添加悬浮窗口:

要获取CompatModeWrapper,首先得在应用程序的AndroidManifest.xml文件中添加权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

MainActivity的代码如下:

  1. publicclassMainActivityextendsActivity
  2. {
  3. @Override
  4. publicvoidonCreate(BundlesavedInstanceState)
  5. {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.main);
  8. //获取启动按钮
  9. Buttonstart=(Button)findViewById(R.id.start_id);
  10. //获取移除按钮
  11. Buttonremove=(Button)findViewById(R.id.remove_id);
  12. //绑定监听
  13. start.setOnClickListener(newOnClickListener()
  14. {
  15. @Override
  16. publicvoidonClick(Viewv)
  17. {
  18. //TODOAuto-generatedmethodstub
  19. Intentintent=newIntent(MainActivity.this,FxService.class);
  20. //启动FxService
  21. startService(intent);
  22. finish();
  23. }
  24. });
  25. remove.setOnClickListener(newOnClickListener()
  26. {
  27. @Override
  28. publicvoidonClick(Viewv)
  29. {
  30. //uninstallApp("com.phicomm.hu");
  31. Intentintent=newIntent(MainActivity.this,FxService.class);
  32. //终止FxService
  33. stopService(intent);
  34. }
  35. });
  36. }
  37. }

FxService的代码如下:

  1. packagecom.phicomm.hu;
  2. importandroid.app.Service;
  3. importandroid.content.Intent;
  4. importandroid.graphics.PixelFormat;
  5. importandroid.os.Handler;
  6. importandroid.os.IBinder;
  7. importandroid.util.Log;
  8. importandroid.view.Gravity;
  9. importandroid.view.LayoutInflater;
  10. importandroid.view.MotionEvent;
  11. importandroid.view.View;
  12. importandroid.view.WindowManager;
  13. importandroid.view.View.OnClickListener;
  14. importandroid.view.View.OnTouchListener;
  15. importandroid.view.WindowManager.LayoutParams;
  16. importandroid.widget.Button;
  17. importandroid.widget.LinearLayout;
  18. importandroid.widget.Toast;
  19. publicclassFxServiceextendsService
  20. {
  21. //定义浮动窗口布局
  22. LinearLayoutmFloatLayout;
  23. WindowManager.LayoutParamswmParams;
  24. //创建浮动窗口设置布局参数的对象
  25. WindowManagermWindowManager;
  26. ButtonmFloatView;
  27. privatestaticfinalStringTAG="FxService";
  28. @Override
  29. publicvoidonCreate()
  30. {
  31. //TODOAuto-generatedmethodstub
  32. super.onCreate();
  33. Log.i(TAG,"oncreat");
  34. createFloatView();
  35. }
  36. @Override
  37. publicIBinderonBind(Intentintent)
  38. {
  39. //TODOAuto-generatedmethodstub
  40. returnnull;
  41. }
  42. privatevoidcreateFloatView()
  43. {
  44. wmParams=newWindowManager.LayoutParams();
  45. //获取的是WindowManagerImpl.CompatModeWrapper
  46. mWindowManager=(WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);
  47. Log.i(TAG,"mWindowManager--->"+mWindowManager);
  48. //设置windowtype
  49. wmParams.type=LayoutParams.TYPE_PHONE;
  50. //设置图片格式,效果为背景透明
  51. wmParams.format=PixelFormat.RGBA_8888;
  52. //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
  53. wmParams.flags=LayoutParams.FLAG_NOT_FOCUSABLE;
  54. //调整悬浮窗显示的停靠位置为左侧置顶
  55. wmParams.gravity=Gravity.LEFT|Gravity.TOP;
  56. //以屏幕左上角为原点,设置x、y初始值,相对于gravity
  57. wmParams.x=0;
  58. wmParams.y=0;
  59. //设置悬浮窗口长宽数据
  60. wmParams.width=WindowManager.LayoutParams.WRAP_CONTENT;
  61. wmParams.height=WindowManager.LayoutParams.WRAP_CONTENT;
  62. /*//设置悬浮窗口长宽数据
  63. wmParams.width=200;
  64. wmParams.height=80;*/
  65. LayoutInflaterinflater=LayoutInflater.from(getApplication());
  66. //获取浮动窗口视图所在布局
  67. mFloatLayout=(LinearLayout)inflater.inflate(R.layout.float_layout,null);
  68. //添加mFloatLayout
  69. mWindowManager.addView(mFloatLayout,wmParams);
  70. //浮动窗口按钮
  71. mFloatView=(Button)mFloatLayout.findViewById(R.id.float_id);
  72. mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0,
  73. View.MeasureSpec.UNSPECIFIED),View.MeasureSpec
  74. .makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED));
  75. Log.i(TAG,"Width/2--->"+mFloatView.getMeasuredWidth()/2);
  76. Log.i(TAG,"Height/2--->"+mFloatView.getMeasuredHeight()/2);
  77. //设置监听浮动窗口的触摸移动
  78. mFloatView.setOnTouchListener(newOnTouchListener()
  79. {
  80. @Override
  81. publicbooleanonTouch(Viewv,MotionEventevent)
  82. {
  83. //TODOAuto-generatedmethodstub
  84. //getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标
  85. wmParams.x=(int)event.getRawX()-mFloatView.getMeasuredWidth()/2;
  86. Log.i(TAG,"RawX"+event.getRawX());
  87. Log.i(TAG,"X"+event.getX());
  88. //减25为状态栏的高度
  89. wmParams.y=(int)event.getRawY()-mFloatView.getMeasuredHeight()/2-25;
  90. Log.i(TAG,"RawY"+event.getRawY());
  91. Log.i(TAG,"Y"+event.getY());
  92. //刷新
  93. mWindowManager.updateViewLayout(mFloatLayout,wmParams);
  94. returnfalse;//此处必须返回false,否则OnClickListener获取不到监听
  95. }
  96. });
  97. mFloatView.setOnClickListener(newOnClickListener()
  98. {
  99. @Override
  100. publicvoidonClick(Viewv)
  101. {
  102. //TODOAuto-generatedmethodstub
  103. Toast.makeText(FxService.this,"onClick",Toast.LENGTH_SHORT).show();
  104. }
  105. });
  106. }
  107. @Override
  108. publicvoidonDestroy()
  109. {
  110. //TODOAuto-generatedmethodstub
  111. super.onDestroy();
  112. if(mFloatLayout!=null)
  113. {
  114. //移除悬浮窗口
  115. mWindowManager.removeView(mFloatLayout);
  116. }
  117. }
  118. }

悬浮窗口的布局文件为R.layout.float_layout,所以,如果我们想设计一个非常美观的悬浮窗口,可以在该布局文件里编写。当然,也可以使用自定义View来设计(哈哈,少年们,在此基础上发挥想象吧)。

上面代码的效果图如下:左边为启动界面。点击“启动悬浮窗口”按钮,会启动后台service创建悬浮窗口,同时finish当前Activity,这样一个悬浮窗口就创建出来了,该窗口可实现任意位置移动,且可点击监听创建Toast提示(当然,也可以启动一个Activity)。若要移除已创建的窗口,可点击“移除悬浮窗口按钮”,或者强制禁止该应用进程。

android悬浮窗口的实现第2张android悬浮窗口的实现第3张

同样的,在一个Activity里绘制悬浮视图,不过下面的代码主要还是验证区分LocalWindowManger和CompatModeWrapper添加的视图。

LocalWindowManger可通过activity.getSystemService(Context.WINDOW_SERVICE)或getWindow().getWindowManager获取。当我们通过LocalWindowManger添加视图时,退出Activity,添加的视图也会随之消失。

验证代码如下:

  1. packagecom.phicomm.hu;
  2. importandroid.app.Activity;
  3. importandroid.content.Context;
  4. importandroid.content.Intent;
  5. importandroid.graphics.PixelFormat;
  6. importandroid.os.Bundle;
  7. importandroid.util.Log;
  8. importandroid.view.Gravity;
  9. importandroid.view.LayoutInflater;
  10. importandroid.view.MotionEvent;
  11. importandroid.view.View;
  12. importandroid.view.WindowManager;
  13. importandroid.view.View.OnClickListener;
  14. importandroid.view.View.OnTouchListener;
  15. importandroid.view.WindowManager.LayoutParams;
  16. importandroid.widget.Button;
  17. importandroid.widget.LinearLayout;
  18. publicclassFloatWindowTestextendsActivity
  19. {
  20. /**Calledwhentheactivityisfirstcreated.*/
  21. privatestaticfinalStringTAG="FloatWindowTest";
  22. WindowManagermWindowManager;
  23. WindowManager.LayoutParamswmParams;
  24. LinearLayoutmFloatLayout;
  25. ButtonmFloatView;
  26. @Override
  27. publicvoidonCreate(BundlesavedInstanceState)
  28. {
  29. super.onCreate(savedInstanceState);
  30. //createFloatView();
  31. setContentView(R.layout.main);
  32. Buttonstart=(Button)findViewById(R.id.start);
  33. Buttonstop=(Button)findViewById(R.id.stop);
  34. start.setOnClickListener(newOnClickListener()
  35. {
  36. @Override
  37. publicvoidonClick(Viewv)
  38. {
  39. //TODOAuto-generatedmethodstub
  40. createFloatView();
  41. //finish();
  42. //handle.post(r);
  43. }
  44. });
  45. stop.setOnClickListener(newOnClickListener()
  46. {
  47. @Override
  48. publicvoidonClick(Viewv)
  49. {
  50. //TODOAuto-generatedmethodstub
  51. if(mFloatLayout!=null)
  52. {
  53. mWindowManager.removeView(mFloatLayout);
  54. finish();
  55. }
  56. }
  57. });
  58. }
  59. privatevoidcreateFloatView()
  60. {
  61. //获取LayoutParams对象
  62. wmParams=newWindowManager.LayoutParams();
  63. //获取的是LocalWindowManager对象
  64. mWindowManager=this.getWindowManager();
  65. Log.i(TAG,"mWindowManager1--->"+this.getWindowManager());
  66. //mWindowManager=getWindow().getWindowManager();
  67. Log.i(TAG,"mWindowManager2--->"+getWindow().getWindowManager());
  68. //获取的是CompatModeWrapper对象
  69. //mWindowManager=(WindowManager)getApplication().getSystemService(Context.WINDOW_SERVICE);
  70. Log.i(TAG,"mWindowManager3--->"+mWindowManager);
  71. wmParams.type=LayoutParams.TYPE_PHONE;
  72. wmParams.format=PixelFormat.RGBA_8888;;
  73. wmParams.flags=LayoutParams.FLAG_NOT_FOCUSABLE;
  74. wmParams.gravity=Gravity.LEFT|Gravity.TOP;
  75. wmParams.x=0;
  76. wmParams.y=0;
  77. wmParams.width=WindowManager.LayoutParams.WRAP_CONTENT;
  78. wmParams.height=WindowManager.LayoutParams.WRAP_CONTENT;
  79. LayoutInflaterinflater=this.getLayoutInflater();//LayoutInflater.from(getApplication());
  80. mFloatLayout=(LinearLayout)inflater.inflate(R.layout.float_layout,null);
  81. mWindowManager.addView(mFloatLayout,wmParams);
  82. //setContentView(R.layout.main);
  83. mFloatView=(Button)mFloatLayout.findViewById(R.id.float_id);
  84. Log.i(TAG,"mFloatView"+mFloatView);
  85. Log.i(TAG,"mFloatView--parent-->"+mFloatView.getParent());
  86. Log.i(TAG,"mFloatView--parent--parent-->"+mFloatView.getParent().getParent());
  87. //绑定触摸移动监听
  88. mFloatView.setOnTouchListener(newOnTouchListener()
  89. {
  90. @Override
  91. publicbooleanonTouch(Viewv,MotionEventevent)
  92. {
  93. //TODOAuto-generatedmethodstub
  94. wmParams.x=(int)event.getRawX()-mFloatLayout.getWidth()/2;
  95. //25为状态栏高度
  96. wmParams.y=(int)event.getRawY()-mFloatLayout.getHeight()/2-40;
  97. mWindowManager.updateViewLayout(mFloatLayout,wmParams);
  98. returnfalse;
  99. }
  100. });
  101. //绑定点击监听
  102. mFloatView.setOnClickListener(newOnClickListener()
  103. {
  104. @Override
  105. publicvoidonClick(Viewv)
  106. {
  107. //TODOAuto-generatedmethodstub
  108. Intentintent=newIntent(FloatWindowTest.this,ResultActivity.class);
  109. startActivity(intent);
  110. }
  111. });
  112. }
  113. }

将上面的代码相关注释部分取消,然后运行代码查看Log信息,那么就可以知道问题所在了(每一个Activity对应一个LocalWindowManger,每一个App对应一个CompatModeWrapper),所以要实现在App所在进程中运行的悬浮窗口,当然是得要获取CompatModeWrapper,而不是LocalWindowManger

本文相关的完整代码下载链接:http://download.csdn.net/detail/stevenhu_223/4996970

免责声明:文章转载自《android悬浮窗口的实现》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇基于时间干涉电场的非侵入式深部脑刺激法码云推送项目总是没有权限下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android中突发情况Activity数据的保存和恢复

Android中突发情况Activity数据的保存和恢复 写在前面:在我们的APP使用的过程中,总有可能出现各种手滑、被压在后台、甚至突然被杀死的情况。所以对APP中一些临时数据或关键持久型数据,就需要我们使用正确的方式进行保存或恢复。 突发情况都有哪些? 因为本文讨论的是当一些突发情况的出现时,对数据的保存和恢复。所以现在总结一下突发情况应该都有哪些?...

Android中activity背景色的设置

主题Theme就是用来设置界面UI风格,可以设置整个应用或者某个活动Activity的界面风格。在Android SDK中内置了下面的Theme,可以按标题栏Title Bar和状态栏Status Bar是否可见来分类: [html] view plaincopyprint? •android:theme="@android:style/Th...

Android 虚拟现实(virtual reality)入门指南

入门指南 本文档介绍怎样使用实验性的 Cardboard SDK for Android 创建您自己的虚拟实境 (VR) 体验。 Android 演示版应用:Treasure Hunt 本教程中的代码演示样例摘自“Treasure Hunt”Android 演示版应用。 Cardboard 是一个简单的设备。可让智能手机发挥虚拟实境平台的威力。 Card...

[Android Memory] App调试内存泄露之Context篇(上)

转载自:http://www.cnblogs.com/qianxudetianxia/p/3645106.html Context作为最基本的上下文,承载着Activity,Service等最基本组件。当有对象引用到Activity,并不能被回收释放,必将造成大范围的对象无法被回收释放,进而造成内存泄漏。 下面针对一些常用场景逐一分析。 1. CallBa...

Activity快速入门理解

​ 在Java领域,JBPM和Activity是两个主流的工作流系统,而Activity的出现无疑将会取代JBPM(Activity的开发者就是从Jbpm开发者出来的 1. 1个插件 在Eclipse中安装Activity插件,让你可以在Eclipse中绘制Activity工作流图 2. 1个引擎 ProcessEngine对象,Activity工作流引擎...

深入研究 .NET 5 的开放式遥测

OpenTelemetry 介绍 OpenTelemetry是一种开放的源代码规范,工具和SDK,用于检测,生成,收集和导出遥测数据(指标,日志和跟踪),开放遥测技术得到了Cloud Native Computing Foundation(CNCF)的支持,该基金会支持一系列流行的优秀的开源项目,你可以去看一下CNCF景观图,https://landsca...