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启动前台服务承载通知,避免系统因内存不足杀死进程导致通知消失
实现效果参考:
内容的提问来源于stack exchange,提问作者Adeel Zaman
相关产品推荐
相关产品推荐

