【问题标题】:Android Lifecycle ViewModel not persisted after locking and unlocking in landscape modeAndroid Lifecycle ViewModel 在横向模式下锁定和解锁后不持久
【发布时间】:2018-02-20 23:26:17
【问题描述】:

我有一个非常简单的应用程序,其中包含一个虚拟的 Activity 和一个虚拟的 Android Lifecycle ViewModel ViewModel

片段活动

class FragmentActivity: AppCompatActivity() {
    companion object {
        private const val TAG = "FragmentActivity"
        private const val KEY = "key_key"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fragment)
        Log.d(TAG, "Activity ${hashCode()}, onCreate: orientation ${resources.configuration.orientation}")

        if (savedInstanceState != null) {
            Log.d(TAG, "Activity ${hashCode()}, onCreate: saved string from savedInstanceState ${savedInstanceState.getString(KEY)}")
        } else {
            Log.d(TAG, "Activity ${hashCode()}, onCreate: no savedInstanceState")
        }

        val myViewModel: MyViewModel = ViewModelProviders
                .of(this, VmFactory())
                .get(MyViewModel::class.java)

    }

    override fun onResume() {
        super.onResume()

        Log.d(TAG, "Activity ${hashCode()}, onResume: orientation ${resources.configuration.orientation}")
    }

    override fun onStop() {
        super.onStop()

        Log.d(TAG, "Activity ${hashCode()}, onStop: orientation ${resources.configuration.orientation}")
    }

    override fun onDestroy() {
        super.onDestroy()

        Log.d(TAG, "Activity ${hashCode()}, onDestroy: orientation ${resources.configuration.orientation}")
    }

    override fun onSaveInstanceState(outState: Bundle?) {
        val savedString = "SAVED_STATE_" + hashCode()
        outState?.putString(KEY, savedString)
        Log.d(TAG, "Activity ${hashCode()}, onSaveInstanceState: $savedString")

        super.onSaveInstanceState(outState)
    }
}

视图模型

class MyViewModel: ViewModel() {
    companion object {
        private const val TAG = "MyViewModel"
    }

    init {
        Log.d(TAG, "MyViewModel ${hashCode()}: created")
    }

    override fun onCleared() {
        Log.d(TAG, "MyViewModel ${hashCode()}: onCleared")

        super.onCleared()
    }
}

ViewModelFactory

class VmFactory: ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass == MyViewModel::class.java) {
            return MyViewModel() as T
        } else {
            throw IllegalArgumentException()
        }
    }
}

清单

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.dkarmazi.unknownmemorysampleapp">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".WebViewActivity">
        </activity>

        <activity android:name=".FragmentActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

杀死 ViewModel 的步骤

  1. 将应用设置为横向模式
  2. 锁定屏幕
  3. 解锁屏幕并观察 ViewModel 消失了,Activity 被销毁并创建了两次。

日志输出

02-20 16:30:14.159 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onCreate: orientation 2
02-20 16:30:14.159 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onCreate: no savedInstanceState
02-20 16:30:14.169 8296-8296/com.dkarmazi.viewmodelscoping D/MyViewModel: MyViewModel 55090662: created
02-20 16:30:14.183 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onResume: orientation 2
### LOCKED IN LANDSCAPE MODE
02-20 16:30:22.978 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onSaveInstanceState: SAVED_STATE_244798673
02-20 16:30:22.996 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onStop: orientation 2
### UNLOCKED IN LANDSCAPE MODE
02-20 16:30:33.177 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onStop: orientation 2
02-20 16:30:33.178 8296-8296/com.dkarmazi.viewmodelscoping D/MyViewModel: MyViewModel 55090662: onCleared
02-20 16:30:33.179 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onDestroy: orientation 2
02-20 16:30:33.241 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onCreate: orientation 1
02-20 16:30:33.241 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onCreate: saved string from savedInstanceState SAVED_STATE_244798673
02-20 16:30:33.242 8296-8296/com.dkarmazi.viewmodelscoping D/MyViewModel: MyViewModel 113479034: created
02-20 16:30:33.248 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onResume: orientation 1
02-20 16:30:33.705 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onSaveInstanceState: SAVED_STATE_218111434
02-20 16:30:33.710 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onStop: orientation 1
02-20 16:30:33.712 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onDestroy: orientation 1
02-20 16:30:33.815 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 158140230, onCreate: orientation 2
02-20 16:30:33.815 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 158140230, onCreate: saved string from savedInstanceState SAVED_STATE_218111434
02-20 16:30:33.822 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 158140230, onResume: orientation 2

这种行为与发现 here 一致,但是,我希望 arch 库能够处理这种情况,因为它非常标准,并且纵向模式下的锁定和解锁可以按预期工作。

有什么好的想法可以防止ViewModel 在这种特殊情况下被破坏?

在 Nexus 5X、API 27 上测试

编辑 1: 在添加要保存在 onSaveInstanceState 中的字符串并检查该字符串是否在所有活动破坏和创建过程中持续存在后,我很确定这是库的错误.

编辑 2:为什么会出现这个问题?

问题 1:在横向锁定的情况下,捆绑包会以某种方式从 Activity 244798673 正确路由到位于 02-20 16:30:33.241 的 Activity 218111434,但是 ViewModel 无法通过这一系列操作持续存在。这与 bundles 行为不一致,因为我们在技术上仍然在同一个 Activity 范围内。

问题2:竖屏锁定和解锁的日志输出:

02-20 16:38:10.283 8567-8567/? D/FragmentActivity: Activity 244798673, onCreate: orientation 1
02-20 16:38:10.283 8567-8567/? D/FragmentActivity: Activity 244798673, onCreate: no savedInstanceState
02-20 16:38:10.293 8567-8567/? D/MyViewModel: MyViewModel 55090662: created
02-20 16:38:10.301 8567-8567/? D/FragmentActivity: Activity 244798673, onResume: orientation 1
02-20 16:38:13.459 8567-8567/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onSaveInstanceState: SAVED_STATE_244798673
02-20 16:38:13.480 8567-8567/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onStop: orientation 1
02-20 16:38:17.704 8567-8567/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onResume: orientation 1

ViewModel在纵向锁定和解锁中持久化,与横向场景不一致。

【问题讨论】:

    标签: android android-architecture-components android-architecture-lifecycle android-architecture


    【解决方案1】:

    这其实是一个源自Android框架的bug:

    https://issuetracker.google.com/issues/73644080

    底层的 Android Arch 库使用保留的片段来持久化ViewModels。实验上,在开启 PIN / PATTERN / SWIPE / PASSWORD 的情况下,在横向模式下锁定和解锁设备的相同场景下,保留的碎片也将无法生存。因此,每次我们解锁设备时,我们都会得到一个 ViewModel 的新实例。

    一些用例:

    1. 设备:NEXUS 5X,API 级别:24,锁定方式:PIN / PATTERN / SWIPE / PASSWORD。可以通过任何解锁方法解锁来重现。也可以通过指纹解锁来重现。

    2. 设备:NEXUS 5X,API 级别:27,锁定方式:PIN / PATTERN / PASSWORD 。只有在使用指纹解锁设备时才能重现。使用 PIN / PATTERN / PASSWORD 解锁效果很好。

    3. 设备:Pixel,API 级别:27,锁定方式:PIN(其他未测试)+指纹。只有在使用指纹解锁设备时才能重现。使用 PIN / PATTERN / PASSWORD 解锁效果很好。

    【讨论】:

      【解决方案2】:

      您在此处提供的 ViewModelFactory 不是 Singleton。应该是这个问题。

      val myViewModel: MyViewModel = ViewModelProviders
                      .of(this, VmFactory())
                      .get(MyViewModel::class.java)
      

      将工厂设置为单例,它应该可以工作。

      【讨论】:

      • 不,它不会有帮助,如果你查看android.arch.lifecycle.ViewModelProvider::get() 的源代码,只有当它无法在内部检索现有视图模型时才会调用Factory::create。在这种情况下,工厂的范围并不重要,Factory::create 将被调用,无论它是否是单例。
      • 见下面的源代码。如果您提供自己的工厂,它会使用提供的工厂,否则它会使用默认工厂的静态实例(sDefaultFactory 变量)。 android.googlesource.com/platform/frameworks/support/+/master/….
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-11
      • 1970-01-01
      相关资源
      最近更新 更多