Android悬浮窗口的实现

效果图:(悬浮框可拖动)

  在项目开发中有一个需求:弹出悬浮窗后,响应悬浮窗的事件再弹出对话框,但是对话框怎么也不显示。也就是说在弹出悬浮框的同时,不能再弹出对话框,可能的原因:

    1.悬浮框的焦点在最前面,把对话框挡住了,我们看不到。

    2.浮动框限制了对话框的弹出。

  解决:

    弹出对话框的时候把悬浮框关掉,然后对话框处理完了,把对话框关掉,在重新开启一个悬浮框,把需要的值传进去。

就相关知识详解:

  当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮窗口)。那么不受acitvity影响的悬浮窗口是怎么实现的呢?

  竟然它能悬浮在手机桌面,且不受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的代码如下:

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)   {  // TODO Auto-generated method stub  Intent intent = new Intent(MainActivity.this, FxService.class);  //启动FxService  
                startService(intent);  finish();  }  });  remove.setOnClickListener(new OnClickListener()   {  @Override  public void onClick(View v)   {  //uninstallApp("com.phicomm.hu");  Intent intent = new Intent(MainActivity.this, FxService.class);  //终止FxService  
                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()   {  // TODO Auto-generated method stub  super.onCreate();  Log.i(TAG, "oncreat");  createFloatView();        }  @Override  public IBinder onBind(Intent intent)  {  // TODO Auto-generated method stub  return null;  }  private void createFloatView()  {  wmParams = new WindowManager.LayoutParams();  //获取的是WindowManagerImpl.CompatModeWrapper  mWindowManager = (WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);  Log.i(TAG, "mWindowManager--->" + mWindowManager);  //设置window type  wmParams.type = LayoutParams.TYPE_PHONE;   //设置图片格式,效果为背景透明  wmParams.format = PixelFormat.RGBA_8888;   //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)  wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;        //调整悬浮窗显示的停靠位置为左侧置顶  wmParams.gravity = Gravity.LEFT | Gravity.TOP;         // 以屏幕左上角为原点,设置x、y初始值,相对于gravity  wmParams.x = 0;  wmParams.y = 0;  //设置悬浮窗口长宽数据    wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;  wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;  /*// 设置悬浮窗口长宽数据 wmParams.width = 200; wmParams.height = 80;*/  LayoutInflater inflater = LayoutInflater.from(getApplication());  //获取浮动窗口视图所在布局  mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);  //添加mFloatLayout  
        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)   {  // TODO Auto-generated method stub  //getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标  wmParams.x = (int) event.getRawX() - mFloatView.getMeasuredWidth()/2;  Log.i(TAG, "RawX" + event.getRawX());  Log.i(TAG, "X" + event.getX());  //减25为状态栏的高度  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;  //此处必须返回false,否则OnClickListener获取不到监听  
            }  });   mFloatView.setOnClickListener(new OnClickListener()   {  @Override  public void onClick(View v)   {  // TODO Auto-generated method stub  Toast.makeText(FxService.this, "onClick", Toast.LENGTH_SHORT).show();  }  });  }  @Override  public void onDestroy()   {  // TODO Auto-generated method stub  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   
{  /** Called when the activity is first created. */  private static final String TAG = "FloatWindowTest";  WindowManager mWindowManager;  WindowManager.LayoutParams wmParams;  LinearLayout mFloatLayout;  Button mFloatView;  @Override  public void onCreate(Bundle savedInstanceState)   {  super.onCreate(savedInstanceState);  //createFloatView();  
        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)  {  // TODO Auto-generated method stub  
                createFloatView();  //finish();  //handle.post(r);  
            }  });  stop.setOnClickListener(new OnClickListener()  {  @Override  public void onClick(View v)   {  // TODO Auto-generated method stub  if(mFloatLayout != null)  {  mWindowManager.removeView(mFloatLayout);  finish();  }     }  });  }  private void createFloatView()  {  //获取LayoutParams对象  wmParams = new WindowManager.LayoutParams();  //获取的是LocalWindowManager对象  mWindowManager = this.getWindowManager();  Log.i(TAG, "mWindowManager1--->" + this.getWindowManager());  //mWindowManager = getWindow().getWindowManager();  Log.i(TAG, "mWindowManager2--->" + getWindow().getWindowManager());  //获取的是CompatModeWrapper对象  //mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);  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();//LayoutInflater.from(getApplication());  
          mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);  mWindowManager.addView(mFloatLayout, wmParams);  //setContentView(R.layout.main);  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)   {  // TODO Auto-generated method stub  wmParams.x = (int)event.getRawX() - mFloatLayout.getWidth()/2;  //25为状态栏高度  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)   {  // TODO Auto-generated method stub  Intent intent = new Intent(FloatWindowTest.this, ResultActivity.class);  startActivity(intent);  }  });  }  
}  

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

本文相关的完整代码下载链接:

http://pan.baidu.com/s/1sjHsWJ7 提取码:xt4u

 

转载于:https://www.cnblogs.com/Joanna-Yan/p/4758365.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/290580.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android之invalid address or address of corrupt block 0xabb494a0 passed to dlfree崩溃解决办法

1、问题 手机崩溃 invalid address or address of corrupt block 0xabb494a0 passed to dlfree Fatal signal 11 (SIGSEGV), code 1, fault addr 0xdeadbaad in tid 29629 (AsyncTask #1) #00 pc 000308a6 /system/lib/libc.so (dlfree1285) 2、原因 const char* 转char *的…

软件配置管理(三)软件配置管理核心功能

文章目录一、CMM/CMMI与软件配置管理产品完整性二、三库管理三、基线管理基线管理的好处基线管理的步骤四、配置库管理五、变更管理变更管理流程六、配置审计七、配置状态报告一、CMM/CMMI与软件配置管理 软件配置管理是CMM/CMMI二级&#xff08;可重复级&#xff09;的一个重…

移除指定 global using 命名空间

前言在《.NET 6新特性试用 | 隐式using指令》中&#xff0c;我们介绍过&#xff0c;开启隐式using指令时&#xff0c;会在编译时生成 GlobalUsings.g.cs 文件&#xff0c;加入大量 global using 指令&#xff1a;// <auto-generated/> global using global::Microsoft.As…

Ubuntu 每日技巧- 自动备份Ubuntu 14.04到Box云存储上

Ubuntu 每日技巧- 自动备份Ubuntu 14.04到Box云存储上 如今你已经升级或者安装了Ubuntu 14.04&#xff0c; 但是还有另外一件保护你的新系统需要做的事情&#xff1a;备份&#xff01; Ubuntu内置了一个备份工具 Dj Dup Backup Tool。它允许你备份你的系统并保存在本地或者通过…

程序员学好英语的方法(转)

英语对每个人来说都很重要&#xff0c;对于程序员来说尤其的重要&#xff0c;因为一些框架都是外国大佬写的&#xff0c;我们要时时的阅读 API,而这些 API 大多数都是英文了&#xff0c;所以我们程序员对英语的要求还是蛮高的。下面我整理的一些学习英语的方法&#xff0c;分享…

软件配置管理(四)代码味道与重构

文章目录重构的概念及意义代码味道代码味道分类1.类内味道1.1 可度量的味道-Measured Smells1.1.1 过长函数-Long Method1.1.2 过大类-Large Class1.1.3 过长参数列-Long Parameter List1.1.4 过多的注释-Comments1.2 不必要的复杂性-Unnecessary Complexity1.2.1 夸夸其谈的未…

Atitit。 《吠陀》 《梨俱吠陀》overview 经读后感  是印度上古时期一些文献的总称...

Atitit。 《吠陀》 《梨俱吠陀》overview 经读后感 是印度上古时期一些文献的总称 1. 印度古《吠陀》经&#xff0c;是印度上古时期一些文献的总称&#xff0c; 1 1.1.1. 医学意义 2 2. 梨俱吠陀&#xff08;篇章规模&#xff0c;字数&#xff09; 2 2.1. 神曲结构模式编辑 2 …

【C#/.NET 日常开发技巧】JWT+ActionFilter 简便控制器代码

微信公众号&#xff1a;趣编程ACE关注可了解更多.NET日常开发技巧&#xff0c;如需源码&#xff0c;请公众号留言 源码;JWTActionFilter 简便控制器代码这是微软关于过滤器的介绍&#xff1a;https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters?viewaspne…

在P2P市场中代替“看不见的手”的算法在哪里

◆ ◆ ◆ 本文简单探讨P2P市场机制&#xff0c;在此基础上探讨匹配撮合服务的可能性。 ◆ ◆ ◆ P2P市场机制基本定义与假设 首先&#xff0c;我们回顾一些基本定义与假设。P2P市场为“网络借贷是指个体和个体之间通过互联网平台实现的直接借贷。”[2]利率决定一般是平台决定借…

Android之adb jdwp获取debug版本app的进程Id

1、adb jdwp命令 adb jdwp获取debug版本app的进程Id 2、用途 带我们运行一个debug版本的app时候&#xff0c;我们需要过滤日志&#xff0c;我们一般采取这种办法 adb shell ps | grep package 得到进程ID,然后 adb logcat | grep pid 打印日志&#xff0c;有了adb jdwp&…

NPOI格式设置1

using NPOI.SS.UserModel; using NPOI.HSSF.UserModel; //创建Execl IWorkbook hssfworkbook new HSSFWorkbook(); //创建一个Sheet hssfworkbook.CreateSheet("Sheet1"); //HSSFWorkbook实例写入文件 FileStream file new FileStream("test.xls", F…

软件配置管理(五)常用重构技巧

文章目录一、重新组织函数1.提炼函数2.内联函数3.内联临时变量4.以查询取代临时变量5.引入解释性变量6.分解临时变量7.移除对参数的赋值8.以函数对象取代函数9.替换算法二、在对象之间搬移特性1.搬移函数2.搬移字段3.提炼类4.将类内联化5.隐藏“委托关系”6.移除中间人7.引入外…

关于他们回答的 怎样在桌面建一个python GUI的快捷方式 这个问题

在之前的2个随笔里面&#xff0c;有写过《找到可以解决问题的正确的人》、《如何提问》&#xff0c;说白了就是您需要帮助的时候&#xff0c;您得让对方100%懂你&#xff0c;否则没戏。 那么最近看到这样1个古老的问题&#xff0c;和一些没有答到"点儿"上的回答&…

Android之最简单和靠谱的监听Home键和菜单键(最近任务栏)

1、介绍ACTION_CLOSE_SYSTEM_DIALOGS /*** Broadcast Action: This is broadcast when a user action should request a* temporary system dialog to dismiss. Some examples of temporary system* dialogs are the notification window-shade and the recent tasks dialog.*…

.NET Core中行为过滤器ActionFilterAttribute的使用介绍

什么是行为过滤器&#xff1f;行为过滤器是你可以应用到一个控制器行为的&#xff0c;或者整个控制器的&#xff0c;来修改控制器行为的执行方式的属性。当请求进入 API 接口的时候&#xff0c;操作过滤器提供了一个进入之前&#xff08;before&#xff09;和进入之后&#xff…

基本线程同步(三)在同步的类里安排独立属性

声明&#xff1a;本文是《 Java 7 Concurrency Cookbook 》的第二章&#xff0c;作者&#xff1a; Javier Fernndez Gonzlez 译者&#xff1a;许巧辉 校对&#xff1a;方腾飞 在同步的类里安排独立属性 当你使用synchronized关键字来保护代码块时&#xff0c;你必须通过一…

旧项目适配iphone6和iphone plus

iphone手机屏幕大小和像素&#xff1a;1.iPhone5/5s 320x568&#xff0c;像素640x1136&#xff0c;2x2.iPhone6 375x667&#xff0c;像素750x1334&#xff0c;2x3.iPhone6 Plus 414x736&#xff0c;像素1242x2208&#xff0c;3x旧的项目在xcode6上运行在iphone6或…

java之用反射实现方法(已知实体对象和实体参数)

1、问题 有个函数需要在Android 23&#xff08;6.0&#xff09;以上&#xff0c;但是我们的API是22,所以这个实体对象调用不了这个函数&#xff0c;这个时候我们应该想到的是反射&#xff0c;切记。 2、实现 同时看我写得很着急&#xff0c;因为我是先class.forName("**…

Could not load the Tomcat server configuration at \Servers\Tomcat v7.0 Server at localhost-config

之前不小心删除了server的服务器设置&#xff0c;运行时各种报错Could not load the Tomcat server configuration at \Servers\Tomcat v7.0 Server at localhost-config &#xff0c;网上找到解决方法记录下&#xff1a; 第一步&#xff1a;将左边的classpath中的server删除掉…

软件项目组织管理(一)项目管理概述

文章目录什么是项目项目的特征项目的组成要素&#xff08;三维约束&#xff09;什么是项目管理什么是IT项目什么是软件项目管理项目管理的目标&#xff08;项目成功的标志&#xff09;软件项目失败的原因活动的3个基本特点人类活动分为两种类型作业和项目的区别&#xff08;必考…