【问题标题】:App crashes on start caused by NPE in android.content.Context.getStringandroid.content.Context.getString 中的 NPE 导致应用程序在启动时崩溃
【发布时间】:2016-04-19 13:28:54
【问题描述】:

我们遇到了一个非常奇怪的崩溃,它指向系统类。它出现在应用程序启动时。

致命异常:java.lang.RuntimeException:无法启动活动 组件信息{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.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)

我们使用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);
  }
}

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

由 java.lang.NullPointerException 引起 在 android.content.Context.getString(Context.java:343)

这意味着当用户启动应用程序时,Dagger 注入到AppTrackingInstance“破碎”的应用程序上下文中。怎么可能? 我们广泛使用Dagger,并且这个上下文在很多地方注入都没有问题。仅在某些特定情况下(我无法重现)应用程序在启动时由于上下文中断而崩溃。

崩溃出现在不同的设备和操作系统版本上,主要出现在 4.x 操作系统上,但也很少出现在某些 5.0.2 操作系统版本上:

由于是应用启动时崩溃,我进行了很多调查,发现了非常相似的问题(12app crash on update)。

我使用了一些测试设备 - Nexus 4 (Android 5.0.1)、Samsung S3 (Android 4.3) - 并试图重现该问题:

  • 在有/没有互联网连接的情况下打开应用程序
  • 打开/关闭应用程序的 50 倍
  • 打开应用程序,从 Play Market 卸载,从 Play Market 安装回来并再次打开
  • 从不同的深层链接打开应用程序
  • 从移动网站打开应用程序
  • 从游戏市场安装应用程序,不要打开它。从深层链接冷启动
  • 从推送通知打开应用程序
  • 打开具有不同语言环境的应用程序
  • 从最近打开应用程序
  • 清除应用数据并打开
  • 安装旧的生产版本,手动更新到最新的生产版本
  • 安装旧的生产版本,从 Play Market 更新到最新版本
  • 浏览应用程序 XXX 分钟,然后从 Play Market 更新到最新版本

在此测试期间发生了 0 次崩溃,但崩溃仍然出现在用户设备上,我不知道为什么会发生这种情况。

可能是multidexDagger 1 的原因,但我不能自信地说。

【问题讨论】:

  • 不是一个真正的解决方案(为此发表评论),而是一项改进,可以通过阻止 getString() 调用来解决您的问题:配置设置不应放入 strings.xml!如果您有一个 Adjust-Tracking id,您应该将其作为 buildConfigField 添加到您的风格中,并通过 BuildConfig.ADJUST_TRACKING_ID 或类似命名来引用它们。
  • 谢谢,我们在这种情况下使用BuildConfig 变量和风格。
  • 您确定R.string.adjust_variable 的翻译正确吗?另外,尝试重现崩溃的你应该尝试使用其他语言
  • 好吧,这个字符串没有翻译,因为它在每种语言中都应该是相同的。我试过不同的语言,但不是这样。

标签: android crash dagger android-multidex


【解决方案1】:

致命异常:java.lang.RuntimeException:无法启动活动 组件信息{.......

我曾经有过这样的堆栈跟踪,听起来绝对没有那么可怕。 表示,onCreate() 中的MyApp 中抛出了异常

context.getResources(),你提供给AppTrackingInstance 类是空的,它会导致崩溃。

原因,为什么getResources() 返回null(=> 崩溃)对我来说听起来像竞争条件,尤其是因为它并非每次都会发生(根据我从邮政)。

由于我也在使用 Dagger1 和 MultiDex,而且我没有这个问题,我猜想,可能的解决方案是懒惰地开始初始化 ObjectGraph

这个 sn-p 对我来说就像一个魅力:

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's - 我在应用程序中使用的每个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
        }
    }
}

它应该对您有所帮助,因为在第一个 ActivityActivityBase 的扩展)的 onCreate() 期间,资源肯定已经定义,所以 getResources() 不应该返回 null。

另外两个选项是

  • 避免使用多重索引
  • 请在您的上下文已满时暂时进行轮询(不过,一旦 getResources() 失败 - 谁知道还有什么可能出错,我担心它会导致其他崩溃 - 恕我直言)

希望对你有帮助。

【讨论】:

  • 我们使用 Runnable,因为它是 Multidex 应用程序的解决方法。关于上下文 - 是的,它在任何地方都正确注入,我认为单独的 setter 不会解决问题。对象不为空,上下文中的getResources() 抛出 NPE,这很奇怪。无论如何,感谢您的帮助。
  • @VeaceslavGaidarji hm .. 堆栈跟踪看起来 Context 对象为空,它是 NPE 的原因。无论如何,我建议在那里尝试 obj.graph 和 trackAppLaunch 的延迟初始化。今晚我从滑雪场回来后,我会更新我的答案。并且还要检查 Context 类中的第 343 行实际上是做什么的:-)
  • 不,context 对象本身不是空的,它里面的一些资源是空的。
  • @VeaceslavGaidarji 我已经检查了 Context 类 - 你说得对,问题在于空资源。然后对我来说,这看起来更像是一种竞争条件,尤其是,正如我从您的帖子中了解到的那样,它并非每次都发生。我已经用建议更新了我的帖子,并删除了其中不相关的部分。祝你好运!
  • 好吧,它会起作用的。你是对的,在Activity 创建上下文的那一刻将被完全初始化,我们避免了这个崩溃。但是我们需要在Application.onCreate 中而不是在第一个Activity 中跟踪一些事件。
【解决方案2】:

似乎 Context 对象没有被初始化。此调用出错:

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

验证此方法中的上下文是否为空。我相信问题就在那里。

【讨论】:

  • 正如您在上面看到的,我们已经讨论过了 - 不,上下文 not 为空 - 上下文的资源为空。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-20
  • 2019-03-12
  • 1970-01-01
  • 1970-01-01
  • 2016-09-02
相关资源
最近更新 更多