MvvmCross / MvvmCross

The .NET MVVM framework for cross-platform solutions, including Android, iOS, MacCatalyst, macOS, tvOS, WPF, WinUI
http://mvvmcross.com
Microsoft Public License
3.87k stars 1.31k forks source link

[Android] Setup initialization issue when app killed and resumed #1192

Closed cyrilcathala closed 7 years ago

cyrilcathala commented 8 years ago

Hi,

I found an issue on Android apps when an app is killed and then resumed.

The problem: Usually, the MvxSetup is initialized during the splashscreen on Android. If an app is killed by the OS and you try to resume the app, the splashscreen doesn't launch, so the MvxSetup is initialized during the Activity.OnCreate() through the MvxActivityViewExtensions.OnViewCreate. Well, the problem is, when you register fragments BEFORE the Activity.OnCreate(), the setup isn't yet initialized and it crashes because Mvx (the IoC container) is null.

How to reproduce: The issue can be reproduced with the XPlatformMenus sample, the MainActivity registers fragments before the base.OnCreate() call (check MainActivity.OnCreate). Go to Dev Settings, check the "Don't keep activities" and set Background process limit to "No background processes". Then you just have to launch the sample once, go to the launch screen, then go back to the app.

The solution? Here is a workaround, ensuring the setup is initialized before registering fragments:

public abstract class BaseFragmentActivity : MvxCachingFragmentActivity
{
[...]
        protected override void OnCreate(Bundle bundle)
        {
            var setup = MvxAndroidSetupSingleton.EnsureSingletonAvailable(ApplicationContext);
            setup.EnsureInitialized();

            RegisterFragments();
            base.OnCreate(bundle);
        }

        protected abstract void RegisterFragments();
}

This is not perfect because when the app restarts, it freezes during the setup initialization... Do you think of a better solution?

Please note that it seems to be the same problem as issue #1112.

martijn00 commented 8 years ago

The registration will change quite a bit next release: https://github.com/MvvmCross/MvvmCross-AndroidSupport/pull/149

This might be affected by the change. Can you check?

andyci commented 8 years ago

I think the issue is a bit more complex as we potentially have EnsureInitialized being called from multiple places (activities and fragments) which could lead to the code being called twice before it has chance to finish and subsequently crashing at https://github.com/MvvmCross/MvvmCross/blob/71265521e1b57d1e01cca514dfd434de07660440/Cirrious/Cirrious.MvvmCross.Droid/Platform/MvxAndroidSetupSingleton.cs#L39

Not only that but if we want to use IOC, etc. from an Android Service or a BroadcastReceiver then the chance of this occuring only increases. I propose we change EnsureInitialized to allow multiple callers without throwing an exception. The code would basically be the same as that proposed in #955.

Would we accept a pull request for this or are we tackling this as part of #1136?

martijn00 commented 8 years ago

A separate PR for this might be a good idea, as #1136 can take some time before it is finished.

sescandell commented 8 years ago

Hi,

I have the same issue as described here and in #1112 : application crashes on start after been recycled by Android.

I tried different solutions about forcing a call to EnsureInitialized(), but nothing works. Application is still crashing.

Do you have any recommendation to fix or workaround this?

Thanks,

Cheesebaron commented 8 years ago

That is a long thread, please include details.

sescandell commented 8 years ago

To make it simple, my issue occurs when the application is closed (using the touch home screen button for example) and user back to the app.

Debug is difficult because we need the application to be garbage collected by Android (so debugger link is lost). A way to force garbage collection is described in #1112 as:

  • In Developer Options on device, check "Don't keep activities" and "Background process limit" to "No background process"
  • Run the app.
  • Press home button on device.
  • Return to the app.

I'm not sure about that, ,but it seems that ViewModel is not available when OnCreateView is called. Does in this situation ViewModel load differs from "classic" loading (through a call to ShowViewModel)?

Many thanks,

I'll try to make a sample app to isolate behavior

petermajor commented 8 years ago

I've experienced the same thing on our app, but the issue went away after upgrading to 4.1.4

@sescandell what version of MvvmCross are you using?

I think there existed an edge case where Mvx.Resolve was called before EnsureSetupInitialized was called.

I was trying to fix this bug by hacking around the lifecycle with EnsureInitialized calls, but then 4.1.4 was released, I upgraded, and the issue hasn't occurred since...

My stack trace from Google Place looks exactly like the stack trace in #1112:

java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    ... 1 more
Caused by: md52ce486a14f4bcd95899665e9d932190b.JavaProxyThrowable: System.NullReferenceException: Object reference not set to an instance of an object
  at MvvmCross.Platform.Mvx.Resolve[TService] () [0x00005] in <filename unknown>:0 
  at MvvmCross.Binding.Droid.BindingContext.MvxAndroidBindingContextHelpers.Current[T] () [0x00000] in <filename unknown>:0 
  at MvvmCross.Binding.Droid.BindingContext.MvxAndroidBindingContextHelpers.Current () [0x00000] in <filename unknown>:0 
  at MvvmCross.Binding.Droid.Views.MvxLayoutInflater.Inflate (Int32 resource, Android.Views.ViewGroup root, Boolean attachToRoot) [0x00014] in <filename unknown>:0 
  at Android.Views.LayoutInflater.n_Inflate_ILandroid_view_ViewGroup_Z (IntPtr jnienv, IntPtr native__this, Int32 resource, IntPtr native_root, Boolean attachToRoot) [0x00011] in <filename unknown>:0 
  at (wrapper dynamic-method) System.Object:943757a0-051b-4c2a-ab23-0d28f80c35b6 (intptr,intptr,int,intptr,bool)
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <filename unknown>:0 
  at Android.Runtime.JNIEnv.CallNonvirtualVoidMethod (IntPtr jobject, IntPtr jclass, IntPtr jmethod, Android.Runtime.JValue* parms) [0x0008a] in <filename unknown>:0 
  at Android.Support.V4.App.FragmentActivity.OnCreate (Android.OS.Bundle savedInstanceState) [0x00091] in <filename unknown>:0 
  at MvvmCross.Droid.Support.V7.AppCompat.MvxEventSourceAppCompatActivity.OnCreate (Android.OS.Bundle bundle) [0x0000d] in <filename unknown>:0 
  at Pockit.Mobile.Droid.Views.Dashboard.DashboardView.OnCreate (Android.OS.Bundle bundle) [0x00000] in <filename unknown>:0 
  at Android.Support.V4.App.FragmentActivity.n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) [0x00011] in <filename unknown>:0 
  at (wrapper dynamic-method) System.Object:a288e82b-40ae-4237-a48c-3897c7220b2d (intptr,intptr,intptr)
    at md573776cfc7cf206948ac0dd9a1b21a16c.DashboardView.n_onCreate(Native Method)
    at md573776cfc7cf206948ac0dd9a1b21a16c.DashboardView.onCreate(DashboardView.java:28)
    at android.app.Activity.performCreate(Activity.java:6251)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
    at android.app.ActivityThread.-wrap11(ActivityThread.java)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:148)
    at android.app.ActivityThread.main(ActivityThread.java:5422)
Toine-db commented 7 years ago

I've the same issue for a while now, but I'm not sure if this is a Mvx issue or maybe a Xamarin or Android issue.

It comes down to "MvxCachingFragmentCompatActivity" crashes during resume when the app is collected (or maybe in the middle of being collected). Strangly it does not happen when I enable "Do not keep activities" but only when I open a lot of other apps and later return to my App.

PS: setup.EnsureInitialized() does not work, it seems it doesn't even start the constructor of MainView

my stacktrace

Shutting down VM FATAL EXCEPTION: main Process: XXX.OnsiteIssuer, PID: 2975 java.lang.RuntimeException: java.lang.reflect.InvocationTargetException at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)

Caused by: java.lang.reflect.InvocationTargetException at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404) ... 1 more

Caused by: android.runtime.JavaProxyThrowable: System.NotSupportedException: Could not activate JNI Handle 0xbe9e89a8 (key_handle 0x2618fdc0) of Java type 'XXX/MainView' as managed type 'MobileVision.Droid.Views.MainView'. ---> System.NullReferenceException: Object reference not set to an instance of an object at MvvmCross.Platform.Mvx.Resolve[TService] () [0x00005] in <4ddde23419c5494288c799fcdbb0f189>:0 at MobileVision.Droid.Views.MainView..ctor () [0x00017] in <23f6ce84ef1c4249af33145ecdae1046>:0 at (wrapper dynamic-method) System.Object:ba01a382-86c0-48a1-b3ec-8af5760fe8a0 (intptr,object[]) at Java.Interop.TypeManager.n_Activate (System.IntPtr jnienv, System.IntPtr jclass, System.IntPtr typename_ptr, System.IntPtr signature_ptr, System.IntPtr jobject, System.IntPtr parameters_ptr) [0x000fb] in <016b073b6e4e4c3cb2868e007e04c12e>:0 --- End of inner exception stack trace --- at Java.Interop.TypeManager.n_Activate (System.IntPtr jnienv, System.IntPtr jclass, System.IntPtr typename_ptr, System.IntPtr signature_ptr, System.IntPtr jobject, System.IntPtr parameters_ptr) [0x00189] in <016b073b6e4e4c3cb2868e007e04c12e>:0 at (wrapper dynamic-method) System.Object:4d7bf158-05a2-4aab-831d-f9bcfa31c0a7 (intptr,intptr,intptr,intptr,intptr,intptr) at mono.android.TypeManager.n_activate(Native Method) at mono.android.TypeManager.Activate(TypeManager.java:7) at XXX.MainView.(MainView.java:28) at java.lang.reflect.Constructor.newInstance(Native Method) at java.lang.Class.newInstance(Class.java:1690) at android.app.Instrumentation.newActivity(Instrumentation.java:1094) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2962) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3243) at android.app.ActivityThread.access$1000(ActivityThread.java:218) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1718) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6914) ... 4 more

Toine-db commented 7 years ago

Found the problem in my code, it was a field in the main activity that was initialized with Mvx.Resolve. The fields was created in the middle of constructing just before the body of the MvxCachingFragmentCompatActivity, so crashed because in the body is Mvx ensured.

So my app works now, but when recovering from the GC it shows a white screen for a few second instead of a splashscreen. Would be great to change that.

PS: it would be great if MVVMCross tools have much more logging, like when using Mvx.Resolve.