You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 23:00:00