Engineering a reliable push notification architecture becomes complicated when Android OEMs implement aggressive, non-standard battery optimizations. If your Firebase Cloud Messaging (FCM) implementation works flawlessly on Pixel and Samsung devices but fails consistently on Xiaomi, Redmi, or POCO devices, you are encountering the MIUI background execution limit.
This issue specifically manifests when an app is removed from the recent apps tray. FCM messages fail to wake the application, and system notifications do not display unless the user manually discovers and enables a hidden "Autostart" permission.
Resolving this requires a hybrid approach: optimizing the backend payload structure to leverage Google Play Services, and implementing a robust programmatic routing mechanism in the Android client to guide users to the correct system settings.
Understanding the Root Cause: MIUI vs. Standard Android
To understand the MIUI notification fix, we must look at how FCM push notifications operate under the hood.
In standard AOSP (Android Open Source Project), when an FCM payload is received, Google Play Services broadcasts an intent to the target app. If the app is in the background or killed, the system temporarily wakes the app's FirebaseMessagingService to handle the message (provided it has a high-priority flag).
MIUI alters this fundamental lifecycle to extend battery life. When a user swipes an app away from the recents screen on a MIUI device, the OS performs an action akin to a system-level "Force Stop."
Once an app is force-stopped, the Android OS drops all broadcast receivers for that package. Google Play Services attempts to deliver the FCM broadcast, but the MIUI framework explicitly blocks it from waking the app process. The only exception is if the application is manually whitelisted in the MIUI Security application under the "Autostart" (or "Auto-start") category.
The Fix Step 1: Backend Payload Optimization
For backend integrators driving enterprise mobile engagement, relying entirely on data payloads is a critical mistake when dealing with MIUI.
FCM offers two types of payloads: notification and data.
- Data messages are handled entirely by the client app's
FirebaseMessagingService. If the app process is dead on MIUI, the message is lost. - Notification messages are intercepted and handled directly by Google Play Services when the app is in the background or killed.
Because Google Play Services is a privileged system application that is implicitly whitelisted by MIUI, it can display the system tray notification even if your app's process is dead.
To maximize delivery rates without requiring immediate user intervention, structure your FCM requests as a mixed payload.
{
"message": {
"topic": "enterprise_alerts",
"notification": {
"title": "Critical System Alert",
"body": "Your server deployment has failed."
},
"data": {
"click_action": "FLUTTER_NOTIFICATION_CLICK",
"deep_link": "app://deployment/12345",
"event_id": "req_88492"
},
"android": {
"priority": "high"
}
}
}
By including the notification object, Google Play Services will render the UI. When the user taps the notification, the system launches your app and passes the data payload via the launch Intent's extras.
The Fix Step 2: Programmatically Routing to MIUI Autostart
If your application relies on background data processing (e.g., decrypting secure messages, updating local SQLite databases silently) before displaying a notification, a notification payload is insufficient. You must use data payloads, which means you absolutely require the MIUI Autostart permission.
Because Autostart is not a standard Android permission (android.permission.RECEIVE_BOOT_COMPLETED does not cover it), you cannot request it via the standard ActivityCompat.requestPermissions API. You must detect the environment and route the user to the specific MIUI settings screen.
Implement the following Kotlin utility to handle the routing safely.
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
object MiuiAutostartUtils {
private val AUTO_START_INTENTS = arrayOf(
// Modern MIUI / HyperOS
Intent().setComponent(
ComponentName(
"com.miui.securitycenter",
"com.miui.permcenter.autostart.AutoStartManagementActivity"
)
),
// Legacy MIUI
Intent().setComponent(
ComponentName(
"com.coloros.safecenter",
"com.coloros.safecenter.permission.startup.StartupAppListActivity"
)
),
// Fallback MIUI
Intent().setComponent(
ComponentName(
"com.miui.securitycenter",
"com.miui.securitycenter.MainActivity"
)
)
)
fun isMiui(): Boolean {
return Build.MANUFACTURER.equals("Xiaomi", ignoreCase = true) ||
Build.MANUFACTURER.equals("Redmi", ignoreCase = true) ||
Build.MANUFACTURER.equals("POCO", ignoreCase = true)
}
fun navigateToAutostartSettings(context: Context): Boolean {
if (!isMiui()) return false
for (intent in AUTO_START_INTENTS) {
if (context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
try {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
return true
} catch (e: Exception) {
// Log to crashlytics/monitoring, continue to next intent
}
}
}
return false
}
}
Implementing the User Experience
Do not trigger this intent on app launch. Blindly throwing users into a system settings menu causes high abandonment rates.
Instead, implement an onboarding screen that explains why the permission is needed. Tie this flow to a DataStore or SharedPreferences flag so it only prompts once.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class NotificationOnboardingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notification_onboarding)
findViewById<Button>(R.id.btn_enable_autostart).setOnClickListener {
if (MiuiAutostartUtils.isMiui()) {
val success = MiuiAutostartUtils.navigateToAutostartSettings(this)
if (success) {
markAutostartPromptShown()
} else {
// Fallback: Inform user to manually navigate to Settings -> Apps -> Permissions -> Autostart
}
}
}
}
private fun markAutostartPromptShown() {
// Implementation for saving state (e.g., via Androidx DataStore)
}
}
Deep Dive: Why the Intent Array is Necessary
OEM custom operating systems are highly fragmented. Xiaomi frequently refactors the internal structure of their "Security" app across different MIUI and HyperOS versions.
If you hardcode a single ComponentName (e.g., com.miui.permcenter.autostart.AutoStartManagementActivity), your app will crash with an ActivityNotFoundException on older devices or specific regional ROMs (like the EU ROM vs Global ROM).
The AUTO_START_INTENTS array acts as a progressive fallback mechanism. The resolveActivity check ensures we only attempt to launch an Intent if the Android PackageManager confirms the target component exists on the current device.
Common Pitfalls and Edge Cases
The HyperOS Transition
Xiaomi is actively replacing MIUI with HyperOS. Fortunately for backend and client developers, HyperOS maintains backwards compatibility with the legacy com.miui.securitycenter package names for system intents. The code provided above remains valid for modern HyperOS devices out-of-the-box.
Battery Saver Strict Mode
Even with Autostart enabled, MIUI's "Battery Saver" mode can restrict network access for background apps. If push delivery remains inconsistent, advise users to navigate to App Info -> Battery Saver and select "No Restrictions." This can also be programmatically mapped using a similar Intent strategy targeting com.miui.powerkeeper.
Enterprise Alternatives: Vendor Push Networks
For enterprise mobile engagement where guaranteed delivery is critical (e.g., banking alerts, medical dispatch systems), relying solely on FCM is a recognized architectural weakness. In these environments, you should implement the Xiaomi Push (MiPush) SDK alongside FCM. Xiaomi's proprietary push network operates at the system level and bypasses the Autostart requirement entirely, ensuring 100% delivery rates on their hardware.
Conclusion
Fixing FCM on MIUI devices requires bridging the gap between backend architecture and client-side system configuration. By restructuring your payloads to utilize Google Play Services for notification rendering, and tactically routing users to the Autostart settings for background execution rights, you can restore predictable notification behavior across the highly fragmented Android ecosystem.