Android:延迟加载 DataBinding 视图会引发异常

Posted

技术标签:

【中文标题】Android:延迟加载 DataBinding 视图会引发异常【英文标题】:Android: lazy-loading of DataBinding view throws exception 【发布时间】:2022-01-10 21:03:43 【问题描述】:

有一个大的数据绑定视图,可能需要几秒钟才能膨胀。我想向用户显示一个启动屏幕并为主视图膨胀一个延迟的动作。 android studio 抛出异常“无法调用观察者方法”。

主活动:

override fun onCreate(savedInstanceState: Bundle?) 
    super.onCreate(savedInstanceState)
    setContentView(R.layout.screen_splash)
    Handler(Looper.getMainLooper()).postDelayed(
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
            this,
            R.layout.activity_main
        )
        binding.lifecycleOwner = this // this line throws exception
    , 1000)

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
    <variable
        name="vm"
        type="com.example.ViewModel"/>
</data>

<RelativeLayout
    android:layout_
    android:layout_
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
            android:id="@+id/map_list"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_
            android:layout_
            tools:context=".MainActivity" />
</RelativeLayout>

例外:

2021-12-05 13:42:56.638 23701-23701/com.example E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example, PID: 23701
java.lang.RuntimeException: Failed to call observer method
    at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback(ClassesInfoCache.java:226)
    at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeMethodsForEvent(ClassesInfoCache.java:194)
    at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeCallbacks(ClassesInfoCache.java:185)
    at androidx.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged(ReflectiveGenericLifecycleObserver.java:37)
    at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
    at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:196)
    at androidx.databinding.ViewDataBinding.setLifecycleOwner(ViewDataBinding.java:434)
    at com.example.databinding.ActivityMainBindingImpl.setLifecycleOwner(ActivityMainBindingImpl.java:166)
    at com.example.MainActivity.onCreate$lambda-3(MainActivity.kt:106)
    at com.example.MainActivity.$r8$lambda$lffeScwTEbHi2B1isKEoQYU2po4(Unknown Source:0)
    at com.example.MainActivity$$ExternalSyntheticLambda5.run(Unknown Source:2)
    at android.os.Handler.handleCallback(Handler.java:888)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:213)
    at android.app.ActivityThread.main(ActivityThread.java:8178)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)
 Caused by: java.lang.NumberFormatException: s == null
    at java.lang.Integer.parseInt(Integer.java:577)
    at java.lang.Integer.valueOf(Integer.java:801)
    at com.example.databinding.ControlPanelBindingImpl.executeBindings(ControlPanelBindingImpl.java:800)...

【问题讨论】:

使用“数据绑定”的原因是什么? 当应用用户看到白屏时,主视图需要 2+ 秒才能充气。我想显示一条启动消息,然后加载视图。 您可以通过其他方式处理此问题,显示覆盖 2 秒的进度条,2 秒后您可以隐藏进度条并显示实际屏幕。 如何在主视图之前增加进度?最好显示启动画面。 您是否检查过如何在 XML 中使用数据绑定?你会收到java.lang.NumberFormatException: s == null 【参考方案1】:

我不确定您的应用程序的结构。在我们的例子中,我们有一个类似的要求,我们想要显示一个加载器,直到初始的fragment 被绑定。所以我们在activity 中创建了一个viewStub。然后,当附加片段时,我们在共享视图模型中将liveData 设置为SHOW,通知活动膨胀viewStub。通过这种方式,我们膨胀了隐藏全屏显示启动图像的视图存根。然后,一旦创建了片段中的视图并在onViewCreated 中,我们再次将共享视图模型中的liveData 设置为HIDE,这会隐藏 viewStub 并显示片段。

【讨论】:

命令“viewStub.inflate()”正在抛出“java.lang.NumberFormatException: s == null”。我很高兴看到延迟加载数据绑定视图的工作示例。【参考方案2】:

在您的主要活动中使用 fragmentContainerView。显示要在此容器中显示的视图。在容器前面创建一个视图。在此视图中显示启动消息。加载主视图时,使启动视图的可见性消失。所以闪屏会使用activity生命周期,主视图会使用fragment生命周期。这可能是您的解决方案。

【讨论】:

感谢您的建议。我尝试了 FragmentContainerView 方法,就像在这个示例 repo github.com/codehustler53/FragmentContainerViewVsFrameLayout 中一样,不幸的是,当生命周期应用于片段时,Android Studio 抛出了相同的异常。【参考方案3】:

免责声明:事实证明该问题已在 UPDATE 2 部分解决 在答案中;如果其他部分可以帮助未来的访问者解决其他潜在问题,则会留下其他部分

乍一看,以为Caused by: java.lang.NumberFormatException: s == null与问题有关;尽管您在 cmets 中告诉过它正在同步工作。

java.lang.RuntimeException: Failed to call observer method 异常不会通过在代码中跟踪错误来帮助了解错误。

但是您的代码在简单的布局中成功地与我合作;可能问题与您在访问binding.lifecycleOwner 时尝试同步加载的繁重布局有关;我猜后者 sn-p 需要一段时间才能访问lifecycleOwner。所以,你可以提前发布一些延迟。

为此,我将使用协程而不是发布延迟;因为代码会更加线性和可读:

CoroutineScope(Main).launch 

    delay(1000) // Original delay of yours
    val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
        this@MainActivity,
        R.layout.activity_main
    )

    delay(1000) // try and error to manipulate this delay 
    binding.lifecycleOwner = this@MainActivity 


如果还没有使用,协程依赖是

def coroutine_version = "1.5.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"

更新

在加载主要活动时,您的代码中发布的延迟无助于在该延迟期间显示启动画面/启动屏幕;

Handler(Looper.getMainLooper()).postDelayed(
    val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
        this,
        R.layout.activity_main
    ) // This won't be called unless the 1000 sec is over
    binding.lifecycleOwner = this 
, 1000)

您的代码的作用:

    显示启动画面 延迟发布(此处仍未加载主布局) 延迟结束后显示主布局

所以,发布的延迟只是累积到加载主布局的时间;这甚至使它更加滞后。此外,这不是使用闪屏的推荐方式(This medium post 会有所帮助)

相反,我认为你打算做什么:

    显示启动画面 加载主布局 发布延迟,以便在延迟期间加载主布局需要时间 延迟结束后显示主布局

但是,问题是需要加载的是 UI,它需要在主线程中执行,而不是在后台线程中。所以,我们不是使用两种不同的布局,而是调用setContentView() 两次;您可以改为为您的主布局创建一个布局,并添加一些表示启动屏幕的视图,这将完全遮盖主布局(在其前面),直到加载布局(即延迟结束);然后删除此启动视图:

演示:

splash_screen.xml(您想要的任何布局都必须匹配父级以隐藏它):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/splash_screen"
    android:layout_
    android:layout_
    android:background="@color/black"
    android:gravity="center">

    <ImageView
        android:layout_
        android:layout_
        android:src="@mipmap/ic_launcher" />
</LinearLayout>

主要活动:

class MainActivity : AppCompatActivity() 

    companion object 
        const val TAG = "LOG_TAG"
    

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)

        Log.d(TAG, "Start Inflating layout")
        binding = DataBindingUtil.setContentView(
            this@MainActivity,
            R.layout.activity_main
        )

        // Show only the first time app launches, not in configuration changes
        if (savedInstanceState == null) 
            CoroutineScope(IO).launch 
                Log.d(TAG, "Start of delay")
                delay(1000)
                Log.d(TAG, "End of delay")
                withContext(Main) 
                    hideSplash()
                
            
            showSplash()
        

        binding.lifecycleOwner = this@MainActivity
        Log.d(TAG, "End Inflating layout")

    

    private fun showSplash() 
        supportActionBar?.hide()

        // Inflate splash screen layout
        val splashLayout =
            layoutInflater.inflate(
                R.layout.splash_screen,
                binding.rootLayout,
                false
            ) as LinearLayout

        binding.rootLayout.addView(
            splashLayout
        )

    

    private fun hideSplash() 
        supportActionBar?.show()
        binding.rootLayout.removeView(
            findViewById(R.id.splash_screen)
        )
    


日志

2021-12-11 21:59:18.349 20681-20681/  D/LOG_TAG: Start Inflating layout
2021-12-11 21:59:18.452 20681-20707/  D/LOG_TAG: Start of delay
2021-12-11 21:59:18.476 20681-20681/  D/LOG_TAG: End Inflating layout
2021-12-11 21:59:20.457 20681-20707/  D/LOG_TAG: End of delay

现在延迟随着布局膨胀而运行;加载时显示的启动画面;并在延迟结束时结束。

更新 2

这绝对行不通:databinding = ... 行需要 2.5 秒才能完成,无法在“databinding.root”准备好之前添加视图。它适用于提供的代码,因为您的主视图很小。

现在尝试在 dataBinding 中将膨胀布局与 setContentView() 分开;仍然都需要在主线程中

setContentView(R.layout.screen_splash)

CoroutineScope(Main).launch 
    // Inflate main screen layout asynchronously 
    binding = ActivityMainBinding.inflate(layoutInflater) 

    delay(2500) // 2.5 sec delay of loading the mainLayout before setContentView

    setContentView(binding.root)
    binding.lifecycleOwner = this@MainActivity

【讨论】:

如果这没有任何线索;请发布一些可以重现问题的内容 我尝试了您的代码,并且在第一次延迟完成后它会立即产生相同的错误。布局确实很重,想法是在开始时将其全部加载,以便更快地跨屏幕导航。 根据 logcat,binding.lifecycleOwner 赋值需要几毫秒,而 secContentView 2500。 你能用一个简单的布局来检查一下吗?这已经适用于我的任何一种情况……我认为问题在于繁重的布局负载 @Gonki 我已经更新了答案,希望现在可以使用

以上是关于Android:延迟加载 DataBinding 视图会引发异常的主要内容,如果未能解决你的问题,请参考以下文章

DSL 元素 'android.dataBinding.enabled' 已过时,已替换为 'android.buildFeatures.dataBinding'

Android databinding不存在

Android11——DataBinding的使用

ClassNotFoundException:没有找到类“android.databinding.DataBinderMapper”

Android:DataBinding 和 ViewBinding 的区别

Android中的Listview延迟加载平滑[重复]