android悬浮窗口的实现

来源:互联网 发布:java工程师要多少钱 编辑:程序博客网 时间:2024/04/27 17:54

转自:http://blog.csdn.net/stevenhu_223/article/details/8504058

       

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

     FxService的代码如下:

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

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

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

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