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'
ClassNotFoundException:没有找到类“android.databinding.DataBinderMapper”