应用程序在启动时崩溃,原因是android.content.Context.getString中的NPE。

19
我们遇到了一个非常奇怪的崩溃,指向系统类。它出现在应用程序启动时。
致命异常:java.lang.RuntimeException: 无法启动活动 ComponentInfo {com.myapp.android / com.myapp.android.main.BaseMainActivity}: java.lang.RuntimeException:无法创建应用程序 com.myapp.android.main.MyApp:java.lang.NullPointerException 在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2377)处 在android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429)处 在android.app.ActivityThread.access $ 800(ActivityThread.java:151)处 在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1342)处 在android.os.Handler.dispatchMessage(Handler.java:110)处 在android.os.Looper.loop(Looper.java:193)处 在android.app.ActivityThread.main(ActivityThread.java:5333)处 在java.lang.reflect.Method.invokeNative(Method.java)处 在java.lang.reflect.Method.invoke(Method.java:515)处 在com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:828)处 在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 在android.app.LoadedApk.makeApplication(LoadedApk.java:529)处 在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292)处 在android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429)处 在android.app.ActivityThread.access $ 800(ActivityThread.java:151)处 在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1342)处 在android.os.Handler.dispatchMessage(Handler.java:110)处 在android.os.Looper.loop(Looper.java:193)处 在android.app.ActivityThread.main(ActivityThread.java:5333)处 在java.lang.reflect.Method.invokeNative(Method.java)处 在java.lang.reflect.Method.invoke(Method.java:515)处 在com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:828)处 在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:644)处 在dalvik.system.NativeStart.main(NativeStart.java)处,由java.lang.NullPointerException引起 在android.content.Context.getString(Context.java:343)处 在com.myapp.android.api.singletons.AppTrackingInstance.initAdjust(AppTrackingInstance.java:114)处 在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)处 在com.myapp.android.injection.modules.ApplicationScopeModule $$ ModuleAdapter $ ProvideAppTrackingInstanceProvidesAdapter.get(ApplicationScopeModule $$ ModuleAdapter.java:1552)处 在dagger.internal.Linker $ SingletonBinding.get(Linker.java:364)处 在com.myapp.android.main.MyApp $$ InjectAdapter.injectMembers(MyApp $$ InjectAdapter.java:70)处 在com.myapp.android.main.MyApp $$ InjectAdapter.injectMembers(MyApp $$ InjectAdapter.java:23)处 在dagger.ObjectGraph $ DaggerObjectGraph.inject(ObjectGraph.java:281)处 在com.myapp.android.main.MyApp $ 1.run(MyApp.java:57)处 在com.myapp.android.main.MyApp.onCreate(MyApp.java:51)处 在android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1007)处 在android.app.LoadedApk.makeApplication(LoadedApk.java:526)处 在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292)处 在android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429)处 在android.app.ActivityThread.access $ 800(ActivityThread.java:151)处 在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1342)处 在android.os.Handler.dispatchMessage(Handler.java:110)处 在android.os.Looper.loop(Looper.java:193)处 在android.app 我们使用 Dagger 1,我们的应用程序使用了 multidexDagger 模块:
@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);
  }
}

从实现和堆栈跟踪中,我们得到了崩溃原因:

由 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

由于应用程序启动时崩溃,我进行了大量调查,并发现了相似的问题(12应用程序更新时崩溃)。

然后我拿了一些测试设备 - Nexus 4(Android 5.0.1),三星S3(Android 4.3) - 并尝试重现这个问题:

  • 在有/没有网络连接的情况下打开应用程序
  • 打开/关闭应用程序50次
  • 打开应用程序,从Play市场卸载,再从Play市场安装并重新打开
  • 从不同的深度链接打开应用程序
  • 从移动网站打开应用程序
  • 从Play市场安装应用程序,不要打开它。从深度链接进行冷启动
  • 从推送通知中打开应用程序
  • 使用不同的语言环境打开应用程序
  • 从最近使用列表中打开应用程序
  • 清除应用程序数据并打开
  • 安装旧的生产版本,手动更新到最新的生产版本
  • 安装旧的生产版本,从Play市场更新到最新版本
  • 浏览应用程序XXX分钟,然后从Play市场更新到最新版本

这些测试过程中没有出现任何崩溃,但是崩溃仍然会在用户设备上出现,我不知道为什么会发生。

可能是由于multidexDagger 1导致的,但我不能确定。


这不是一个真正的解决方案(因此是一条注释),但是一个可以改进并修复您的问题的方法是避免使用getString()调用:配置设置不应该放在strings.xml中!如果您有Adjust-Tracking ID,您应该将其作为buildConfigField添加到您的flavors中,并通过BuildConfig.ADJUST_TRACKING_ID或类似命名引用它们。 - WarrenFaith
谢谢,我们在这种情况下使用BuildConfig变量和flavors。 - Veaceslav Gaidarji
你确定 R.string.adjust_variable 已经正确翻译了吗?另外,为了重现崩溃问题,你可以尝试使用其他语言。 - David Medenjak
好的,对于这个字符串没有翻译,因为它在每种语言中都应该是相同的。我尝试了不同的语言,但情况并非如此。 - Veaceslav Gaidarji
2个回答

2
致命异常:java.lang.RuntimeException: 无法启动活动ComponentInfo{.......}。
我曾经遇到过这样的堆栈跟踪,实际上并不像听起来那么可怕。这意味着,在MyApp的onCreate()方法中抛出了异常。
也就是说,你提供给AppTrackingInstance类的context.getResources()为空,导致崩溃。
从我从帖子中了解到的情况来看,getResources()返回null(=>发生崩溃)的原因可能是竞争条件。
由于我也使用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
        }
    }
}

这应该对你有所帮助,因为在第一个Activity(扩展自ActivityBase)的onCreate()期间,资源已经被定义,所以getResources()不应该返回null。

另外还有两个选项:

  • 避免使用Multidex
  • 轮询等待上下文完全加载(尽管一旦getResources()失败,谁知道还会出现什么其他问题,我担心这会导致其他崩溃 - 在我看来)

希望这可以帮到你。


我们使用Runnable,因为它是Multidex应用程序的解决方法。关于上下文 - 是的,它在所有地方都被正确注入了,我不认为单独的setter会解决问题。对象不为空,在Context内部调用getResources()时抛出NPE,这很奇怪。无论如何,感谢您的帮助。 - Veaceslav Gaidarji
@VeaceslavGaidarji 嗯...堆栈跟踪看起来像是上下文对象为空,导致了NPE。无论如何,我建议尝试使用obj.graph的惰性初始化并在那里跟踪应用程序启动。今晚从滑雪场回来后,我会更新我的答案。还要检查Context类中的第343行实际上是做什么的 :-) - Konstantin Loginov
不,context对象本身不为空,其中一些资源为空。 - Veaceslav Gaidarji
@VeaceslavGaidarji 我已经检查了Context类 - 你是完全正确的,问题出在空资源上。对我来说,这看起来更像是一种竞争条件,特别是当它并不总是发生时,正如我从你的帖子中所理解的那样。我已经更新了我的帖子,并删除了不相关的部分。祝好运! - Konstantin Loginov
@VeaceslavGaidarji,由于在onCreate()中调用appTrackingInstance.trackAppLaunch();是必要的,因此我看到的热修复方法是使用定时任务(timer.scheduleAtFixedRate())和通过Handler发送到目标(sendToTarget),检查context.getResources()是否为空,只有在不为空时才初始化一切并停止它。如果您认为这有意义-我可以在答案中添加代码示例。 - Konstantin Loginov
显示剩余3条评论

0
似乎Context对象没有被初始化。错误出现在此调用中:
@Provides
@Singleton
AppTrackingInstance provideAppTrackingInstance(@ForApplication Context context) {
    return new AppTrackingInstance(context);
}

在这个方法中,验证上下文是否为空。我认为问题就在那里。


正如您在上面所看到的,我们已经讨论过了 - 不是空的上下文 - 上下文资源为空。 - Konstantin Loginov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接