当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口)。那么这种不受Activity界面影响的悬浮窗口是怎么实现的呢?
竟然它能悬浮在手机桌面,且不受Activity界面的影响,说明该悬浮窗口是不隶属于Activity界面的,也就是说,他是隶属于启动它的应用程序所在进程。如360App所在的应用进程,当杀掉它所在的应用进程时,它才会消失。
悬浮窗口的实现涉及到WindowManager(基于4.0源码分析),它是一个接口,实现类有WindowManagerImpl,CompatModeWrapper(WindowManagerImpl的内部类),LocalWindowManager(Window的内部类),它们之间的关系如下图的类图:

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的代码如下:
- publicclassMainActivityextendsActivity
- {
-
- @Override
- publicvoidonCreate(BundlesavedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- Buttonstart=(Button)findViewById(R.id.start_id);
-
- Buttonremove=(Button)findViewById(R.id.remove_id);
-
- start.setOnClickListener(newOnClickListener()
- {
-
- @Override
- publicvoidonClick(Viewv)
- {
-
- Intentintent=newIntent(MainActivity.this,FxService.class);
-
- startService(intent);
- finish();
- }
- });
-
- remove.setOnClickListener(newOnClickListener()
- {
-
- @Override
- publicvoidonClick(Viewv)
- {
-
- Intentintent=newIntent(MainActivity.this,FxService.class);
-
- stopService(intent);
- }
- });
-
- }
- }
FxService的代码如下:
- packagecom.phicomm.hu;
-
- importandroid.app.Service;
- importandroid.content.Intent;
- importandroid.graphics.PixelFormat;
- importandroid.os.Handler;
- importandroid.os.IBinder;
- importandroid.util.Log;
- importandroid.view.Gravity;
- importandroid.view.LayoutInflater;
- importandroid.view.MotionEvent;
- importandroid.view.View;
- importandroid.view.WindowManager;
- importandroid.view.View.OnClickListener;
- importandroid.view.View.OnTouchListener;
- importandroid.view.WindowManager.LayoutParams;
- importandroid.widget.Button;
- importandroid.widget.LinearLayout;
- importandroid.widget.Toast;
-
- publicclassFxServiceextendsService
- {
-
-
- LinearLayoutmFloatLayout;
- WindowManager.LayoutParamswmParams;
-
- WindowManagermWindowManager;
-
- ButtonmFloatView;
-
- privatestaticfinalStringTAG="FxService";
-
- @Override
- publicvoidonCreate()
- {
-
- super.onCreate();
- Log.i(TAG,"oncreat");
- createFloatView();
- }
-
- @Override
- publicIBinderonBind(Intentintent)
- {
-
- returnnull;
- }
-
- privatevoidcreateFloatView()
- {
- wmParams=newWindowManager.LayoutParams();
-
- mWindowManager=(WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);
- Log.i(TAG,"mWindowManager--->"+mWindowManager);
-
- wmParams.type=LayoutParams.TYPE_PHONE;
-
- wmParams.format=PixelFormat.RGBA_8888;
-
- wmParams.flags=LayoutParams.FLAG_NOT_FOCUSABLE;
-
- wmParams.gravity=Gravity.LEFT|Gravity.TOP;
-
- wmParams.x=0;
- wmParams.y=0;
-
-
- wmParams.width=WindowManager.LayoutParams.WRAP_CONTENT;
- wmParams.height=WindowManager.LayoutParams.WRAP_CONTENT;
-
-
-
-
-
- LayoutInflaterinflater=LayoutInflater.from(getApplication());
-
- mFloatLayout=(LinearLayout)inflater.inflate(R.layout.float_layout,null);
-
- mWindowManager.addView(mFloatLayout,wmParams);
-
- mFloatView=(Button)mFloatLayout.findViewById(R.id.float_id);
-
- mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0,
- View.MeasureSpec.UNSPECIFIED),View.MeasureSpec
- .makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED));
- Log.i(TAG,"Width/2--->"+mFloatView.getMeasuredWidth()/2);
- Log.i(TAG,"Height/2--->"+mFloatView.getMeasuredHeight()/2);
-
- mFloatView.setOnTouchListener(newOnTouchListener()
- {
-
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent)
- {
-
-
- wmParams.x=(int)event.getRawX()-mFloatView.getMeasuredWidth()/2;
- Log.i(TAG,"RawX"+event.getRawX());
- Log.i(TAG,"X"+event.getX());
-
- wmParams.y=(int)event.getRawY()-mFloatView.getMeasuredHeight()/2-25;
- Log.i(TAG,"RawY"+event.getRawY());
- Log.i(TAG,"Y"+event.getY());
-
- mWindowManager.updateViewLayout(mFloatLayout,wmParams);
- returnfalse;
- }
- });
-
- mFloatView.setOnClickListener(newOnClickListener()
- {
-
- @Override
- publicvoidonClick(Viewv)
- {
-
- Toast.makeText(FxService.this,"onClick",Toast.LENGTH_SHORT).show();
- }
- });
- }
-
- @Override
- publicvoidonDestroy()
- {
-
- super.onDestroy();
- if(mFloatLayout!=null)
- {
-
- mWindowManager.removeView(mFloatLayout);
- }
- }
-
- }
悬浮窗口的布局文件为R.layout.float_layout,所以,如果我们想设计一个非常美观的悬浮窗口,可以在该布局文件里编写。当然,也可以使用自定义View来设计(哈哈,少年们,在此基础上发挥想象吧)。
上面代码的效果图如下:左边为启动界面。点击“启动悬浮窗口”按钮,会启动后台service创建悬浮窗口,同时finish当前Activity,这样一个悬浮窗口就创建出来了,该窗口可实现任意位置移动,且可点击监听创建Toast提示(当然,也可以启动一个Activity)。若要移除已创建的窗口,可点击“移除悬浮窗口按钮”,或者强制禁止该应用进程。


同样的,在一个Activity里绘制悬浮视图,不过下面的代码主要还是验证区分LocalWindowManger和CompatModeWrapper添加的视图。
LocalWindowManger可通过activity.getSystemService(Context.WINDOW_SERVICE)或getWindow().getWindowManager获取。当我们通过LocalWindowManger添加视图时,退出Activity,添加的视图也会随之消失。
验证代码如下:
- packagecom.phicomm.hu;
-
- importandroid.app.Activity;
- importandroid.content.Context;
- importandroid.content.Intent;
- importandroid.graphics.PixelFormat;
- importandroid.os.Bundle;
- importandroid.util.Log;
- importandroid.view.Gravity;
- importandroid.view.LayoutInflater;
- importandroid.view.MotionEvent;
- importandroid.view.View;
- importandroid.view.WindowManager;
- importandroid.view.View.OnClickListener;
- importandroid.view.View.OnTouchListener;
- importandroid.view.WindowManager.LayoutParams;
- importandroid.widget.Button;
- importandroid.widget.LinearLayout;
-
- publicclassFloatWindowTestextendsActivity
- {
-
-
- privatestaticfinalStringTAG="FloatWindowTest";
- WindowManagermWindowManager;
- WindowManager.LayoutParamswmParams;
- LinearLayoutmFloatLayout;
- ButtonmFloatView;
- @Override
- publicvoidonCreate(BundlesavedInstanceState)
- {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.main);
-
- Buttonstart=(Button)findViewById(R.id.start);
- Buttonstop=(Button)findViewById(R.id.stop);
-
- start.setOnClickListener(newOnClickListener()
- {
-
- @Override
- publicvoidonClick(Viewv)
- {
-
- createFloatView();
-
-
- }
- });
-
- stop.setOnClickListener(newOnClickListener()
- {
-
- @Override
- publicvoidonClick(Viewv)
- {
-
- if(mFloatLayout!=null)
- {
- mWindowManager.removeView(mFloatLayout);
- finish();
- }
- }
- });
-
-
- }
-
- privatevoidcreateFloatView()
- {
-
- wmParams=newWindowManager.LayoutParams();
-
-
- mWindowManager=this.getWindowManager();
- Log.i(TAG,"mWindowManager1--->"+this.getWindowManager());
-
- Log.i(TAG,"mWindowManager2--->"+getWindow().getWindowManager());
-
-
-
- Log.i(TAG,"mWindowManager3--->"+mWindowManager);
- wmParams.type=LayoutParams.TYPE_PHONE;
- wmParams.format=PixelFormat.RGBA_8888;;
- wmParams.flags=LayoutParams.FLAG_NOT_FOCUSABLE;
- wmParams.gravity=Gravity.LEFT|Gravity.TOP;
- wmParams.x=0;
- wmParams.y=0;
- wmParams.width=WindowManager.LayoutParams.WRAP_CONTENT;
- wmParams.height=WindowManager.LayoutParams.WRAP_CONTENT;
-
- LayoutInflaterinflater=this.getLayoutInflater();
-
- mFloatLayout=(LinearLayout)inflater.inflate(R.layout.float_layout,null);
- mWindowManager.addView(mFloatLayout,wmParams);
-
- mFloatView=(Button)mFloatLayout.findViewById(R.id.float_id);
-
- Log.i(TAG,"mFloatView"+mFloatView);
- Log.i(TAG,"mFloatView--parent-->"+mFloatView.getParent());
- Log.i(TAG,"mFloatView--parent--parent-->"+mFloatView.getParent().getParent());
-
- mFloatView.setOnTouchListener(newOnTouchListener()
- {
-
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent)
- {
-
- wmParams.x=(int)event.getRawX()-mFloatLayout.getWidth()/2;
-
- wmParams.y=(int)event.getRawY()-mFloatLayout.getHeight()/2-40;
- mWindowManager.updateViewLayout(mFloatLayout,wmParams);
- returnfalse;
- }
- });
-
-
- mFloatView.setOnClickListener(newOnClickListener()
- {
-
- @Override
- publicvoidonClick(Viewv)
- {
-
- Intentintent=newIntent(FloatWindowTest.this,ResultActivity.class);
- startActivity(intent);
- }
- });
-
- }
- }
将上面的代码相关注释部分取消,然后运行代码查看Log信息,那么就可以知道问题所在了(每一个Activity对应一个LocalWindowManger,每一个App对应一个CompatModeWrapper),所以要实现在App所在进程中运行的悬浮窗口,当然是得要获取CompatModeWrapper,而不是LocalWindowManger。
本文相关的完整代码下载链接:http://download.csdn.net/detail/stevenhu_223/4996970