【问题标题】:Android navigation components with deep link: onNewIntent called multiple times带有深层链接的 Android 导航组件:onNewIntent 被多次调用
【发布时间】:2020-09-09 02:34:03
【问题描述】:

这次我需要你的帮助,了解如何使用带有深度链接的 android 导航组件。

我一直在关注这个documentation,片段和深度链接之间的连接工作正常。

问题在于接收深层链接的活动。就我而言,我设置了 android:launchMode="singleTask"

<activity android:name=".features.welcome.WelcomeActivity"
    android:launchMode="singleTask">
     <nav-graph android:value="@navigation/welcome_nav_graph" />
</activity>

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    Timber.d("onNewIntent: $intent with activity: $this")
    navController.handleDeepLink(intent)
}

通过这种配置,我注意到了一些奇怪的行为:

WelcomeActivity 每次单击深度链接时都会收到两次 onNewIntent 调用。有时甚至会创建该活动的新实例......就像

1_object1-onNewIntent

2_object1-onNewIntent

3_object2-onCreate

这里有一些日志:

首次发布

onCreate: Intent { flg=0x10000000 cmp={applicationId}/{package}.WelcomeActivity } 与活动: {package}.WelcomeActivity@4adbef0

打开深层链接

onNewIntent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}… flg=0x10010000 cmp={applicationId}/{package}.WelcomeActivity(有 extras) } 带有活动:{package}.WelcomeActivity@4adbef0

onNewIntent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x1001c000 cmp={applicationId}/{package}.WelcomeActivity(有 extras) } 带有活动:{package}.WelcomeActivity@4adbef0

onCreate: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x1001c000 cmp={applicationId}/{package}.WelcomeActivity(有 extras) } 活动:{package}.WelcomeActivity@b77c6b

杀死应用并打开深层链接

onCreate: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x10018000 cmp={applicationId}/{package}.WelcomeActivity(有 extras) } 活动:{package}.WelcomeActivity@b78f4df

onNewIntent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x1001c000 cmp={applicationId}/{package}.WelcomeActivity(有 extras) } 活动:{package}.WelcomeActivity@b78f4df

onCreate: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x1001c000 cmp={applicationId}/{package}.WelcomeActivity(有 extras) } 与 {package}.WelcomeActivity@dfe87b2

更新:

1 - 启动模式似乎与此问题无关。我注意到默认启动模式也是如此。

2- navController.navigate(intent.dataString.toUri()) 似乎工作正常。所以我猜问题是navController.handleDeepLink(intent)。

【问题讨论】:

  • 不错。就我而言,我必须在调用 super 之前设置 intent.data = null。谢谢。
  • 您找到解决方案了吗?对我来说,当我点击链接时,onNewIntent() 也会多次调用
  • @Arj1411,好吧,我很久以前就在下面发布了解决方案:D。请记住,其他人后来也这样做了,因此请选择最适合您的解决方案。
  • 对我来说,唯一的区别是使用混合。

标签: android android-jetpack android-architecture-navigation android-jetpack-navigation


【解决方案1】:

测试不同的变化,我得出的结论是“navController.handleDeepLink(intent)”导致了这种奇怪的行为。

这是我尝试过的:

我从导航中删除了 deepLink,并且深层链接工作正常(我手动添加了 deepLink),并且行为正常:使用 singleTask,如果 Activity 已创建,则仅调用一次 onNewIntent。如果未创建活动,则调用 onCreate。

另一个问题是 navController.handleDeepLink(intent) 将在 onCreate 中自动调用(您可以在 javadocs 中查看)。调用onNewIntent时,需要调用navController.handleDeepLink(intent)。

我决定从 NavController 尝试“navigate(Uri deepLink)”,我发现它按预期工作(第一段中描述的行为)。 有了这个替代方法,我决定做一些改变:

class WelcomeActivity : AppCompatActivity(){

      private val navController by lazy { findNavController(R.id.nav_host_fragment) }

      private var deepLinkData: Uri? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Timber.d("onCreate: $intent  with activity: $this")
        val data = intent.data
        intent.data = null
        setContentView(R.layout.activity_welcome)
        handleDeepLink(data)
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        Timber.d("onNewIntent: $intent with activity: $this")
        setIntent(intent)
        val data = intent?.data
        handleDeepLink(data)
    }

    private fun handleDeepLink(uri: Uri?) {
        //TODO: there is an issue that will cause onNewIntent to be called twice when the activity is already present.
        if (uri != null && deepLinkData.toString() != uri.toString() && navController.graph.hasDeepLink(uri)) {
            //possible deep link for LoginFragment
            deepLinkData = uri
            navController.navigate(uri)
        }
    }

}

注意 onCreate 中的这段代码很重要:

val data = intent.data
intent.data = null

这是因为如果我需要阻止调用“navController.handleDeepLink(intent)”,因为如果存在该信息,它将自动调用,从而导致奇怪的行为。

navController.graph.hasDeepLink(uri) 将帮助您查看您的图表是否可以处理该 uri。如果不使用,那么“navigate(Uri deepLink)”会抛出异常。

如果您遇到同样的问题,希望它可以帮助您。如果您对此有更多见解,请随时留下一些 cmets。

【讨论】:

    【解决方案2】:

    onNewIntent 的回调第一次到达时,只需设置标志intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 并将变异意图传递给handleDeepLink(intent);

    此标志消除了onNewIntent 回调的第二次到达,因为它重新附加到现有的Activity(完全重建回栈跟踪到所需的深层链接目标)而不是启动新的Activity

    详情在handleDeepLink方法的源码中。

    【讨论】:

    • 这很好用。但是,它在 API 19 上给了我奇怪的行为,深层链接之前的片段也被删除了。 intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) 解决了我的问题。
    • @veeyikpong 感谢提及,我添加了这些标志,它可以防止重新创建片段
    【解决方案3】:

    点击隐式深层链接时,会有意设置FLAG_ACTIVITY_NEW_TASK。并且根据文档,将重新创建 backstack 以使其处于良好状态。请参阅此处的文档Implicit deep link

    如果您不希望这种行为并且不想更改意图标志,您可以自己处理深度链接,而不是将其传递给导航组件。所以如果你使用AdvancedNavigationSample你需要去掉handledeeplink并使用findNavController().navigate()指向方向。

    还记得将启动器活动的启动模式设置为 singleTask 以接收点击 deepink 或通知的新意图。

    1. 将示例中的 NavController.handledeeplink() 替换为
    fun BottomNavigationView.navigateDeeplink(
        navGraphIds: List<Int>,
        fragmentManager: FragmentManager,
        containerId: Int,
        uri: Uri
    ) {
        navGraphIds.forEachIndexed { index, navGraphId ->
            val fragmentTag = getFragmentTag(index)
    
            // Find or create the Navigation host fragment
            val navHostFragment = obtainNavHostFragment(
                fragmentManager,
                fragmentTag,
                navGraphId,
                containerId
            )
            // Handle deeplink
            val canHandleDeeplink = navHostFragment.navController.graph.hasDeepLink(uri)
    
            if (canHandleDeeplink) {
                if (selectedItemId != navHostFragment.navController.graph.id) {
                    selectedItemId = navHostFragment.navController.graph.id
                }
                navHostFragment.lifecycleScope.launchWhenResumed {
                    // Wait for fragment to restore state from backStack
                    // otherwise navigate will be ignored
                    // Ignoring navigate() call: FragmentManager has already saved its state
                    navHostFragment.navController.navigateOnce(uri)
                }
            }
        }
    }
    
    
    1. intent.data 存储在变量中并将其设置为null,以便导航组件无法处理它。在onHandleNewIntent 中也可以这样做
    2. 将此深层链接传递给底部导航以进行处理。如果应用已经在运行,您可以使用上述功能直接导航到目的地。

    完整的解决方案在这个sample实现。

    优点:

    1. 您可以以同样的方式直接处理通知点击。从操作中创建一个深层链接并将其传递给 PendingIntent 中的intent.data
    2. 您可以根据需要验证和更改深层链接。就像用户没有登录一样,它直接进入登录活动,或者移动到指定的目的地。

    【讨论】:

    • 兄弟,你拯救了我的一天。非常感谢!
    【解决方案4】:

    感觉很奇怪,但实际上似乎按预期工作。 The documentation 关于隐式深层链接说:

    在触发隐式深度链接时,回栈的状态取决于是否使用 Intent.FLAG_ACTIVITY_NEW_TASK 标志启动了隐式 Intent:

    如果设置了标志,则任务返回堆栈被清除并替换为深度链接目标......

    如果未设置该标志,您将保留在触发隐式深层链接的上一个应用的任务堆栈中。

    在您的情况下,我相信当您点击链接时,意图会设置标志Intent.FLAG_ACTIVITY_NEW_TASK,因此会创建整个新堆栈。目前the implementation只是用新创建的栈重启activity,确保任务状态一致。

    如果您不需要此行为,一种可能的解决方法是在导航组件有机会处理它之前从意图中清除标志 Intent.FLAG_ACTIVITY_NEW_TASK

    【讨论】:

      【解决方案5】:

      我遇到了类似的问题,我关注了NavigationAdvancedSample,我有多个BottomNavigationView 和多个NavHostFragments。因此,将我的修复发布给那些有相同案例的人。

      更具体地说,当应用通过通知深层链接启动时,我的 MainActivity 的 onCreate() 被调用了两次。

      我为意图设置了以下标志:

      private fun getPendingIntent(data: Uri?): PendingIntent {
          val intent = Intent(context, MainActivity::class.java)
          intent.action = Intent.ACTION_VIEW
          intent.data = data
          intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
          return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
      }
      

      而且我在 MainActivity 的清单中没有 android:launchMode="singleTask",因为它对我的情况并没有真正的帮助。

      我已经解决了我的问题,首先防止 navController.handleDeepLink(intent) 使用以下代码在 MainActivity 的 onCreate() 上自动调用(感谢您):

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          val uri = intent.data
          intent.data = null
      }
      

      然后在NavigationExtensions 文件中的this PR 中进行更改,该文件基本上将navController.handleDeepLink(intent) 替换为navController.navigate(uri),这导致Activity 被创建了两次,正如您在帖子中指出的那样。

      因此,使用BottomNavigationView 设置导航的代码在 MainActivity 中如下所示:

      private fun setupBottomNavigation(uri: Uri?) {
          val navGraphIds =
              listOf(
                  R.navigation.all_integrations,
                  R.navigation.favourites,
                  R.navigation.settings
              )
      
          currentNavController = bottomNavView.setupWithNavController(
              navGraphIds,
              supportFragmentManager,
              R.id.navHostContainer
          )
      
          uri?.let {
              bottomNavView.handleDeepLinks(
                  navGraphIds,
                  supportFragmentManager,
                  R.id.navHostContainer,
                  it
              )
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2020-07-13
        • 2020-05-30
        • 2020-03-02
        • 2021-08-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-05
        • 2021-12-15
        相关资源
        最近更新 更多