本文轉自 http://blog.csdn.net/stevenhu_223/article/details/8504058java
當咱們在手機上使用360安全衛士時,手機屏幕上時刻都會出現一個小浮動窗口,點擊該浮動窗口可跳轉到安全衛士的操做界面,並且該浮動窗口不受其餘activity的覆蓋影響仍然可見(多米音樂也有相關的和主界面交互的懸浮小窗口)。那麼這種不受Activity界面影響的懸浮窗口是怎麼實現的呢?android
居然它能懸浮在手機桌面,且不受Activity界面的影響,說明該懸浮窗口是不隸屬於Activity界面的,也就是說,他是隸屬於啓動它的應用程序所在進程。如360App所在的應用進程,當殺掉它所在的應用進程時,它纔會消失。數組
懸浮窗口的實現涉及到WindowManager(基於4.0源碼分析),它是一個接口,實現類有WindowManagerImpl,CompatModeWrapper(WindowManagerImpl的內部類),LocalWindowManager(Window的內部類),它們之間的關係以下圖的類圖:安全
WindowManagerImpl:app
1.是WindowManager的實現類,windowmanager的大部分操做都在這裏實現,可是並不會直接調用,而是做爲LocalWindowManager和WindowManagerImpl.CompatModeWrapper的成員變量來使用。ide
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函數初始化獲取的。post
因此,在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的代碼以下:
- public class MainActivity extends Activity
- {
-
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- Button start = (Button)findViewById(R.id.start_id);
-
- Button remove = (Button)findViewById(R.id.remove_id);
-
- start.setOnClickListener(new OnClickListener()
- {
-
- @Override
- public void onClick(View v)
- {
-
- Intent intent = new Intent(MainActivity.this, FxService.class);
-
- startService(intent);
- finish();
- }
- });
-
- remove.setOnClickListener(new OnClickListener()
- {
-
- @Override
- public void onClick(View v)
- {
-
- Intent intent = new Intent(MainActivity.this, FxService.class);
-
- stopService(intent);
- }
- });
-
- }
- }
FxService的代碼以下:
- package com.phicomm.hu;
-
- import android.app.Service;
- import android.content.Intent;
- import android.graphics.PixelFormat;
- import android.os.Handler;
- import android.os.IBinder;
- import android.util.Log;
- import android.view.Gravity;
- import android.view.LayoutInflater;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.WindowManager;
- import android.view.View.OnClickListener;
- import android.view.View.OnTouchListener;
- import android.view.WindowManager.LayoutParams;
- import android.widget.Button;
- import android.widget.LinearLayout;
- import android.widget.Toast;
-
- public class FxService extends Service
- {
-
-
- LinearLayout mFloatLayout;
- WindowManager.LayoutParams wmParams;
-
- WindowManager mWindowManager;
-
- Button mFloatView;
-
- private static final String TAG = "FxService";
-
- @Override
- public void onCreate()
- {
-
- super.onCreate();
- Log.i(TAG, "oncreat");
- createFloatView();
- }
-
- @Override
- public IBinder onBind(Intent intent)
- {
-
- return null;
- }
-
- private void createFloatView()
- {
- wmParams = new WindowManager.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;
-
-
-
-
-
- LayoutInflater inflater = 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(new OnTouchListener()
- {
-
- @Override
- public boolean onTouch(View v, MotionEvent event)
- {
-
-
- 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);
- return false;
- }
- });
-
- mFloatView.setOnClickListener(new OnClickListener()
- {
-
- @Override
- public void onClick(View v)
- {
-
- Toast.makeText(FxService.this, "onClick", Toast.LENGTH_SHORT).show();
- }
- });
- }
-
- @Override
- public void onDestroy()
- {
-
- 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,添加的視圖也會隨之消失。
驗證代碼以下:
- package com.phicomm.hu;
-
- import android.app.Activity;
- import android.content.Context;
- import android.content.Intent;
- import android.graphics.PixelFormat;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.Gravity;
- import android.view.LayoutInflater;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.WindowManager;
- import android.view.View.OnClickListener;
- import android.view.View.OnTouchListener;
- import android.view.WindowManager.LayoutParams;
- import android.widget.Button;
- import android.widget.LinearLayout;
-
- public class FloatWindowTest extends Activity
- {
-
-
- private static final String TAG = "FloatWindowTest";
- WindowManager mWindowManager;
- WindowManager.LayoutParams wmParams;
- LinearLayout mFloatLayout;
- Button mFloatView;
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.main);
-
- Button start = (Button)findViewById(R.id.start);
- Button stop = (Button)findViewById(R.id.stop);
-
- start.setOnClickListener(new OnClickListener()
- {
-
- @Override
- public void onClick(View v)
- {
-
- createFloatView();
-
-
- }
- });
-
- stop.setOnClickListener(new OnClickListener()
- {
-
- @Override
- public void onClick(View v)
- {
-
- if(mFloatLayout != null)
- {
- mWindowManager.removeView(mFloatLayout);
- finish();
- }
- }
- });
-
-
- }
-
- private void createFloatView()
- {
-
- wmParams = new WindowManager.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;
-
- LayoutInflater inflater = 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(new OnTouchListener()
- {
-
- @Override
- public boolean onTouch(View v, MotionEvent event)
- {
-
- wmParams.x = (int)event.getRawX() - mFloatLayout.getWidth()/2;
-
- wmParams.y = (int)event.getRawY() - mFloatLayout.getHeight()/2 - 40;
- mWindowManager.updateViewLayout(mFloatLayout, wmParams);
- return false;
- }
- });
-
-
- mFloatView.setOnClickListener(new OnClickListener()
- {
-
- @Override
- public void onClick(View v)
- {
-
- Intent intent = new Intent(FloatWindowTest.this, ResultActivity.class);
- startActivity(intent);
- }
- });
-
- }
- }
將上面的代碼相關注釋部分取消,而後運行代碼查看Log信息,那麼就能夠知道問題所在了(每個Activity對應一個LocalWindowManger,每個App對應一個CompatModeWrapper),因此要實如今App所在進程中運行的懸浮窗口,固然是得要獲取CompatModeWrapper,而不是LocalWindowManger。
本文相關的完整代碼下載連接:http://download.csdn.net/detail/stevenhu_223/4996970