Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App crashes on start caused by NPE in android.content.Context.getString

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.

like image 580
Veaceslav Gaidarji Avatar asked Jan 14 '16 09:01

Veaceslav Gaidarji


1 Answers

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.

like image 73
Konstantin Loginov Avatar answered Oct 19 '22 20:10

Konstantin Loginov