Android全局悬浮球源码解析:Service+WindowManager实现任意界面悬浮窗

发布时间:2026/10/19 7:30:24

Android全局悬浮球源码解析:Service+WindowManager实现任意界面悬浮窗 简介这是一份面向Android开发者的悬浮球功能实现源码聚焦于通过一个后台服务让悬浮按钮在任意应用界面之上显示并交互适合需要掌握悬浮窗、WindowManager与系统级权限处理的中级开发者参考。压缩包共35个文件约2.02MB以xml布局与配置、java服务与Activity代码、gradle构建脚本为主另含png截图、properties与jar等辅助文件结构完整可直接导入Android Studio运行。资源围绕FloatingViewService展开涵盖SYSTEM_ALERT_WINDOW权限声明与Android 6.0以上动态申请、TYPE_PHONE窗口参数设置、addView添加视图、触摸与点击事件绑定、START_STICKY保活及横竖屏切换重布局等关键环节并附有README说明与运行截图便于对照理解服务生命周期与窗口管理机制。目前已有2420人学习下载适合希望快速搭建悬浮球原型、排查权限与窗口层级问题的开发者借鉴。1. 一个服务搞定全局悬浮球为什么我建议你从这份源码开始拆做 Android 开发绕不开一个需求悬浮球。产品经理一句“我要在任何界面都能拖动的悬浮球”背后是 WindowManager、Service 生命周期、权限适配、触摸事件分发、内存泄漏一堆坑。很多教程只给你一个 Activity 里加个 View 的 Demo切到桌面就没了。这份源码解决的就是这个问题——用一个 Service开启悬浮球任何界面都能用包括桌面、其他 App 之上。它适合两类人一是刚接触 Android 系统级 UI 的开发者想搞懂 WindowManager 到底怎么用二是手上有类似需求、需要一个能跑通的完整参考实现的人。关键词“Android开发-其它控件”里悬浮球属于典型的自定义控件 系统窗口组合这份代码把两者串起来了。2. 项目定位与技术选型为什么是 Service WindowManager2.1 悬浮球本质是一个系统级窗口普通 View 活在 Activity 的窗口里Activity 一销毁View 就没了。悬浮球要“任何界面都可以使用”意味着它的宿主不能是 Activity必须是更底层的系统窗口。Android 里能往系统窗口层塞 View 的入口就是WindowManager而WindowManager需要在一个不受界面切换影响的生命周期里持有——Service正好满足。这份源码的核心思路很直接Service 启动时通过WindowManager.addView()把悬浮球 View 挂到TYPE_APPLICATION_OVERLAY类型的窗口上Service 销毁时removeView()。整个项目没有引入第三方库纯原生 API依赖只有 Android SDK 本身。常见做法是配合LifecycleService或手动管理但这份代码用的是最朴素的ServiceHandler对新手更友好读起来没有额外框架的干扰。技术栈可以概括为层级用到的技术作用窗口层WindowManager TYPE_APPLICATION_OVERLAY把 View 挂到系统窗口服务层Service onStartCommand管理悬浮球生命周期触摸层OnTouchListener MotionEvent拖动与点击判定权限层SYSTEM_ALERT_WINDOW悬浮窗权限申请UI 层自定义 View / ImageView悬浮球外观与交互2.2 为什么不用 Activity 或 AccessibilityService有人会问用AccessibilityService不是也能做悬浮球吗可以但那是杀鸡用牛刀。无障碍服务适合做自动化操作悬浮球只是它的副产物而且用户开启无障碍的意愿远低于悬浮窗权限。用 Activity 做宿主更不行切后台就挂。所以 Service WindowManager 是这个场景下最合理的组合这份源码选型没有跑偏。2.3 目录结构长什么样拿到源码后先看目录心里有个地图app/src/main/java/com/example/floatball/ ├── FloatBallService.java // 核心服务管理窗口添加/移除 ├── FloatBallView.java // 悬浮球自定义 View处理触摸 ├── MainActivity.java // 入口申请权限、启动服务 └── utils/ └── PermissionUtils.java // 悬浮窗权限检查与跳转 app/src/main/res/ ├── layout/activity_main.xml ├── drawable/ball_bg.xml // 悬浮球背景 └── values/colors.xml AndroidManifest.xml // 注册 Service 与权限这个结构很干净核心逻辑集中在FloatBallService和FloatBallView两个文件里。新手先读MainActivity看怎么启动再读FloatBallService看窗口怎么加最后读FloatBallView看触摸怎么处理。3. 核心模块拆解从权限申请到窗口挂载的完整链路3.1 权限申请没有它后面全白搭SYSTEM_ALERT_WINDOW是特殊权限不能只写在 Manifest 里就完事必须引导用户去设置页手动开启。源码里PermissionUtils的做法是// PermissionUtils.java public static boolean canDrawOverlays(Context context) { if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { // Android 6.0 及以上需要动态检查 return Settings.canDrawOverlays(context); } return true; // 6.0 以下默认有权限 } public static void requestOverlayPermission(Activity activity, int requestCode) { if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { Intent intent new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(package: activity.getPackageName())); activity.startActivityForResult(intent, requestCode); } }逻辑说明canDrawOverlays在 6.0 以上用Settings.canDrawOverlays判断6.0 以下直接返回 true。requestOverlayPermission跳转到当前应用的悬浮窗权限设置页用package:URI 限定到本应用避免用户在其他应用列表里迷路。参数requestCode用于在onActivityResult里区分回调来源。注意部分国产 ROM 即使canDrawOverlays返回 true实际添加窗口仍可能失败所以addView一定要包 try-catch。3.2 Service 启动与窗口添加FloatBallService的onCreate里做两件事创建悬浮球 View拿到 WindowManager。onStartCommand里执行添加。核心代码// FloatBallService.java Override public void onCreate() { super.onCreate(); windowManager (WindowManager) getSystemService(WINDOW_SERVICE); floatBallView new FloatBallView(this); initLayoutParams(); } private void initLayoutParams() { layoutParams new WindowManager.LayoutParams(); if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { // Android 8.0 起必须用这个类型 layoutParams.type WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { layoutParams.type WindowManager.LayoutParams.TYPE_PHONE; } layoutParams.format PixelFormat.RGBA_8888; // 支持透明 layoutParams.flags WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; layoutParams.gravity Gravity.TOP | Gravity.START; layoutParams.width WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.height WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.x 100; layoutParams.y 300; } Override public int onStartCommand(Intent intent, int flags, int startId) { if (!floatBallView.isAttachedToWindow()) { try { windowManager.addView(floatBallView, layoutParams); } catch (Exception e) { // 权限被回收或 ROM 限制时会走到这里 stopSelf(); } } return START_STICKY; // 被杀后尝试重启 }逻辑说明initLayoutParams里最关键的是type8.0 以上必须用TYPE_APPLICATION_OVERLAY否则直接崩。FLAG_NOT_FOCUSABLE让悬浮球不抢焦点不影响下层应用输入FLAG_LAYOUT_NO_LIMITS允许拖出屏幕边界。onStartCommand里先判断isAttachedToWindow防止重复添加try-catch 兜底 ROM 限制。返回START_STICKY让服务被系统杀掉后有机会重建。参数怎么改layoutParams.x和y是初始位置想让它默认靠右就把x设大一点或者用gravity TOP | END。width/height用WRAP_CONTENT让 View 自己决定大小如果你固定了悬浮球尺寸也可以写死 dp 值。3.3 触摸处理拖动与点击的判定悬浮球要能拖还要能点。难点在于区分“用户是想拖还是想点”。源码在FloatBallView里用了一个阈值判断// FloatBallView.java private float downX, downY; private int lastX, lastY; private static final int CLICK_THRESHOLD 10; // dp Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX event.getRawX(); downY event.getRawY(); lastX layoutParams.x; lastY layoutParams.y; return true; case MotionEvent.ACTION_MOVE: int dx (int) (event.getRawX() - downX); int dy (int) (event.getRawY() - downY); layoutParams.x lastX dx; layoutParams.y lastY dy; windowManager.updateViewLayout(this, layoutParams); return true; case MotionEvent.ACTION_UP: float moveX Math.abs(event.getRawX() - downX); float moveY Math.abs(event.getRawY() - downY); if (moveX CLICK_THRESHOLD moveY CLICK_THRESHOLD) { performClick(); // 触发点击 } return true; } return super.onTouchEvent(event); }逻辑说明ACTION_DOWN记录按下时的原始坐标和窗口当前位置。ACTION_MOVE计算偏移量更新layoutParams.x/y后调用updateViewLayout实时移动。ACTION_UP时判断移动距离是否小于阈值小于就认为是点击。CLICK_THRESHOLD用 dp 转 px 更准确源码里简化成了固定值二次开发时建议改成dp2px(10)。参数说明event.getRawX()是相对屏幕的坐标getX()是相对父 View 的这里必须用getRawX。lastX/lastY保存的是上一次窗口位置不是按下位置这样连续拖动不会跳。3.4 服务销毁与资源释放悬浮球最怕内存泄漏和窗口残留。源码在onDestroy里做了清理Override public void onDestroy() { super.onDestroy(); if (floatBallView ! null floatBallView.isAttachedToWindow()) { windowManager.removeView(floatBallView); } floatBallView null; windowManager null; }逻辑说明先判断 View 是否还挂在窗口上再移除避免IllegalArgumentException。置空引用帮助 GC。常见做法还会在这里注销广播、停止动画这份源码因为功能单一清理逻辑很轻。4. 跑起来从导入到看到悬浮球的每一步4.1 环境准备与导入这份源码是标准 Android 工程用 Android Studio 直接Open项目根目录即可。Gradle 版本和 AGP 版本在build.gradle里如果提示同步失败优先检查本地 SDK 是否装了对应 API Level。常见做法是让 Studio 自动下载缺失组件不要手动改版本号除非你明确知道自己在做什么。导入后先看AndroidManifest.xml确认两处uses-permission android:nameandroid.permission.SYSTEM_ALERT_WINDOW / service android:name.FloatBallService /权限和 Service 注册缺一不可。有些 ROM 还要求FOREGROUND_SERVICE但这份源码没用到前台服务所以不加也能跑只是后台存活时间短。4.2 启动流程与权限回调MainActivity里通常是一个按钮点击后先检查权限没有就申请有就启动 Service// MainActivity.java btnStart.setOnClickListener(v - { if (PermissionUtils.canDrawOverlays(this)) { startService(new Intent(this, FloatBallService.class)); finish(); // 启动后退出验证是否真的全局 } else { PermissionUtils.requestOverlayPermission(this, REQUEST_CODE); } }); Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode REQUEST_CODE) { if (PermissionUtils.canDrawOverlays(this)) { startService(new Intent(this, FloatBallService.class)); finish(); } } }逻辑说明finish()是故意加的用来验证悬浮球是否真的独立于 Activity。如果退出后悬浮球还在说明窗口挂载成功如果消失说明代码有问题。onActivityResult里重新检查权限因为用户可能授权也可能拒绝。4.3 验证悬浮球是否全局生效跑起来后按这个顺序验证点击按钮授权看到悬浮球出现在屏幕上。按 Home 键回到桌面悬浮球应该还在。打开另一个 App悬浮球应该覆盖在它上面。拖动悬浮球松手后位置保持。点击悬浮球触发点击事件源码里可能是 Toast 或日志。如果第 2 步就失败说明窗口类型不对或权限没真正生效。如果第 3 步失败检查是否被其他应用的悬浮窗遮挡或者 ROM 有白名单限制。4.4 停止服务与清理源码里没有提供停止入口二次开发时建议加一个通知栏按钮或悬浮球长按菜单调用stopService(new Intent(this, FloatBallService.class))。停止后onDestroy会移除窗口悬浮球消失。测试时可以用adb shell am stopservice强制停止观察窗口是否正确清理。5. 避坑与常见问题那些让我加班到凌晨的细节5.1 悬浮球在部分机型上不显示现象代码在模拟器上正常真机上授权后悬浮球不出现。原因国产 ROM 对TYPE_APPLICATION_OVERLAY有额外限制或者canDrawOverlays返回 true 但实际未授权。解决在addView外层加 try-catch捕获WindowManager.BadTokenException和SecurityException失败时引导用户去系统设置里手动开启“显示在其他应用上层”。5.2 拖动时悬浮球闪烁或跳动现象手指移动时悬浮球位置跳变。原因ACTION_MOVE里用了event.getX()而不是getRawX()或者lastX/lastY在每次 MOVE 时被重置。解决统一用getRawX/getRawYlastX/lastY只在ACTION_DOWN时赋值MOVE 时基于按下时的窗口位置加偏移。5.3 服务被杀后悬浮球消失现象锁屏或内存紧张后悬浮球没了。原因普通 Service 在后台容易被系统回收。解决改成前台服务startForeground加一个低优先级通知。注意 Android 8.0 以上前台服务也要用startForegroundService启动并在 5 秒内调用startForeground。5.4 点击事件不触发现象悬浮球能拖但点不动。原因onTouchEvent返回了 false或者阈值判断把点击当成了拖动。解决确保ACTION_DOWN返回 trueACTION_UP里用Math.abs计算移动距离阈值建议 10dp 左右。如果悬浮球内部还有子 View注意事件分发子 View 可能消费了触摸事件。5.5 退出应用后悬浮球还在但点击无响应现象Activity 销毁后悬浮球还在但点击没反应。原因点击回调里引用了 Activity 的 Context 或匿名内部类导致内存泄漏或空指针。解决回调里用getApplicationContext()或者通过广播/接口把事件传出去不要在 Service 里直接持有 Activity 引用。6. 二次开发把悬浮球改成你想要的样子6.1 换外观从圆形到任意布局源码里的悬浮球是一个简单 View你可以直接换成ImageView加自定义 drawable或者用LayoutInflater加载一个复杂布局。关键点WindowManager.LayoutParams的width/height要跟着改如果用WRAP_CONTENT就不用动。常见做法是把悬浮球做成可展开的菜单点击后弹出几个小按钮这时候窗口尺寸要动态更新调用updateViewLayout重新设置宽高。6.2 加交互长按、双击、贴边吸附长按和双击可以在onTouchEvent里用GestureDetector处理比手动判断更稳。贴边吸附的逻辑是ACTION_UP时判断当前layoutParams.x离屏幕左边近还是右边近然后用属性动画或ValueAnimator平滑移动过去。注意屏幕宽度用getResources().getDisplayMetrics().widthPixels获取不要写死。6.3 数据通信悬浮球与 Activity 怎么传值Service 和 Activity 通信常见三种方式广播、Binder、LiveData。这份源码没涉及但二次开发大概率要加。推荐用LocalBroadcastManager或LiveData前者简单后者生命周期安全。如果悬浮球要控制 Activity 里的播放器可以定义一个接口Activity 绑定 Service 时把接口传进去。6.4 一个我踩过的坑窗口类型与 targetSdkVersion有次我把targetSdkVersion升到 30悬浮球突然不显示了。排查半天发现是TYPE_PHONE在 8.0 以上被废弃而我的代码里判断条件写反了。从那以后我每次改targetSdkVersion都强制走一遍悬浮球显示验证确认TYPE_APPLICATION_OVERLAY分支生效。这个习惯帮我省了不少返工时间。6.5 验证改造是否成功的最小清单改完代码后按这个清单跑一遍验证项预期结果失败时先看授权后启动悬浮球出现addView 是否抛异常按 Home悬浮球仍在窗口类型是否正确打开其他 App悬浮球覆盖权限是否被 ROM 限制拖动跟手不跳是否用 getRawX点击触发回调阈值与返回值停止服务悬浮球消失onDestroy 是否 removeView这份源码最值钱的地方不是代码多复杂而是它把“一个服务开启全局悬浮球”这条链路完整跑通了没有多余封装每一行都能对应到一个具体问题。你拿到后先跑通再按上面的思路改外观、加交互、补通信基本就能覆盖大部分悬浮球需求。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/19 7:25:24

no_DS.zip 拆包实战:轻量级工程骨架的配置与避坑指南

简介:这是一份面向Windows平台开发者与脚本初学者的轻量级压缩包,围绕批处理与JavaScript的协同调用展开,可用于快速搭建本地脚本启动环境或作为自动化任务的最小示例。包内共2个文件,包含1个bat批处理脚本与1个js脚本&#xff0c…

2026/10/19 7:25:24

供应链系统HTML静态原型:快速搭建与二次开发指南

简介:这份供应链系统页面原型资源面向产品经理、UI设计师、前端开发者及供应链业务人员,用于在开发前期快速理解系统界面布局与交互流程,降低沟通成本。压缩包共299个文件,约661KB,以87个html页面为主体,辅…

2026/10/19 7:25:24

EQTransformer地震相位拾取原理与工程实践

简介:EQTransformer是一个面向地球物理与人工智能交叉领域的开源Python软件包,专为地震信号自动检测与P/S波相位拾取任务设计,适用于地震学研究者、地质工程技术人员及具备深度学习基础的科研开发者。资源共92个文件,包含20个核心…

2026/10/19 8:50:45

Arthas实战手册:Java线上诊断、性能分析与故障排查

1. Arthas 是什么?为什么线上出了事,大家第一个想到它多年前我第一次遇到线上 Java 服务 CPU 飙到 99% 却没有监控权限,运行日志里只有几行无关痛痒的 WARN,当时那种“既不能重启、又不能断点调试、代码也不敢乱动”的无助感&…

2026/10/19 8:50:45

基于YOLOv11的智能安防监控系统搭建实战

就从标题聊起吧。用YOLOv11搭一套安防监控系统,这事儿听起来像是个大工程,但拆开来看,核心就三件事:把视频流里的人准确认出来、判断他有没有异常行为、然后在出事的时候及时告警。这套东西落地之后,可以用在园区周界、…

2026/10/19 8:50:45

YOLOv5/8/10远程监控动物侵扰检测实战:完整复盘与部署踩坑

做“远程监控动物侵扰检测”这个毕设项目时,我踩过的坑比想象中多得多。这个题目听起来就是“用YOLOv5、YOLOv8、YOLOv10训练一个模型,识别监控画面里出现的动物并告警”,但真正动手做才发现,从数据集标注、模型选型、训练调参&am…

2026/10/19 8:50:45

Arch Linux 从 stable 切换到 LTS 内核完整指南与踩坑实录

用过 Arch 一段时间的人,迟早会遇到一次这样的时刻:pacman -Syu之后系统提示更新了内核,重启后 Wi-Fi 或显卡驱动开始闹脾气,或者某个外设直接“消失”。这时候最容易想到的补救方案,就是把内核从一直追最新的 stable …

2026/10/19 8:50:45

视频汇聚平台如何破解高速服务区监控四大难题

1. 服务区监控的四个"老毛病",根源都指向同一个地方这两年一直在跑高速服务区的智慧化改造项目,跑得越多越觉得,服务区的监控体系普遍给人一种"看起来都有,实际上难用"的观感。前阵子在某个服务区摸底时&…

2026/10/19 8:45:45

开源CLI代码审查工具:Git原生+LLM可插拔的自动化守门人

1. 项目概述:这不是又一个“AI写代码”玩具,而是一套可嵌入日常开发流的开源代码审查协作者open-code-review 这个名字乍看平平无奇,但拆开来看——open(开放)、code(代码)、review(…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/19 0:04:56

悟空开源无代码平台实战:从实体建模到应用设计避坑指南

简介:基于悟空开源无代码平台的多种应用设计源码,面向希望在不编写代码的情况下快速搭建CRM、HRM、财务系统、ERP、SRM等业务应用的企业与技术人员,通过拖放式界面和内置模板即可完成应用定制与部署。压缩包共697个文件,约10.74MB…

2026/10/19 0:04:56

基于.NET9的多租户权限系统:数据权限与动态API落地实践

简介:这份资源是一套面向 .NET 与 Vue 全栈开发者的中台权限管理系统,后端基于 .Net9,前端采用 Vue3 Element Plus,并配套 uniapp uviewui 移动端方案,实现前后端分离。系统覆盖多租户隔离、数据权限、动态 Api、任务…

2026/10/19 0:04:56

场地预约小程序毕设实战:SSM+微信小程序+MySQL数据库设计到避坑

简介:面向毕业设计场景的场地预约小程序完整项目,基于微信小程序SSMMySql技术栈开发,适合计算机相关专业学生作为选题参考、功能扩展或二次开发起点。系统实现场地类型管理、场地管理、预约管理、取消申请与押金退回等功能,分为管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑