• 【转】android源码分析之windowmanager (android悬浮窗口的实现)


    本文转自 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 plaincopy
     
    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 plaincopy
     
    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 plaincopy
     
    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

  • 相关阅读:
    Ajax实现动态的二级级联菜单
    使用 Struts 2 实现国际化
    使用 Struts2 校验器校验用户注册信息
    Python基础 9.正则
    Python基础 8.迭代器 生成器 包
    Python基础 7.异常
    Python基础 6 .文件操作
    Python基础 5.类
    Python基础 4.函数
    Python基础 3 控制流程
  • 原文地址:https://www.cnblogs.com/xiaoQLu/p/3108855.html
Copyright © 2020-2023  润新知