Jetpack Compose在Redmi设备触发Offset is unspecified异常咨询
问题说明
项目开发过程中会高频触发如下致命崩溃:
Fatal Exception: java.lang.IllegalStateException Offset is unspecified
- 复现设备特征:仅在Redmi品牌的手机、平板设备上出现
- 影响范围:Jetpack Compose 1.1.0 到最新的1.2.0-beta03版本均存在该问题,无法通过切换Compose版本规避
- 崩溃触发链路:触摸事件分发阶段,
AndroidComposeView计算窗口位置偏移时拿到了未初始化的Offset值,直接抛出异常,完整堆栈如下:
androidx.compose.ui.geometry.Offset.getX-impl (Offset.java:28) androidx.compose.ui.platform.AndroidComposeView.recalculateWindowPosition (AndroidComposeView.java:2) androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc (AndroidComposeView.java:6) androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent (AndroidComposeView.java:62) android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3088) android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2781) android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3088) android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2781) android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3088) android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2781) android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3088) android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2781) com.android.internal.policy.DecorView.superDispatchTouchEvent (DecorView.java:496) com.android.internal.policy.PhoneWindow.superDispatchTouchEvent (PhoneWindow.java:1853) android.app.Activity.dispatchTouchEvent (Activity.java:4072) com.android.internal.policy.DecorView.dispatchTouchEvent (DecorView.java:454) android.view.View.dispatchPointerEvent (View.java:13744) android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent (ViewRootImpl.java:5601) android.view.ViewRootImpl$ViewPostImeInputStage.onProcess (ViewRootImpl.java:5401) android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:4902) android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:4955) android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:4921) android.view.ViewRootImpl$AsyncInputStage.forward (ViewRootImpl.java:5061) android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:4929) android.view.ViewRootImpl$AsyncInputStage.apply (ViewRootImpl.java:5118) android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:4902) android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:4955) android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:4921) android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:4929) android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:4902) android.view.ViewRootImpl.deliverInputEvent (ViewRootImpl.java:7654) android.view.ViewRootImpl.doProcessInputEvents (ViewRootImpl.java:7623) android.view.ViewRootImpl.enqueueInputEvent (ViewRootImpl.java:7584) android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent (ViewRootImpl.java:7785) android.view.InputEventReceiver.dispatchInputEvent (InputEventReceiver.java:251) android.view.InputEventReceiver.nativeConsumeBatchedInputEvents (InputEventReceiver.java) android.view.InputEventReceiver.consumeBatchedInputEvents (InputEventReceiver.java:213) android.view.ViewRootImpl.doConsumeBatchedInput (ViewRootImpl.java:7730) android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run (ViewRootImpl.java:7809) android.view.Choreographer$CallbackRecord.run (Choreographer.java:1031) android.view.Choreographer.doCallbacks (Choreographer.java:854) android.view.Choreographer.doFrame (Choreographer.java:782) android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1016) android.os.Handler.handleCallback (Handler.java:883) android.os.Handler.dispatchMessage (Handler.java:100) android.os.Looper.loop (Looper.java:223) android.app.ActivityThread.main (ActivityThread.java:7562) java.lang.reflect.Method.invoke (Method.java) com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:539) com.android.internal.os.ZygoteInit.main (ZygoteInit.java:950)
根因说明
大量开发者都在Redmi/小米设备上遇到过相同问题,这是MIUI系统的兼容性bug:MIUI修改了原生触摸事件上报逻辑,在批量投递触摸事件的场景下,会偶发传入x/y坐标为NaN的无效MotionEvent,Compose内部recalculateWindowPosition方法没有对这类非法值做容错,直接读取Offset坐标时就会触发Offset is unspecified的崩溃,和Compose版本没有直接关系,所以切换版本无法解决。
可行解决方案
目前有两个经过线上大流量验证的规避方案,不会影响正常触摸交互:
- 方案1:自定义安全ComposeView,在事件分发入口拦截无效触摸事件,代码如下:
class SafeComposeView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : ComposeView(context, attrs) { override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { ev?.let { if (it.x.isNaN() || it.y.isNaN()) { return true } } return super.dispatchTouchEvent(ev) } }
项目中所有使用Compose的位置替换为该自定义View即可。
- 方案2:如果是直接通过
ComponentActivity.setContent接入Compose,可以直接在Activity层重写dispatchTouchEvent做全局拦截,不需要替换所有ComposeView:
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { ev?.let { if (it.x.isNaN() || it.y.isNaN()) { return true } } return super.dispatchTouchEvent(ev) }
补充:如果项目中同时存在原生View和Compose混合的页面,优先用Activity层全局拦截的方案,覆盖范围更全,不会漏掉其他位置触发的同类崩溃。
内容的提问来源于stack exchange,提问作者Ильин Павел
相关产品推荐
相关产品推荐

