android悬浮窗口的实现

来源:互联网 发布:松下机器人编程视频 编辑:程序博客网 时间:2024/05/18 02:18

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

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

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

    

 

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的代码如下:

[java] view plain copy
  1. public class MainActivity extends Activity   
  2. {  
  3.   
  4.     @Override  
  5.     public void onCreate(Bundle savedInstanceState)  
  6.     {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.main);  
  9.         //获取启动按钮  
  10.         Button start = (Button)findViewById(R.id.start_id);  
  11.         //获取移除按钮  
  12.         Button remove = (Button)findViewById(R.id.remove_id);  
  13.         //绑定监听  
  14.         start.setOnClickListener(new OnClickListener()   
  15.         {  
  16.               
  17.             @Override  
  18.             public void onClick(View v)   
  19.             {  
  20.                 // TODO Auto-generated method stub  
  21.                 Intent intent = new Intent(MainActivity.this, FxService.class);  
  22.                 //启动FxService  
  23.                 startService(intent);  
  24.                 finish();  
  25.             }  
  26.         });  
  27.           
  28.         remove.setOnClickListener(new OnClickListener()   
  29.         {  
  30.               
  31.             @Override  
  32.             public void onClick(View v)   
  33.             {  
  34.                 //uninstallApp("com.phicomm.hu");  
  35.                 Intent intent = new Intent(MainActivity.this, FxService.class);  
  36.                 //终止FxService  
  37.                 stopService(intent);  
  38.             }  
  39.         });  
  40.           
  41.     }  
  42. }  

     FxService的代码如下:

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

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

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

      

 

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

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

        验证代码如下:

[java] view plain copy
  1. package com.phicomm.hu;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Context;  
  5. import android.content.Intent;  
  6. import android.graphics.PixelFormat;  
  7. import android.os.Bundle;  
  8. import android.util.Log;  
  9. import android.view.Gravity;  
  10. import android.view.LayoutInflater;  
  11. import android.view.MotionEvent;  
  12. import android.view.View;  
  13. import android.view.WindowManager;  
  14. import android.view.View.OnClickListener;  
  15. import android.view.View.OnTouchListener;  
  16. import android.view.WindowManager.LayoutParams;  
  17. import android.widget.Button;  
  18. import android.widget.LinearLayout;  
  19.   
  20. public class FloatWindowTest extends Activity   
  21. {  
  22.     /** Called when the activity is first created. */  
  23.       
  24.     private static final String TAG = "FloatWindowTest";  
  25.     WindowManager mWindowManager;  
  26.     WindowManager.LayoutParams wmParams;  
  27.     LinearLayout mFloatLayout;  
  28.     Button mFloatView;  
  29.     @Override  
  30.     public void onCreate(Bundle savedInstanceState)   
  31.     {  
  32.         super.onCreate(savedInstanceState);  
  33.         //createFloatView();  
  34.         setContentView(R.layout.main);  
  35.           
  36.         Button start = (Button)findViewById(R.id.start);  
  37.         Button stop = (Button)findViewById(R.id.stop);  
  38.           
  39.         start.setOnClickListener(new OnClickListener()   
  40.         {  
  41.               
  42.             @Override  
  43.             public void onClick(View v)  
  44.             {  
  45.                 // TODO Auto-generated method stub  
  46.                 createFloatView();  
  47.                 //finish();  
  48.                 //handle.post(r);  
  49.             }  
  50.         });  
  51.           
  52.         stop.setOnClickListener(new OnClickListener()  
  53.         {  
  54.               
  55.             @Override  
  56.             public void onClick(View v)   
  57.             {  
  58.                 // TODO Auto-generated method stub  
  59.                 if(mFloatLayout != null)  
  60.                 {  
  61.                     mWindowManager.removeView(mFloatLayout);  
  62.                     finish();  
  63.                 }     
  64.         }  
  65.         });  
  66.           
  67.           
  68.     }  
  69.       
  70.     private void createFloatView()  
  71.     {  
  72.         //获取LayoutParams对象  
  73.         wmParams = new WindowManager.LayoutParams();  
  74.           
  75.         //获取的是LocalWindowManager对象  
  76.         mWindowManager = this.getWindowManager();  
  77.         Log.i(TAG, "mWindowManager1--->" + this.getWindowManager());  
  78.         //mWindowManager = getWindow().getWindowManager();  
  79.         Log.i(TAG, "mWindowManager2--->" + getWindow().getWindowManager());  
  80.        
  81.         //获取的是CompatModeWrapper对象  
  82.         //mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);  
  83.         Log.i(TAG, "mWindowManager3--->" + mWindowManager);  
  84.         wmParams.type = LayoutParams.TYPE_PHONE;  
  85.         wmParams.format = PixelFormat.RGBA_8888;;  
  86.         wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;  
  87.         wmParams.gravity = Gravity.LEFT | Gravity.TOP;  
  88.         wmParams.x = 0;  
  89.         wmParams.y = 0;  
  90.         wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;  
  91.         wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;  
  92.           
  93.         LayoutInflater inflater = this.getLayoutInflater();//LayoutInflater.from(getApplication());  
  94.           
  95.         mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);  
  96.         mWindowManager.addView(mFloatLayout, wmParams);  
  97.         //setContentView(R.layout.main);  
  98.         mFloatView = (Button)mFloatLayout.findViewById(R.id.float_id);  
  99.           
  100.         Log.i(TAG, "mFloatView" + mFloatView);  
  101.         Log.i(TAG, "mFloatView--parent-->" + mFloatView.getParent());  
  102.         Log.i(TAG, "mFloatView--parent--parent-->" + mFloatView.getParent().getParent());  
  103.         //绑定触摸移动监听  
  104.         mFloatView.setOnTouchListener(new OnTouchListener()   
  105.         {  
  106.               
  107.             @Override  
  108.             public boolean onTouch(View v, MotionEvent event)   
  109.             {  
  110.                 // TODO Auto-generated method stub  
  111.                 wmParams.x = (int)event.getRawX() - mFloatLayout.getWidth()/2;  
  112.                 //25为状态栏高度  
  113.                 wmParams.y = (int)event.getRawY() - mFloatLayout.getHeight()/2 - 40;  
  114.                 mWindowManager.updateViewLayout(mFloatLayout, wmParams);  
  115.                 return false;  
  116.             }  
  117.         });  
  118.           
  119.         //绑定点击监听  
  120.         mFloatView.setOnClickListener(new OnClickListener()  
  121.         {  
  122.               
  123.             @Override  
  124.             public void onClick(View v)   
  125.             {  
  126.                 // TODO Auto-generated method stub  
  127.                 Intent intent = new Intent(FloatWindowTest.this, ResultActivity.class);  
  128.                 startActivity(intent);  
  129.             }  
  130.         });  
  131.           
  132.     }  
  133. }  

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

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

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 汽油进到眼睛了怎么办 汽油进了眼睛里怎么办 眼睛里面进了汽油怎么办 脱硫塔里的二氧化硫高怎么办 恐怖黎明铁匠选错怎么办 堡垒之夜草变色怎么办 火柴没有擦的了怎么办 乙醚倒进下水道了怎么办 乙醚和水不分层怎么办 乙醚闻多了头晕怎么办 爱乐维吃了便秘怎么办 刮完滑石粉墙面很软怎么办 被硫酸泼到皮肤怎么办 头磕了一下头晕怎么办 家里有事与工作不能请怎么办 撞了头头晕想吐怎么办 猫不小心摔一下怎么办 一氧化二氮中毒怎么办 电脑开机变慢了怎么办 怎么办抚顺韦德健身卡 预售健身卡合法吗怎么办 被浓硫酸泼到怎么办 婴儿误喝了生水怎么办 宝宝喝了生水拉肚子怎么办 因妈妈喝生水宝宝拉肚子怎么办 喝了几口生水怎么办 不小心吃到蟑螂怎么办 吃了有蛆的樱桃怎么办 不小心误食了蛆怎么办 吃了有蟑螂的汤怎么办 调节天平时指针向右怎么办 香薰蜡烛融化了怎么办 香薰蜡烛挂壁怎么办y 粗蜡烛只烧中间怎么办 紫薯馒头变绿怎么办 小孩手开水烫了怎么办 被油烫伤了怎么办才不留疤 烫伤水泡蹭破了怎么办 烧伤的水泡破了怎么办 烧伤后水泡破了怎么办 烫伤泡破了红肿怎么办