Android开发如何实现来电去电时的Overlay悬浮弹窗功能?
实现通话触发悬浮弹窗的完整方案
1. 配置所有必要权限
首先在AndroidManifest.xml中声明以下权限:
<!-- 读取通话状态权限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 悬浮窗权限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- Android 12及以上通知权限,部分机型后台触发弹窗需要 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
SYSTEM_ALERT_WINDOW属于特殊权限,普通运行时权限申请逻辑无效,需要引导用户跳转到系统设置页手动开启,权限判断代码如下:
// 检查悬浮窗权限是否开启 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(context)) { // 引导用户跳转权限开启页 val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${context.packageName}")) // 此处需要用Activity上下文启动才能接收权限回调 context.startActivity(intent) } }
2. 正确注册通话状态广播
Android 8.0及以上对静态注册的隐式广播做了限制,通话状态广播无法通过静态注册接收,必须动态注册广播接收器:
// 在Application或长期存活的服务中注册 val callReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when(intent?.action) { // 来电状态 TelephonyManager.ACTION_PHONE_STATE_CHANGED -> { val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE) if (state == TelephonyManager.EXTRA_STATE_RINGING) { val incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) showFloatWindow(context, incomingNumber) } } // 去电状态 Intent.ACTION_NEW_OUTGOING_CALL -> { val outgoingNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER) showFloatWindow(context, outgoingNumber) } } } } // 绑定广播过滤规则 val filter = IntentFilter().apply { addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED) addAction(Intent.ACTION_NEW_OUTGOING_CALL) } registerReceiver(callReceiver, filter)
3. 实现悬浮窗弹窗逻辑
悬浮窗需要通过WindowManager添加,注意不同系统版本的窗口类型适配:
private var floatView: View? = null private fun showFloatWindow(context: Context?, phoneNumber: String?) { context ?: return // 无悬浮窗权限直接返回 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) { return } // 避免重复添加弹窗 if (floatView != null) return val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager // 加载自定义悬浮窗布局 floatView = LayoutInflater.from(context).inflate(R.layout.layout_float_call, null) // 给布局赋值,比如显示号码、标记信息等 floatView?.findViewById<TextView>(R.id.tv_phone)?.text = phoneNumber val layoutParams = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, // 窗口类型适配 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE, // 窗口标志:不抢焦点、允许在锁屏显示 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, PixelFormat.TRANSLUCENT ) // 调整弹窗显示位置,比如顶部显示 layoutParams.gravity = Gravity.TOP windowManager.addView(floatView, layoutParams) // 通话结束后移除悬浮窗逻辑,可监听通话Idle状态执行以下代码 // windowManager.removeView(floatView) // floatView = null }
4. 常见踩坑点
- 不要用Activity上下文创建悬浮窗,必须用Application上下文,避免Activity销毁后窗口被连带移除
- 广播接收器
onReceive方法只能执行10秒以内的逻辑,不要在内部做耗时操作,收到广播后直接弹窗即可 - 小米、华为等定制ROM有额外的悬浮窗权限、后台弹出界面权限限制,需要额外引导用户开启对应权限
内容的提问来源于stack exchange,提问作者develop456
相关产品推荐
相关产品推荐

