22、导航与通知:点击通知跳转到指定页面、配置通知的DeepLink、处理通知中的参数
通知推送,几乎是每个App的标配。但通知点击后跳转到哪里,怎么带参数,这里面的坑其实不少。我见过太多项目,通知点进去直接崩了,或者参数丢了,用户一脸懵。
今天我们就来彻底搞定这件事。用Navigation组件配合DeepLink,让通知跳转变得清晰可控。
通知跳转的核心思路
说白了,通知就是一个“外部入口”。它跟用户从桌面图标点进来不一样,通知往往带着特定目标——比如跳到一个订单详情、一个聊天窗口。
在单Activity架构下,我们只有一个Activity。所有页面切换都靠NavController。那通知怎么告诉NavController该去哪?
答案就是:DeepLink。
DeepLink本质上是一个URI。你可以把它理解成“页面的地址”。通知里塞一个DeepLink,点击后系统解析这个地址,Navigation组件自动找到对应的目的地。
核心流程:
- 用户收到通知
- 点击通知,触发PendingIntent
- PendingIntent携带DeepLink URI
- Activity启动,NavController解析URI
- 导航到目标页面,参数自动注入
第一步:在NavGraph中配置DeepLink
先别急着写通知代码。我们得先在导航图里把DeepLink注册好。这样Navigation才知道哪个页面能接收什么格式的URI。
假设我们有一个订单详情页,需要接收订单ID。DeepLink的URI可以设计成:myapp://order/{orderId}
<!-- res/navigation/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/orderDetailFragment"
android:name="com.example.OrderDetailFragment"
android:label="订单详情">
<argument
android:name="orderId"
app:argType="string" />
<deepLink
app:uri="myapp://order/{orderId}" />
</fragment>
</navigation>
小技巧:URI里的占位符 {orderId} 会自动映射到同名的argument上。命名保持一致,省心省力。
第二步:构建通知的PendingIntent
通知的PendingIntent需要携带DeepLink。这里有个关键点:用NavDeepLinkBuilder。这是Navigation组件提供的工具类,专门用来构建通知的Intent。
我自己早期踩过坑,手动拼Intent,结果参数传过去总是null。后来才发现,直接用NavDeepLinkBuilder最稳。
// 构建DeepLink URI
val deepLinkUri = "myapp://order/${orderId}"
// 用NavDeepLinkBuilder创建PendingIntent
val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.orderDetailFragment)
.setArguments(Bundle().apply {
putString("orderId", orderId)
})
.createPendingIntent()
// 构建通知
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("订单更新")
.setContentText("您的订单 $orderId 状态已变更")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
注意:setGraph() 和 setDestination() 必须调用。否则NavDeepLinkBuilder不知道你的导航结构。我曾经漏掉setGraph,结果通知点进去直接崩溃,查了半天才发现。
第三步:处理通知中的参数
参数传过去了,目标Fragment怎么接收?其实跟普通导航传参一样。在Fragment的onCreate或onViewCreated里读取arguments即可。
class OrderDetailFragment : Fragment() {
private val args: OrderDetailFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val orderId = args.orderId
// 用orderId加载数据
loadOrderDetail(orderId)
}
}
这里用 navArgs() 委托,类型安全,不用手动解析Bundle。我推荐大家都用这个方式。
第四步:处理App已启动的情况
上面讲的流程,假设App被杀了,通知点进来重新创建Activity。但如果App已经在前台呢?
嗯,这里有个细节。如果Activity已经存在,系统不会重新创建,而是调用 onNewIntent()。我们需要在那里处理DeepLink。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 正常初始化NavController
val navController = findNavController(R.id.nav_host_fragment)
// 处理启动Intent中的DeepLink
intent?.data?.let { uri ->
navController.handleDeepLink(intent)
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// App已存在时,处理新的DeepLink
intent.data?.let { uri ->
val navController = findNavController(R.id.nav_host_fragment)
navController.handleDeepLink(intent)
}
}
}
关键点:
- onCreate里处理:App冷启动
- onNewIntent里处理:App热启动(已在后台)
- 两个地方都要调用
handleDeepLink()
第五步:AndroidManifest中的配置
别忘了在Manifest里声明DeepLink的scheme。否则系统不认识你的URI。
<activity
android:name=".MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- DeepLink intent-filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="order" />
</intent-filter>
</activity>
launchMode设为singleTask,这样通知点进来不会重复创建Activity实例。这个我建议固定下来。
常见坑与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 通知点进去白屏 | NavDeepLinkBuilder没设setGraph | 加上setGraph(R.navigation.nav_graph) |
| 参数传过去是null | URI占位符和argument名称不一致 | 检查 {orderId} 和 argument name 是否匹配 |
| App在前台时通知无效 | 没处理onNewIntent | 在onNewIntent里调用handleDeepLink |
| 通知点击后回退栈混乱 | 没设置launchMode | Activity设为singleTask |
避坑指南:我曾经在一个项目里,通知点进去总是跳到首页而不是目标页。排查了半天,发现是NavDeepLinkBuilder的setDestination写错了ID。嗯,这种低级错误,大家引以为戒。
总结一下
通知跳转这件事,说白了就是三步:
- 在NavGraph里配好DeepLink
- 用NavDeepLinkBuilder构建PendingIntent
- 在Activity的onCreate和onNewIntent里处理DeepLink
参数传递、回退栈管理,Navigation组件都帮你搞定了。你只需要把URI设计好,剩下的交给框架。
我个人习惯把DeepLink的URI统一管理在一个常量类里,比如 const val ORDER_DEEPLINK = "myapp://order/{orderId}"。这样维护起来方便,不会到处散落字符串。
好了,通知跳转就讲到这里。下一章我们会聊更进阶的话题——导航与多模块架构。到时候见。
公众号:蓝海资料掘金营,微信deep3321