如何在 Android jetpack 组成单一活动应用程序中从通知导航到特定屏幕?

Max*_*e C 5 android-jetpack-compose jetpack-compose-navigation

我想从通知操作按钮导航到撰写中的单个活动应用程序中的特定屏幕。根据此文档,我决定使用深层链接导航。问题是,当我单击通知操作按钮时,它会在导航到预期屏幕之前重新启动我的活动。如果我的活动在后台打开,我不希望它重新启动。

我就是这样做的:

清单.xml

这是我在应用程序清单中指定的内容:

<activity
    android:name=".ui.AppActivity"
    android:launchMode="standard"
    android:exported="true">
    <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
    </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="screenRoute" />
    </intent-filter>
</activity>
Run Code Online (Sandbox Code Playgroud)

根导航图

这是我的根导航图中的深层链接声明:

composable(
   route = "screenRoute",
   deepLinks = listOf(navDeepLink { uriPattern = "myApp://screenRoute" })
) {
   ComposableScreen()
}
Run Code Online (Sandbox Code Playgroud)

待定意向

这是我用于通知操作按钮的待处理意图:

val intent = Intent().apply {
    action = Intent.ACTION_VIEW
    data = "myApp://screenRoute".toUri()
}

val deepLinkPendingIntent = TaskStackBuilder.create(context).run {
    addNextIntentWithParentStack(intent)
    getPendingIntent(1234, FLAG_UPDATE_CURRENT)
}
Run Code Online (Sandbox Code Playgroud)

我以为我在这里做错了什么,因为我没有找到任何关于这次重启的信息。因此,我下载了使用深层链接的官方撰写导航代码实验室(因为它也是单个应用程序活动),并且在使用来自意图的深层链接时执行相同的操作,活动将重新启动。

所以我的问题是:

  1. 是否可以在单个活动应用程序中从通知实现深层链接导航而无需重新启动它?
  2. 如果没有,实现此工作流程的方式是什么(从通知中打开特定的可组合项而不重新启动)?我应该从通知操作按钮发送广播并在我的应用程序内使用深层链接导航吗?
  3. 该活动是否从深层链接重新启动,因为它是主要活动(启动器)?

谢谢

Jol*_*MAS 5

问题

我在这里看到两个问题:

  • 在清单中,您尚未将启动模式设置为单任务android:launchMode="singleTask"
  • 无论您做什么,使用 TaskStackBuilder 都会重新创建一个活动。

可能的解决方案 - 自己处理深层链接

我认为您的问题有多种解决方案。然而,这是一种可能的解决方案

更新启动模式

为了确保您的应用程序始终拥有一个实例,您需要将 launchMode 设置为 singleTask

<activity
    android:name=".ui.AppActivity"
    android:launchMode="singleTask"
    android:exported="true">
    <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
    </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="screenRoute" />
    </intent-filter>
</activity>
Run Code Online (Sandbox Code Playgroud)

冷启动和主机启动

冷启动

当您的应用程序尚未启动并且您打开深层链接时,应用程序将启动并创建您的活动。因此,在 onStart 方法中,您将能够处理深层链接:

override fun onStart() {
        super.onStart()
        intent?.data?.let { /* handle deeplink */ }
        // consume the deeplink
        intent = null

    }
Run Code Online (Sandbox Code Playgroud)
热启动

当您的应用程序已经运行时,单击深层链接会将应用程序带到前面,并会触发具有意图的观察者。

setContent {
            DisposableEffect(Unit) {
                val listener = Consumer<Intent> { intent ->
                    // Handle deeplink

                }
                addOnNewIntentListener(listener)
                onDispose { removeOnNewIntentListener(listener) }
            }
        }
Run Code Online (Sandbox Code Playgroud)

使用VM处理深度链接

在冷启动中,您不在可组合范围内。要解决此问题,您可以使用虚拟机作为视图的事件发射器。

class MyViewModel : ViewModel() {
    val event = MutableStateFlow<Event>(Event.None)

    fun handleDeeplink(uri: Uri) {
        event.update { Event.NavigateWithDeeplink(uri) }
    }

    fun consumeEvent() {
        event.update { Event.None }
    }
}
sealed interface Event {
    data class NavigateWithDeeplink(val deeplink: Uri) : Event
    object None : Event
}
Run Code Online (Sandbox Code Playgroud)

冷启动情况下,调用该handleDeeplink(uri)方法


override fun onStart() {
        super.onStart()
        // To handle a cold deeplink intent we need to keep it and replace it with null
        intent?.data?.let { myViewModel.handleDeeplink(it) }
        intent = null
    }
Run Code Online (Sandbox Code Playgroud)

在热启动情况下,也调用它

DisposableEffect(Unit) {
                    val listener = Consumer<Intent> { intent ->
                        intent.data?.let {
                           myViewModel.handleDeeplink(it)
                        }
                    }
                    addOnNewIntentListener(listener)
                    onDispose { removeOnNewIntentListener(listener) }
                }
Run Code Online (Sandbox Code Playgroud)

现在,在您的主可组合项中,收集事件作为状态,并在收到事件时导航到深层链接。不要忘记使用它,因为我们在这里使用 stateFlow。

 val event by myViewModel.event.collectAsState()

                LaunchedEffect(event) {
                    when (val currentEvent = event) {
                        is Event.NavigateWithDeeplink -> navController.navigate(currentEvent.deeplink)
                        Event.None -> Unit
                    }

                    myViewModel.consumeEvent()
                }

Run Code Online (Sandbox Code Playgroud)

创建 PendingIntent

就像我说的,使用 TaskStackBuilder 将重新创建一个活动。您可以自己创建,而不是使用它来创建待处理的意图

val routeIntent = Intent(
            Intent.ACTION_VIEW,
            MyUri
        ).apply {
            flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
        }

        val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT

        val pending = PendingIntent.getActivity(
            appContext,
            0,
            routeIntent,
            flags
        )
Run Code Online (Sandbox Code Playgroud)