Android 13 通知不显示也不弹权限框:检查 POST_NOTIFICATIONS 和通知渠道
Android 13 通知不显示时,区分 targetSdk 对弹窗的影响,检查 POST_NOTIFICATIONS、用户选择、升级预授权及通知渠道。
应用在 Android 13 上调用了 notify(),没有报错,通知栏却没有消息;创建了通知渠道,权限框也没有出现。先记录设备 API、应用 targetSdk,以及这次是全新安装、覆盖更新,还是从旧手机恢复。通知权限的初始状态和弹窗时机与这些条件有关。
本文针对普通应用通知。通知能否显示至少经过应用级通知开关、Android 13 的 POST_NOTIFICATIONS 权限,以及通知渠道设置。推送消息是否已到达应用是更前面的一层,应先用应用日志确认收到消息并执行了展示逻辑,避免把消息未送达当成通知权限问题。
targetSdk 决定谁控制权限弹窗
Android 13(API 33)及以上引入通知运行时权限。普通通知在全新安装后默认关闭。对于目标 API 33 及以上的应用,应由应用在合适的用户操作后主动请求;调用 createNotificationChannel() 或 notify() 不会代替这一步。
如果应用仍以 API 32 或更低为目标,系统控制弹窗时机:首次创建通知渠道后启动 Activity,或先启动 Activity 再创建第一个渠道时,系统会展示权限提示。这也说明,单纯在后台创建渠道,不一定立刻看到弹窗。
旧目标版本还有一个容易干扰排查的条件:用户点过一次“不允许”后,系统不会再次提示,直到卸载重装或应用更新为目标 API 33 及以上。卸载会清除应用数据,不能要求真实用户用这种方式修复;开发时也应使用隔离测试安装,保留升级场景的数据。
检查最终清单,再显式请求
下面的示例使用 compileSdk >= 33、targetSdk >= 33、minSdk >= 23,并使用 AndroidX Activity、Core。先在清单的 manifest 内、application 外声明:
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS" />
构建变体、库清单或合并规则都可能影响最终结果。查看 Android Studio 的 Merged Manifest,确认声明实际进入待安装的包;不要只看某个源码清单。
下面是可独立放入 Activity 的请求示例。它用一个按钮演示用户主动启用通知的入口,只负责请求和反馈;正式应用应结合“提醒我进度”等功能说明用途,取得许可后再执行自己的通知展示逻辑。
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
class NotificationPermissionActivity : ComponentActivity() {
private val requestNotifications = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
val text = if (granted) "通知权限已允许" else "未开启通知"
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(Button(this).apply {
text = "启用通知"
setOnClickListener { enableNotifications() }
})
}
private fun enableNotifications() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(
this, Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
requestNotifications.launch(
Manifest.permission.POST_NOTIFICATIONS
)
return
}
val enabled = NotificationManagerCompat.from(this)
.areNotificationsEnabled()
val text = if (enabled) "应用通知已开启" else "请检查系统通知设置"
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
}
Activity 还需按项目的导航方式注册到清单中。示例没有在权限回调中立即发送通知,也没有把“点击按钮”当成已经授权;launch() 是异步请求,业务应根据回调结果继续。
用户拒绝后保留其他可用功能,不要在每次进入页面时循环请求。shouldShowRequestPermissionRationale() 返回 true 时,适合先解释用途再由用户决定是否重试;返回 false 也可能是首次申请,不能凭这一项就断定“永久拒绝”。结合应用记录的请求经历和实际权限状态判断,确需用户去设置时,提供主动点击的设置入口。
权限允许后,检查具体渠道
在 API 26 及以上,普通通知还需要已有的通知渠道,NotificationCompat.Builder(context, channelId) 使用的 ID 必须与创建的渠道一致。示例中的 CHANNEL_ID 应与应用发通知处共用同一常量:
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
private const val CHANNEL_ID = "task_updates"
fun ensureTaskChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = context.getSystemService(
NotificationManager::class.java
)
manager.createNotificationChannel(
NotificationChannel(
CHANNEL_ID,
"任务进度",
NotificationManager.IMPORTANCE_DEFAULT
)
)
}
}
创建渠道是幂等操作,但用同一个 ID 再创建一次,不能把用户关闭的渠道重新打开,也不能随意提高已创建渠道的重要性。应用级通知开关已开而某类消息仍没有时,读取 getNotificationChannel(CHANNEL_ID),检查是否存在、importance 是否为 IMPORTANCE_NONE,并在系统设置中核对对应渠道。
如果通知能在通知栏找到,只是没有横幅或声音,继续检查渠道重要性、勿扰模式及设备设置;这种症状与“完全不能发布通知”不同。不要通过不断更换渠道 ID 绕过用户选择。
没弹窗并不总是失败
设备从 Android 12L 或更早升级时,符合条件的已有应用可自动获得通知权限:应用已有通知渠道,而且用户之前没有关闭其通知。从旧设备恢复也可能按已有应用处理。因此“没有弹权限框但能正常发通知”可以是正常状态。
相反,旧系统中已明确关闭的通知,不会因为升级 Android 13 自动恢复。用户把权限框划走也不会改变原权限状态。应根据权限和开关的实际值判断,不要用弹窗是否出现作为唯一证据。
前台服务还有单独边界:启动 FGS 本身不以获得 POST_NOTIFICATIONS 为前提,但仍必须按要求提供前台服务通知。用户拒绝通知权限后,相关提示可以出现在任务管理界面,而不出现在通知抽屉;不要删除 startForeground() 所需的通知来“适配拒绝”。媒体会话、自管理通话等豁免有各自条件,普通业务通知不能据此跳过申请。
修复后分别核验全新安装允许、拒绝、取消,以及覆盖升级且通知原本开启、关闭的场景。允许后应能在正确渠道找到通知;拒绝后应用应正常处理未开启状态。不要清空用户数据来制造一次成功弹窗,再把它当成升级路径已经解决。


