简体   繁体   中英

ViewDataBinding causes crash resuming app after activity.finish(), NPE on access to mRebindRunnable during view attachment

Crash surfaces after successfully launching an activity, inflating a fragment w/ data binding. After pressing the back button at the root level, the app (properly) goes through an activity.finish() but keeps the application instance in the background. Upon relaunching, the app crashes at some point after the view is created (at least based on debugging).

The crash:

2020-08-26 20:00:50.626 9706-9706/com.org.app.dev E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.org.app.dev, PID: 9706
    java.lang.NullPointerException: Attempt to read from field 'java.lang.Runnable androidx.databinding.ViewDataBinding.mRebindRunnable' on a null object reference
        at androidx.databinding.ViewDataBinding.access$100(ViewDataBinding.java:65)
        at androidx.databinding.ViewDataBinding$6.onViewAttachedToWindow(ViewDataBinding.java:165)
        at android.view.View.dispatchAttachedToWindow(View.java:19564)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2028)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1721)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7598)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
        at android.view.Choreographer.doCallbacks(Choreographer.java:790)
        at android.view.Choreographer.doFrame(Choreographer.java:725)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

Digging into source, debugging, the best I can gather is the ViewDataBinding sees that a formerly bound view has reattached, and attempts to rebind with existing resources. However, it's obviously lost it's reference to the static runnable. Relative source code from ViewDataBinding:

    private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;

    static {
        if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
            ROOT_REATTACHED_LISTENER = null;
        } else {
            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
                @TargetApi(VERSION_CODES.KITKAT)
                @Override
                public void onViewAttachedToWindow(View v) {
                    // execute the pending bindings.
                    final ViewDataBinding binding = getBinding(v);
                    binding.mRebindRunnable.run();
                    v.removeOnAttachStateChangeListener(this);
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                }
            };
        }
    }

I did notice the same stack trace from: App crash caused by data binding related to java.lang.Runnable android.databinding.ViewDataBinding.mRebindRunnable but that issue seems to talk about the app crashing instantly (mine only occurs after attempting to rehydrate). But from that issue, I did note that logging my view tag comes back null. I tried setting a static tag in xml, with no change.

Other things I've tried:

  • If I only instantiate the fragment, the issue doesn't occur (but obviously doesn't attach to the framelayout
  • No clear debugging crash point (onCreateView, onViewCreated are both called)
  • I tried changing how I inflate, assign lifecycleowner, bind the viewmodel; No change affected the crash.
  • I tried making some calls into the binding (invalidateAll(), unbind()) with no success.

Again, the issue only crops up when the application instance is still alive, but the activity has been destroyed. Thanks for any tips!!

EDIT: Relevant code bits

Activity (Java) -

ExampleFragment exampleFragment = new ExampleFragment(exampleParameter);
FragmentManager fragmentManager = getSupportFragmentManager();

fragmentManager
        .beginTransaction()
        .add(R.id.example_frame_layout, exampleFragment, EXAMPLE_TAG)
        .commit();

Fragment (Kotlin) -

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    binding = FragmentExampleBinding.inflate(inflater, container, false).apply {
        lifecycleOwner = viewLifecycleOwner
    }
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    binding.run {
        viewModel = this@ExampleFragment.viewModel
    }
}

UPDATE

Interestingly enough, I'm now seeing this when I try to inflate the same fragment (freshly instantiated) into a different (not-launch) activity. Debugging this case, I can't find any issues beyond experiencing the crash after inflating AND returning the binding's root view (instead of null). I have tried with both the created static method for the layout as well as the DataBindingUtil. Both experience the same crash.

Eureka!!

Not only can <merge/> not be used to <include/> layouts (as well documented here: https://developer.android.com/topic/libraries/data-binding/expressions#includes )

BUT

Binding also DOES NOT work across <include/> layouts that have <merge/> at the root. Here's what that looks like:

<layout>
...

    <include layout="id/sub_layout"
        bind:viewModel="@{viewModel} />
...
</layout>

And the issue (R.id.sub_layout.xml):

<layout>
...
    <merge>
        <TextView/>
        <ImageView/>
    </merge>
...
</layout>

Strange that it seemed to work on the launch activity, but good to know the symptoms (crash async after inflation) that lead to this. For folks having troubles with data binding and poor error messages, I would absolutely recommend trying changes to your layout.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM