19

We have a really weird crash, which points to the system classes. It appears on application start.

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.myapp.android/com.myapp.android.main.BaseMainActivity}: java.lang.RuntimeException: Unable to create application com.myapp.android.main.MyApp: java.lang.NullPointerException at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2377) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429) at android.app.ActivityThread.access$800(ActivityThread.java:151) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1342) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:5333) at java.lang.reflect.Method.invokeNative(Method.java) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:828) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:644) at dalvik.system.NativeStart.main(NativeStart.java) Caused by java.lang.RuntimeException: Unable to create application com.myapp.android.main.MyApp: java.lang.NullPointerException at android.app.LoadedApk.makeApplication(LoadedApk.java:529) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429) at android.app.ActivityThread.access$800(ActivityThread.java:151) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1342) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:5333) at java.lang.reflect.Method.invokeNative(Method.java) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:828) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:644) at dalvik.system.NativeStart.main(NativeStart.java) Caused by java.lang.NullPointerException at android.content.Context.getString(Context.java:343) at com.myapp.android.api.singletons.AppTrackingInstance.initAdjust(AppTrackingInstance.java:114) at com.myapp.android.api.singletons.AppTrackingInstance.(AppTrackingInstance.java:92) at com.myapp.android.injection.modules.ApplicationScopeModule.provideAppTrackingInstance(ApplicationScopeModule.java:326) at com.myapp.android.injection.modules.ApplicationScopeModule$$ModuleAdapter$ProvideAppTrackingInstanceProvidesAdapter.get(ApplicationScopeModule$$ModuleAdapter.java:1618) at com.myapp.android.injection.modules.ApplicationScopeModule$$ModuleAdapter$ProvideAppTrackingInstanceProvidesAdapter.get(ApplicationScopeModule$$ModuleAdapter.java:1552) at dagger.internal.Linker$SingletonBinding.get(Linker.java:364) at com.myapp.android.main.MyApp$$InjectAdapter.injectMembers(MyApp$$InjectAdapter.java:70) at com.myapp.android.main.MyApp$$InjectAdapter.injectMembers(MyApp$$InjectAdapter.java:23) at dagger.ObjectGraph$DaggerObjectGraph.inject(ObjectGraph.java:281) at com.myapp.android.main.MyApp$1.run(MyApp.java:57) at com.myapp.android.main.MyApp.onCreate(MyApp.java:51) at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1007) at android.app.LoadedApk.makeApplication(LoadedApk.java:526) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429) at android.app.ActivityThread.access$800(ActivityThread.java:151) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1342) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:5333) at java.lang.reflect.Method.invokeNative(Method.java) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:828) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:644) at dalvik.system.NativeStart.main(NativeStart.java)

We use Dagger 1, our application is multidex-ed.

Dagger module:

@Module(
  library = true,
  injects = {
    MyApp.class
  }
)

public class ApplicationScopeModule {
  private final MyApp application;

  public ApplicationScopeModule(MyApp application) {
    this.application = application;
  }

  @Provides
  @Singleton
  @ForApplication
  Context provideApplicationContext() {
    return application.getApplicationContext();
  }

  @Provides
  @Singleton
  AppTrackingInstance provideAppTrackingInstance(@ForApplication Context context) {
    return new AppTrackingInstance(context);
  }
}

MyApp class:

package com.myapp.android.main;

public class MyApp extends MultiDexApplication {
  private ObjectGraph objectGraph;

  @Inject
  AppTrackingInstance appTrackingInstance;

  @Override
  public void onCreate() {
    super.onCreate();
    // workaround for multi-dex enabled projects
    // taken from http://frogermcs.github.io/MultiDex-solution-for-64k-limit-in-Dalvik/
    // multi-dex separates dex files, and some classes going to additional dex file.
    // Additional .dex files are loaded in Application.attachBaseContext(Context) method
    // (by MultiDex.install(Context) invokation). It means, that before this moment
    // we can’t use classes from them. So i.e. we cannot declare static fields
    // with types attached out of main .dex file.
    // Otherwise we’ll get java.lang.NoClassDefFoundError.
    //
    // the issue should be fixed on the Android level
    //
    new Runnable() {
      @Override
      public void run() {
        initFabric();
        objectGraph = ObjectGraph.create(getModules().toArray());
        objectGraph.inject(MyApp.this);
        appTrackingInstance.trackAppLaunch();
      }
    }.run();
  }

  private void initFabric() {
    Fabric.with(MyApp.this, new Crashlytics.Builder().core(new CrashlyticsCore.Builder().disabled(BuildConfig.IS_DEBUG_BUILD).build()).build());
  }

  public List<Object> getModules() {
    return Arrays.<Object>asList(new ApplicationScopeModule(this));
  }

  public ObjectGraph getObjectGraph() {
    return objectGraph;
  }
}

AppTrackingInstance class:

package com.myapp.android.api.singletons;

public class AppTrackingInstance {
  Context context;
  public AppTrackingInstance(Context context) {
    this.context = context;
    initAdjust();
  }

  private void initAdjust() {
    // "broken" context here
    String variable = context.getString(R.string.adjust_variable);
  }
}

From the implementation and stacktrace we get the crash cause:

Caused by java.lang.NullPointerException at android.content.Context.getString(Context.java:343)

It means that when the user starts the application, Dagger injects into AppTrackingInstance "broken" application context. How it can be possible? We use Dagger widely, and this context injected in many places without problems. Only in some specific cases (which I can't reproduce) app crashes on start due to broken context.

Crash appears on different devices and OS versions, mostly on 4.x OS, but rarely appears on some 5.0.2 OS versions too: 1 screenshot 2 screenshot 3 screenshot

Since it's a crash on app start, I've investigated it a lot and found quite similar problems (1, 2, app crash on update).

Than I took some test devices - Nexus 4 (Android 5.0.1), Samsung S3 (Android 4.3) - and tried to reproduce the issue:

  • open application with/without internet connection
  • open/close 50x times the application
  • open application, uninstall from play market, install back form play market and open again
  • open application from different deeplinks
  • open application from mobile website
  • install application from play market and don't open it. Cold start from deeplinks
  • open application from push notifications
  • open application with different locales
  • open application from recents
  • clear app data and open
  • install old production build, update to the latest production build manually
  • install old production build, update to the latest one from play market
  • navigate through the application XXX minutes, then update to the latest version from play market

0 crashes during this tests, but the crash still appears on users devices, and I have no idea why it's happening.

Probably, it happens cause of multidex or Dagger 1, but I can't say with confidence.

Community
  • 1
  • 1
Veaceslav Gaidarji
  • 4,261
  • 3
  • 38
  • 61
  • Not a real solution (therefor a comment) but an improvement which would fix your issue by preventing the getString() call: Configuration settings should not be put into strings.xml! If you have an Adjust-Tracking id, you should add that as buildConfigField to your flavors and reference them via BuildConfig.ADJUST_TRACKING_ID or similar naming. – WarrenFaith Jan 14 '16 at 09:38
  • thanks, we use `BuildConfig` variables and flavors for such cases. – Veaceslav Gaidarji Jan 14 '16 at 09:39
  • Did you make sure that `R.string.adjust_variable` did get translated correctly? Also, trying to reproduce the crashed you should try using other languages – David Medenjak Jan 19 '16 at 12:24
  • well, there is no translation for this string, as it should be the same in every language. I've tried different languages, it's not the case. – Veaceslav Gaidarji Jan 19 '16 at 13:36

2 Answers2

2

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{.......

I had such stacktrace once and it's absolutely not as scary, at it sounds. It means, exception has been thrown in onCreate() of MyApp.

I.e. context.getResources(), you provided to the AppTrackingInstance class is null and it's causing crash.

The reason, why getResources() return null (=> crash happens) for me sounds like a race condition, especially, as it happens not every time (from what I understood from the post).

Since I'm also using Dagger1 and MultiDex and I don't have this issue, I can guess, that possible solution would be to start initialize ObjectGraph lazily.

This snippet works like a charm for me:

public final class ApplicationScopeModule {

    private final Context applicationContext;

    public ApplicationScopeModule(final Context applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Provides
    @Singleton
    @SuppressWarnings("unused") // invoked by Dagger
    public Context provideApplicationContext() {
        return applicationContext;
    }

    @Provides
    @Singleton
    @SuppressWarnings("unused") // invoked by Dagger
    public Analytics provideAnalytics(Context context) {
        return new DefaultAnalytics(context);
    }

    //...<other providers>..
}

MyApplication, which extends Application:

public class MyApplication extends Application {

    private ObjectGraph objectGraph;

    private final Object lock = new Object();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    protected List<Object> getModules() {
        final ArrayList<Object> modules = new ArrayList<>();
        modules.add(new ApplicationScopeModule(getApplicationContext()));
        return modules;
    }

    public ObjectGraph getApplicationGraph() {
        synchronized (lock) {
            if (objectGraph == null) {
                objectGraph = ObjectGraph.create(getModules().toArray());
            }

            return objectGraph;
        }
    }
}

Then in the ActivityBase's - the base class for every Activity I'm using in app:

public abstract class FragmentActivityBase extends ActionBarActivity {

    private ObjectGraph activityGraph;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        inject(this);
        super.onCreate(savedInstanceState);
    }

    public void inject(final Object object) {
        try {
            if (activityGraph == null) {
                final MyApplication application = (MyApplication) getApplication();
                activityGraph = application.getApplicationGraph();
            }

            activityGraph.inject(object);
        } catch (IllegalArgumentException e) {
            //log error
        }
    }
}

It should help you, since during onCreate() of the first Activity(extension of ActivityBase), resources are definitely already defined, so getResources() shouldn't return null.

Another two options are

  • Avoid Multidex
  • Poll for the moment, when your context is full (though, once getResources() failing - who knows what else could be wrong and I'm afraid it will lead to other crashes - imho)

I hope, it helps.

Konstantin Loginov
  • 15,802
  • 5
  • 58
  • 95
  • we use `Runnable`, as it's a workaround for Multidex apps. About context - yes, it's injected correctly everywhere, and I don't think separate setter will fix the problem. Object isn't null, `getResources()` inside Context throw an NPE, that's the weird thing. Thanks for help, anyway. – Veaceslav Gaidarji Jan 18 '16 at 08:13
  • @VeaceslavGaidarji hm.. The stacktrace looks like the Context object is null and it's cause NPE . Anyway, I suggest to try Lazy initialization of the obj.graph and trackAppLaunch there. I'll update my answer, once I'm back from ski slopes tonight. And also check that's 343rd line in Context class actually do :-) – Konstantin Loginov Jan 18 '16 at 11:30
  • no, `context` object itself isn't null, some resources inside it are null. – Veaceslav Gaidarji Jan 18 '16 at 12:14
  • @VeaceslavGaidarji I've checked Context class - you're absolutely right, the issue is with null-resources. Then for me it looks like a race condition even more, especially, as soon, as it happens not every time, as I understood from your post. I've updated my post with suggestion and removed irrelevant part of it. Good luck! – Konstantin Loginov Jan 18 '16 at 15:53
  • Well, it will work. You're right, that at the moment of `Activity` creation context will be fully initialized and we avoid this crash. But we need to track some events in the `Application.onCreate` and not in the first `Activity`. – Veaceslav Gaidarji Jan 19 '16 at 08:45
  • @VeaceslavGaidarji once it's essential to call `appTrackingInstance.trackAppLaunch(); ` in `onCreate()`, the hotfix I see is recurring task (`timer.scheduleAtFixedRate()` with `sendToTarget` via Handler), which checks if `context.getResources()` is null or not and only then initialize everything and stop it. If you think it makes sense - I can add code-example to the answer. – Konstantin Loginov Jan 19 '16 at 15:31
  • It's all about workarounds, and we try to avoid some weird crashes coming from Android platform. Would be best to have a non-tricky solution, or link to documentation which says that I can't use context in `Application` class. Thanks for help anyway, I'll keep you updated. – Veaceslav Gaidarji Jan 19 '16 at 16:32
  • @VeaceslavGaidarji heh.. I'd say, that your initial `new Runnable()` is already hacky enough and showing some weirdness in Android platform :-) So pretty much all options you have - to avoid multidex, to initialize everything later or polling for the moment, when your context is full (though, once `getResources()` failing - who knows what else could be wrong and I'm afraid it will lead to other crashes - imho). Anyway, it's a great question and it'd be nice to hear, if anyone else have any bright idea, besides the ones I mentioned here. – Konstantin Loginov Jan 19 '16 at 16:55
0

It seems that Context object is not being initialized. Error is in this call:

@Provides
@Singleton
AppTrackingInstance provideAppTrackingInstance(@ForApplication Context context) {
    return new AppTrackingInstance(context);
}

Verify if context is null in this method. I believe that problem is there.