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

Android Studio实现类WhatsApp双按钮常驻置顶来电通知代码咨询

Android 类WhatsApp置顶来电通知实现方案

要实现带操作按钮、点击操作前常驻置顶的来电通知,核心通过「高优先级通话类通知渠道+前台服务保活+常驻通知标记」实现,全版本适配代码如下:

1. 清单文件配置

首先在AndroidManifest.xml中添加所需权限、注册服务与广播接收器:

<!-- Android13+ 通知发送权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- 前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<!-- 唤醒屏幕、锁屏展示权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />

<application ...>
    <!-- 来电通知承载服务 -->
    <service
        android:name=".IncomingCallService"
        android:exported="false"
        android:foregroundServiceType="phoneCall" />
    <!-- 通知按钮操作接收器 -->
    <receiver
        android:name=".CallActionReceiver"
        android:exported="false" />

    <!-- 自定义来电页Activity -->
    <activity android:name=".IncomingCallActivity" ... />
</application>

2. 创建高优先级通知渠道

Android 8.0及以上版本必须创建通知渠道才能正常展示高优先级通知,在应用启动时(如Application的onCreate方法)调用以下方法创建渠道:

private fun initIncomingCallChannel(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val callChannel = NotificationChannel(
            "channel_incoming_call",
            "来电提醒",
            NotificationManager.IMPORTANCE_HIGH
        ).apply {
            description = "用于展示音视频来电通知"
            enableVibration(true)
            vibrationPattern = longArrayOf(0, 800, 400, 800)
            lockscreenVisibility = Notification.VISIBILITY_PUBLIC
            // 关联系统来电铃声
            setSound(
                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE),
                AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                    .build()
            )
        }
        context.getSystemService(NotificationManager::class.java)
            .createNotificationChannel(callChannel)
    }
}

3. 前台服务承载常驻通知

创建IncomingCallService,通过前台服务发送通知,保证服务不停止时通知不会被系统回收、也无法被用户手动划掉:

class IncomingCallService : Service() {
    private val NOTI_ID = 10086
    private val CHANNEL_ID = "channel_incoming_call"

    override fun onBind(intent: Intent?): IBinder? = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 从启动参数获取来电人信息
        val callerName = intent?.getStringExtra("caller_name") ?: "未知联系人"
        val callerNumber = intent?.getStringExtra("caller_number") ?: ""

        // 构建挂断按钮的点击事件
        val rejectIntent = Intent(this, CallActionReceiver::class.java)
            .setAction("ACTION_REJECT_CALL")
        val rejectPendingIntent = PendingIntent.getBroadcast(
            this, 1, rejectIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // 构建接听按钮的点击事件
        val answerIntent = Intent(this, CallActionReceiver::class.java)
            .setAction("ACTION_ANSWER_CALL")
        val answerPendingIntent = PendingIntent.getBroadcast(
            this, 2, answerIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // 构建点击通知跳转的全屏来电页Intent
        val fullScreenIntent = Intent(this, IncomingCallActivity::class.java)
        val fullScreenPendingIntent = PendingIntent.getActivity(
            this, 3, fullScreenIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // 构建通知
        val callNotification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_baseline_call_24) // 替换为项目内的通话图标
            .setContentTitle(callerName)
            .setContentText(callerNumber)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setCategory(NotificationCompat.CATEGORY_CALL) // 标记为通话类通知,获得系统最高展示优先级
            .setOngoing(true) // 关键配置:设置为常驻通知,用户无法手动滑动删除
            .setAutoCancel(false) // 禁止点击后自动消失
            .setFullScreenIntent(fullScreenPendingIntent, true) // 锁屏/后台时悬浮全屏展示
            .addAction(R.drawable.ic_call_end, "挂断", rejectPendingIntent)
            .addAction(R.drawable.ic_call_accept, "接听", answerPendingIntent)
            .build()

        // 启动前台服务,展示通知
        startForeground(NOTI_ID, callNotification)
        return START_STICKY
    }
}

4. 处理按钮点击逻辑

创建CallActionReceiver接收按钮点击事件,执行对应接听/挂断逻辑后停止前台服务,通知会自动移除:

class CallActionReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            "ACTION_ANSWER_CALL" -> {
                // 此处添加接听逻辑:跳转通话页面、启动音视频流等
                Toast.makeText(context, "通话已接通", Toast.LENGTH_SHORT).show()
            }
            "ACTION_REJECT_CALL" -> {
                // 此处添加挂断逻辑:停止铃声、上报服务端挂断状态等
                Toast.makeText(context, "通话已挂断", Toast.LENGTH_SHORT).show()
            }
        }
        // 操作完成后停止服务,通知自动消失
        context.stopService(Intent(context, IncomingCallService::class.java))
    }
}

触发来电通知

收到来电信令时,直接启动前台服务即可弹出通知:

val callIntent = Intent(context, IncomingCallService::class.java).apply {
    putExtra("caller_name", "张三")
    putExtra("caller_number", "13800138000")
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(callIntent)
} else {
    context.startService(callIntent)
}

关键注意点

  • 必须给通知设置setOngoing(true),才能保证用户无法手动划掉通知,只有停止服务才会消失
  • 通知渠道优先级、通知本身优先级必须设为最高,同时标记CATEGORY_CALL分类,才能实现和系统来电一致的置顶、悬浮、锁屏展示效果
  • Android 13及以上版本需要动态申请POST_NOTIFICATIONS运行时权限,否则通知无法展示
  • Android 12及以上版本给PendingIntent必须添加FLAG_IMMUTABLE标记,否则会触发闪退
  • 必须使用startForeground启动前台服务承载通知,避免系统因内存不足杀死进程导致通知消失

实现效果参考:
类WhatsApp来电通知效果

内容的提问来源于stack exchange,提问作者Adeel Zaman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 15:42:22