android懸浮窗口的實現

當我們在手機上使用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


轉載:http://blog.csdn.net/stevenhu_223/article/details/8504058


發佈了34 篇原創文章 · 獲贊 6 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章