Подтвердить что ты не робот

Ошибка приложения при запуске, вызванная NPE в android.content.Context.getString

У нас действительно странный крах, который указывает на системные классы. Он появляется при запуске приложения.

Fatal Exception: java.lang.RuntimeException: невозможно запустить активность ComponentInfo {com.myapp.android/com.myapp.android.main.BaseMainActivity}: java.lang.RuntimeException: невозможно создать приложение 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)         на android.os.Handler.dispatchMessage(Handler.java:110)         на android.os.Looper.loop(Looper.java:193)         at android.app.ActivityThread.main(ActivityThread.java:5333)         в java.lang.reflect.Method.invokeNative(Method.java)         в 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)         в dalvik.system.NativeStart.main(NativeStart.java) Вызывается java.lang.RuntimeException: невозможно создать приложение 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)         на android.os.Handler.dispatchMessage(Handler.java:110)         на android.os.Looper.loop(Looper.java:193)         at android.app.ActivityThread.main(ActivityThread.java:5333)         в java.lang.reflect.Method.invokeNative(Method.java)         в 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)         в dalvik.system.NativeStart.main(NativeStart.java) Вызывается 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)         на com.myapp.android.injection.modules.ApplicationScopeModule.provideAppTrackingInstance(ApplicationScopeModule.java:326)         на 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)         на 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)         на com.myapp.android.main.MyApp.onCreate(MyApp.java:51)         at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1007)         в 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)         на android.os.Handler.dispatchMessage(Handler.java:110)         на android.os.Looper.loop(Looper.java:193)         at android.app.ActivityThread.main(ActivityThread.java:5333)         в java.lang.reflect.Method.invokeNative(Method.java)         в java.lang.reflect.Method.invoke(Method.java:515)         at com.android.internal.os.ZygoteInit $MethodAndArgsCaller.run(ZygoteInit.java:828)         в com.android.internal.os.ZygoteInit.Основной (ZygoteInit.java:644)         в dalvik.system.NativeStart.main(NativeStart.java)

Мы используем Dagger 1, наше приложение multidex -ed.

Модуль кинжала:

@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:

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:

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);
  }
}

Из реализации и stacktrace мы получаем причину сбоя:

Вызывается java.lang.NullPointerException at android.content.Context.getString(Context.java:343)

Это означает, что когда пользователь запускает приложение, Dagger вводит в AppTrackingInstance "сломанный" контекст приложения. Как это возможно? Мы широко используем Dagger, и этот контекст вводится во многих местах без проблем. Только в некоторых конкретных случаях (которые я не могу воспроизвести) приложение запускается при запуске из-за разбитого контекста.

Сбой появляется на разных устройствах и версиях ОС, в основном на 4.x ОС, но редко появляется на некоторых версиях ОС 5.0.2: 1 screenshot 2 screenshot 3 screenshot

Поскольку это крах при запуске приложения, я много исследовал его и нашел довольно похожие проблемы (1, 2, сбой приложения при обновлении).

Чем я взял некоторые тестовые устройства - Nexus 4 (Android 5.0.1), Samsung S3 (Android 4.3) - и попытался воспроизвести проблему:

  • открыть приложение с/без подключения к Интернету
  • открыть/закрыть в 50 раз больше, чем приложение
  • открыть приложение, удалить с игрового рынка, установить обратно игровой рынок и снова открыть.
  • открыть приложение из разных деадаптеров
  • открыть приложение с мобильного сайта
  • установить приложение с игрового рынка и не открывать его. Холодный старт от депиптиков
  • открыть приложение из push-уведомлений
  • открыть приложение с разными локалями
  • открыть приложение из recents
  • удалить данные приложения и открыть
  • установить старую сборку, обновить до последней сборки сборки вручную
  • установить старую сборку, обновить до последней из игрового рынка.
  • перемещаться по приложению через XXX минут, а затем обновлять до последней версии с игрового рынка.

0 сбой во время этих тестов, но авария все еще появляется на пользовательских устройствах, и я понятия не имею, почему это происходит.

Возможно, это происходит из-за multidex или Dagger 1, но я не могу сказать с уверенностью.

4b9b3361

Ответ 1

Fatal Exception: java.lang.RuntimeException: невозможно запустить активность ComponentInfo {.......

У меня была такая stacktrace один раз, и это абсолютно не так страшно, при этом звучит. Это означает, что исключение было выбрано в onCreate() из MyApp.

т.е. context.getResources(), вы предоставили классу AppTrackingInstance значение null и это приведет к сбою.

Причина, почему getResources() return null (= > crash happens) для меня звучит как состояние гонки, особенно, поскольку это происходит не каждый раз (из того, что я понял из после).

Так как я также использую Dagger1 и MultiDex, и у меня нет этой проблемы, я могу догадаться, что возможным решением было бы начать инициализировать ObjectGraph лениво.

Этот фрагмент работает как прелесть для меня:

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, который расширяет 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;
        }
    }
}

Затем в ActivityBase - базовом классе для каждого Activity, который я использую в приложении:

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
        }
    }
}

Это должно помочь вам, поскольку во время onCreate() первого Activity (расширение ActivityBase) ресурсы определенно уже определены, поэтому getResources() не должен возвращать null.

Еще две опции:

  • Избегайте Multidex
  • Опрос на данный момент, когда ваш контекст заполнен (хотя, как только getResources() терпит неудачу - кто знает, что еще может быть неправильно, и я боюсь, что это приведет к другим сбоям - imho)

Надеюсь, это поможет.

Ответ 2

Кажется, что объект Context не инициализируется. Ошибка в этом вызове:

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

Проверьте, является ли контекст нулевым в этом методе. Я считаю, что проблема там.