开发VoIP应用:如何实现后台来电通知?WhatsApp/Telegram方案咨询
Hey there! Let's break down your VoIP app's background notification issue and how the big players like WhatsApp and Telegram nail near-perfect delivery rates.
onMessageReceived in Background Your hunch about battery optimization is spot-on—Android's Doze mode and App Standby aggressively restrict background services to save power. But there's another key detail: FCM has two message types, and only one triggers onMessageReceived in the background:
- Notification messages: Handled directly by the Android system when your app is backgrounded—your service won't get called.
- Data messages: Delivered directly to your
MyFirebaseMessagingServiceregardless of app state, as long as you set the priority tohigh.
Here's how to adjust your setup to ensure incoming call notifications get through:
a. Send High-Priority Data Messages
Make sure your server sends data-only messages with priority: high (critical for bypassing Doze restrictions). Example payload:
{ "to": "DEVICE_FCM_TOKEN", "data": { "type": "voip_incoming_call", "caller_id": "user_123", "caller_name": "Alice Smith", "call_session_id": "session_456" }, "priority": "high" }
b. Update Your Manifest for VoIP Priority
Add permissions and declare your service with proper intent filters, plus VoIP-specific foreground service permissions for Android 12+:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CALL" /> <service android:name=".MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>
c. Refine Your MyFirebaseMessagingService
Handle data messages, launch a full-screen incoming call notification, and start a foreground service to avoid being killed:
public class MyFirebaseMessagingService extends FirebaseMessagingService { private static final String TAG = "FCM Service"; private static final int FOREGROUND_SERVICE_ID = 1001; private static final String VOIP_CHANNEL_ID = "voip_call_channel"; @Override public void onMessageReceived(RemoteMessage remoteMessage) { super.onMessageReceived(remoteMessage); if (remoteMessage.getData().isEmpty()) return; Map<String, String> callData = remoteMessage.getData(); if ("voip_incoming_call".equals(callData.get("type"))) { // Show full-screen incoming call notification (critical for Android 10+) showIncomingCallNotification(callData); // Start foreground service to keep app alive startVoipForegroundService(); } } private void showIncomingCallNotification(Map<String, String> callData) { // Create full-screen intent for incoming call activity Intent callIntent = new Intent(this, IncomingCallActivity.class); callIntent.putExtra("caller_name", callData.get("caller_name")); callIntent.putExtra("session_id", callData.get("call_session_id")); PendingIntent fullScreenIntent = PendingIntent.getActivity( this, 0, callIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, VOIP_CHANNEL_ID) .setSmallIcon(R.drawable.ic_call_icon) .setContentTitle("Incoming Call") .setContentText(callData.get("caller_name")) .setPriority(NotificationCompat.PRIORITY_HIGH) .setCategory(NotificationCompat.CATEGORY_CALL) .setFullScreenIntent(fullScreenIntent, true); // Create channel for Android O+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( VOIP_CHANNEL_ID, "VoIP Calls", NotificationManager.IMPORTANCE_HIGH ); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } NotificationManagerCompat.from(this).notify(1, builder.build()); } private void startVoipForegroundService() { NotificationCompat.Builder serviceBuilder = new NotificationCompat.Builder(this, "voip_service_channel") .setSmallIcon(R.drawable.ic_service_icon) .setContentTitle("Voip Service Active") .setContentText("Ready to receive calls") .setPriority(NotificationCompat.PRIORITY_LOW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel serviceChannel = new NotificationChannel( "voip_service_channel", "VoIP Background Service", NotificationManager.IMPORTANCE_LOW ); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(serviceChannel); startForeground(FOREGROUND_SERVICE_ID, serviceBuilder.build()); } } @Override public void onNewToken(String token) { Log.d(TAG, "Refreshed FCM token: " + token); // Send this token to your server for future call notifications } }
d. Request Battery Optimization Whitelisting
Prompt users to exclude your app from battery restrictions (critical for devices from Xiaomi, Huawei, OPPO, etc.):
private void requestBatteryWhitelist() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); String packageName = getPackageName(); if (!pm.isIgnoringBatteryOptimizations(packageName)) { Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + packageName)); startActivity(intent); } } }
They don't rely solely on FCM—here's their secret sauce:
- Persistent TCP Long Connections: They maintain their own direct connection to their servers, so even if FCM is blocked, calls come through via this channel.
- Persistent Foreground Service: They run a silent foreground service 24/7, which prevents the system from killing their app process.
- Vendor-Specific Optimizations: They work directly with Android OEMs (like Xiaomi, Samsung) to get their apps whitelisted from aggressive background killing by default.
- Telecom API Integration: They use Android's
ConnectionService(part of the Telecom Manager), which gives VoIP apps system-level priority for incoming calls—ensuring notifications wake the device regardless of state. - Multi-Channel Retries: If FCM fails, their servers retry via their own long connection, and vice versa, until the notification is delivered.
- Smart Wake-Up Triggers: They use subtle system events (like network changes) to re-establish connections if they get dropped.
内容的提问来源于stack exchange,提问作者jerrymouse

