【问题标题】:Object is changed after sending it to another Fragment对象在发送到另一个 Fragment 后发生变化
【发布时间】:2020-10-03 09:24:35
【问题描述】:

所以,我有一个非常奇怪的问题,我将一个对象从 Fragment A 传递到 Fragment B ,我在 Fragment B 的一个新实例中修改了这个对象,但是在我更改此对象的值后,当我弹出 Fragment B 并且该对象现在也为 Fragment A

保持修改时,它也会更改该值

片段 A

...

   override fun onItemClick(v: View?, position: Int) {
        searchView.clearFocus()
        val bundle = Bundle()
        bundle.putSerializable("shop", landingAdapter.getItem(position))
        findNavController().navigate(R.id.action_navigation_landing_to_shopFragment, bundle)
    }

...

现在,我从 Fragment B 得到这个对象

片段 B

    private lateinit var shop: Shop
    private lateinit var shopAdapter:ShopAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        shopAdapter = ShopAdapter(sharedViewModel, requireContext(),this)
        arguments?.let {
             shop = it.getSerializable(ARG_SHOP) as Shop
            if (shop.products.isNotEmpty()) {
                shopAdapter.setItems(shop.products)
            }
        }
    }

现在,在我从 Fragment A 获得这个 Shop 对象后,我只在 Fragment B 中修改它

onViewCreated(){

    shop.quantity = 1

}

但是当我回到 Fragment A 时,现在 Shop 对象数量值为 1 ,但这应该没什么,因为我只更改了 Fragment B 而不是 Fragment A 的对象,而在 Fragment B 中是一个新实例对象

我真的很困惑

编辑

到目前为止,我每次从片段 A 转到片段 b 时都尝试发送一个新实例

   val bundle = Bundle()
                bundle.putSerializable("shop", landingAdapter.getItem(position).copy())  

findNavController().navigate(R.id.action_navigation_landing_to_shopFragment, bundle)



         val bundle = Bundle()
                val shop = landingAdapter.getItem(position)
                bundle.putSerializable("shop", shop)  findNavController().navigate(R.id.action_navigation_landing_to_shopFragment, bundle)


         val bundle = Bundle()
                val shop = Shop(landingAdapter.getItem(position).name,landingAdapter.getItem(position).quantity)
                bundle.putSerializable("shop", shop)  
    findNavController().navigate(R.id.action_navigation_landing_to_shopFragment, bundle)

它们都没有向 Fragment B 发送新的 shop 实例,因此每当我在 Fragment B 处更改数量时,Fragment A 都会获得相同的数量值,不应发生变化

【问题讨论】:

  • 如果您不想在原始位置修改它,您需要传递一个副本,不是吗?我不记得足够多的 Android 来回忆 get/putSerializable 做了什么。
  • 但是如果我将原始对象传递给片段 B,那么在片段 b 中,我正在创建该对象的一个​​新实例,我在其中进行更改,奇怪的是这种更改也会影响原始对象
  • 如果你在其他类中改变了传递的对象,它会影响原来的对象,除非你对对象进行深拷贝,所以它将是另一个不同的对象
  • 是什么让你认为你得到了一个新的、不相关的实例?
  • 但是如果我像现在一样将 Fragment A 中的对象作为一个包传递,并且我创建了一个 Shop 类型的新变量来保存它,为什么我在那里所做的更改会影响原始对象

标签: android android-fragments kotlin android-architecture-components android-architecture-navigation


【解决方案1】:

这实际上不是一个有明显答案的明显问题。大约 2 个月前我收到了这个问题,这也让我感到困惑,因为有时这是你得到的行为,有时不是。

首先要注意的是,当您向 Fragment 提供参数时,您会将它们放在一个 Bundle 中。这个包在内部存储了一个用于字符串键的 Map。

所以当您拨打fragment.setArguments(bundle) 时,您基本上是在“发送地图”。

因此,由于没有 IPC 发生(与在活动中通过 Intent 与 Android 对话不同),地图中存在相同的对象实例。

直到系统使用 Bundle 参数的属性,并且确实重新创建了实例

进程死亡后(您的应用程序在后台,Android 回收内存,并且在重新启动时,Android 会重建您的任务堆栈并根据从 FragmentManager 保存到 onSaveInstanceState 的现有历史记录重新创建活动的 Fragments),使用最初传递的参数“重新创建”传入的属性。

此时,Serializable 已被系统重新创建,如果您要导航回去,它将与您最初发送的实例不同。

因此,您会得到相同的实例,因为 Bundle 在内部是一个地图。

您还可以获得不同的实例,因为 Android 可以重新创建它。

解决方案:在发送实例之前使用副本以获得一致的行为。

编辑:

这也适用于可变列表中的嵌套对象。因此,如果您有类似的课程

class A(
    private val list: List<B>
)

还有

class B(
    private var blah: String
)

如果 B 在通过 Bundle 发送 A 后发生突变,则 List&lt;B&gt; 中的 Bs 会发生变化,这将反映在两个屏幕上,除非在进程死亡之后。

所以要记住这一点。

要创建副本,您可以这样做

val copyList = a.list.map { it.copy() }

【讨论】:

  • 你应该记住关于 Java 的一点(如果这个解释有意义的话)是对类的引用的副本是为非原始对象传递的,而不是实例的副本。要复制实例,您必须自己进行。
  • 你知道@EpicPandaForce,我在将它发送到 val shop = shopAdapter.getItem(position) 之类的捆绑包之前做了一个副本,然后将该商店副本传递给捆绑包,我得到了完全相同的行为,这真的很奇怪
  • 我也在数据类上用 .copy() 做了一个拷贝,还有一个 val shop = Shop(...) 也做了同样的事情
  • 这不是副本,它与 shopAdapter 中的对象实例完全相同。如果这是 Kotlin,而您的商店是 data class,那么要获得副本,您只需致电 shop.copy()。如果您从不调用复制或创建新实例,它将是同一个实例。当您将商店交给putSerializable时,请致电copy
  • 如果你喜欢data class(val而不是var,并且在你的类的公共属性中使用List而不是MutableList/ArrayList,你可以减少这些错误。
【解决方案2】:

对象引用按值传递

Kotlin 中的所有对象引用都是按值传递的。这意味着将值的副本传递给方法。但诀窍在于,传递值的副本也会改变对象的真实值。要了解原因,请尝试以下示例:

object  ObjectReferenceExample {
 fun transform(p:Person) {
        p.name = "Person2";
 }
}

class Person(var name:String) 
   fun main() {
     val  person =  Person("Person1");
     ObjectReferenceExample.transform(person);
     System.out.println(person.name);// output are Person2
}

原因是 Kotlin 对象变量只是指向内存堆中真实对象的简单引用。因此,即使 Kotlin 以值的方式向方法传递参数,但如果变量指向对象引用,那么真实的对象也会发生变化。 同样适用于 java

【讨论】:

猜你喜欢
  • 2013-09-12
  • 2014-08-24
  • 2014-03-06
  • 2011-04-03
  • 1970-01-01
  • 2019-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多