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的代码如下:

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://download.csdn.net/detail/stevenhu_223/4996970

     

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

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

相关文章

ChatGPT提示词工程(六):Expanding扩展

目录 一、说明二、安装环境三、扩展&#xff08;Expanding&#xff09;1. 自定义自动回复客户电子邮件2. 提醒模型使用客户电子邮件中的详细信息3. 参数 temperature 一、说明 这是吴恩达 《ChatGPT Prompt Engineering for Developers》 的课程笔记系列。 本文是第七讲的内容…

通达信自动包络线指标公式以及ATR通道指标

根据亚历山大埃尔德在其著作《以交易为生》中的描述&#xff0c;自动包络线的设计思路是将通道看作试穿衬衫一样&#xff0c;寻找那些穿起来既不过松也不过紧的衬衫&#xff0c;只让手腕和脖子露在外面。自动包络线能够适应最近的行情波动&#xff0c;只有在极端情况下&#xf…

微信支付费率0.38还是0.6,0.2费率怎么开,3分钟申请教程

目前微信支付官方给到商家的费率统一为0.6%&#xff0c;部分线下实体店商家由服务商推广开户一般是用的0.38%的费率。 其实很多商户都不知道&#xff0c;其实还可以开通更低的费率&#xff0c;0.2&#xff5e;0.35%的费率。 现在就分享一个如何在几分钟申请提交开通0.2费率的…

微信支付申请费率0.2%的方法,百分百通过不求人

微信支付通用的费率都是0.6%,那么如何申请0.2%呢。方法很简单。

openai.error.RateLimitError: You exceeded your current quota, please check your plan and billing det

报错&#xff1a; 此错误消息表明您已达到API的最大月支出&#xff08;硬限制&#xff09;。这意味着您已经消耗了分配给计划的所有积分或单位&#xff0c;并且已经达到计费周期的限制。这种情况可能有几个原因&#xff0c;例如&#xff1a; 您使用的是高容量或复杂的服务&…

关于0.2%费率的微信支付,你需要了解一下。

经营流水大&#xff0c;支付手续费高&#xff0c;想要减少手续费&#xff1f;不了解微信支付申请流程&#xff0c;步骤太多太繁琐&#xff0c;想要快捷开户&#xff1f;不用担心&#xff0c;这些都可以用微信支付特约商户解决&#xff01; 为助力商家享受到更快捷的微信商户申…

定时任务原理方案综述

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 导读 本文主要介绍目前存在的定时任务处理解决方案。业务系统中存在众多的任务需要定时或定期执行&#xff0c;并且针对不同的系…

巴比特 | 元宇宙每日必读:AI搜索大战打响!微软官宣ChatGPT引入Bing,“文心一言”传首站将接入百度搜索,接下来是谁?...

摘要&#xff1a;据财联社报道&#xff0c;北京时间8日凌晨&#xff0c;微软宣布推出由ChatGPT支持的最新版Bing搜索引擎和Edge浏览器&#xff0c;新版Bing于今日上线&#xff0c;不过预览人数有限。微软计划近期推出移动版本&#xff0c;预览人数有望扩展至数百万人。那么&…

Docker 十周年 | 历史上的今天

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 3 月 20 日&#xff0c;在 1999 年的今天&#xff0c;人类首次成功乘热气球环球飞行。在 24 年的今天&#xff0c;瑞士人皮尔卡、英国人琼斯经过近 20 天的飞行…

macOS 下载汇总(系统、应用和教程) 2023 持续更新中

macOS Ventura 13, macOS Monterey 12, macOS Big Sur 11, macOS Catalina 10.15, macOS Mojave 10.14, macOS High Sierra 10.13, macOS Sierra 10.12 请访问原文链接&#xff1a;https://sysin.org/blog/macOS/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。…

网景浏览器正式发布 | 历史上的今天

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2022 年 12 月 15 日&#xff0c;在 56 年前的今天&#xff0c;美国动画制作家迪士尼逝世。他创造的米老鼠形象曾经给世界上的每一个孩子带来了欢乐&#xff1b;他是一位…

马化腾微信转发文章称要“收紧队形”,腾讯公关回应;微软宣布终止支持 Cortana;TypeScript 5.1发布|极客头条...

「极客头条」—— 技术人员的新闻圈&#xff01; CSDN 的读者朋友们早上好哇&#xff0c;「极客头条」来啦&#xff0c;快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 一分钟速览新闻点&#…

苹果电脑惊现比特币白皮书,乔布斯会是中本聪吗?

原创&#xff1a;刘教链 * * * 隔夜比特币继续在28k附近横盘&#xff0c;静静等待30日线向上靠近。 市场总在百无聊赖之际找些乐子。昨天&#xff0c;圈子里热议最多的话题就是在苹果电脑系统中发现了比特币的白皮书[1]。 话题的源头是4月5号一个叫Andy Baio的网友在waxy.org发…

本地推理,单机运行,MacM1芯片系统基于大语言模型C++版本LLaMA部署“本地版”的ChatGPT

OpenAI公司基于GPT模型的ChatGPT风光无两&#xff0c;眼看它起朱楼&#xff0c;眼看它宴宾客&#xff0c;FaceBook终于坐不住了&#xff0c;发布了同样基于LLM的人工智能大语言模型LLaMA&#xff0c;号称包含70亿、130亿、330亿和650亿这4种参数规模的模型&#xff0c;参数是指…

安装Windows和MacOS双系统 (UEFI + GPT) 和常用软件及配置,一篇文章解决所有

调节显示器自带扬声器音量用Loopback 镜像下载地址:https://mirrors.dtops.cc/iso/MacOS/daliansky_macos/ 黑苹果AppleALC声卡驱动: 通过windows系统查看,右键我的电脑——管理——设备管理器——右键设备属性——详细信息——属性中选择硬件id,查看第一行“值”,注意dev后…

Win11系统更新后网络速度变的很慢怎么办?

Win11系统更新后网络速度变的很慢怎么办&#xff1f;有用户将自己的电脑系统升级到了Win11之后&#xff0c;出现了一些问题。电脑在使用中出现了网络速度变慢的情况。而且其它的设备在连接网络后速度是正常的&#xff0c;那么这个问题要怎么解决&#xff1f;来看看以下的方法分…

为什么微软的网站访问速度都这么慢?

微软这家企业大家还是挺熟悉的&#xff0c;微软自己研发的Windows系统很多人都在使用&#xff0c;微软除了操作系统还自己研发了浏览器&#xff0c;但是国内的小伙伴在使用微软网站的时候访问速度都比较慢&#xff0c;这是什么原因呢&#xff1f; 微软的的有些服务器在国外有些…

实现简单登入注册功能(php表单)

1.环境部署&#xff08;数据库的建立&#xff09; 2.代码展示 <meta charset"utf-8" /> <form action"bd.php" method"GET"><center>账号&#xff1a;<input typetext name"uname"/><br />密码&#x…

帮管客CRM客户管理系统V2.5.4OA系统PHP源码,已去除域名授权无限制版

源码介绍 帮管客CRM客户管理系统基于先进的CRM营销理念设计&#xff0c;集客户档案、销售记录、业务往来于一身&#xff0c;以凝聚客户关系、提升资源价值为核心&#xff0c;将潜在客户变为现实客户、从而提升销售量、提高用户的满意度&#xff0c;并增加企业竞争力。帮管客CR…