Android 7.0和Samsung设备上带有Dagger 2的RuntimeException [英] RuntimeException with Dagger 2 on Android 7.0 and Samsung devices

查看:136
本文介绍了Android 7.0和Samsung设备上带有Dagger 2的RuntimeException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的Google Play控制台上,自从我开始使用Dagger 2以来,我看到了很多崩溃报告,但仅在Android 7.0上,并且主要在三星设备,一些Huawai和Motorola设备以及一些罕见的Xperia设备上:

On my Google Play console I see quite a lot crash reports since I started to use Dagger 2, but only on Android 7.0 and mainly on Samsung devices, some Huawai and Motorola devices and some rare Xperia devices:

java.lang.RuntimeException: 
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2984)
  at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3045)
  at android.app.ActivityThread.-wrap14 (ActivityThread.java)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1642)
  at android.os.Handler.dispatchMessage (Handler.java:102)
  at android.os.Looper.loop (Looper.java:154)
  at android.app.ActivityThread.main (ActivityThread.java:6776)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:1518)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1408)
Caused by: java.lang.RuntimeException: 
  at dagger.android.AndroidInjection.inject (AndroidInjection.java:48)
  at dagger.android.support.DaggerAppCompatActivity.onCreate (DaggerAppCompatActivity.java:43)
  at com.package.MainActivity.onCreate (MainActivity.java:83)
  at android.app.Activity.performCreate (Activity.java:6956)
  at android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1126)
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2927)

由于我手边没有任何受影响的设备,因此无法重现该问题,而且似乎并非所有类型的设备都受到影响,更像是随机启动失败.

I cannot reproduce the issue since I do not have any affected device at hand, also it seems that not all devices of a type are affected, more like a random startup failure.

从研究中我了解到,很有可能在将活动实际附加到应用程序之前调用了活动的onCreate.但我无法证明这一说法...

From what I learned through research is that most likely the activity's onCreate is called before the activity is actually attached to an application. But I cannot prove this statement...

我正在遵循Google针对MVP + Dagger的架构蓝图.

I am following Google's architecture blueprint for MVP+Dagger.

我的应用程序类:

public class App extends DaggerApplication {

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

    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        AppComponent appComponent = DaggerAppComponent.builder().application(this).build();
        appComponent.inject(this);
        return appComponent;
    }

}

我的MainActivity类:

My MainActivity class:

public class MainActivity extends DaggerAppCompatActivity {

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

}

相关Dagger 2代码:

Relevant Dagger 2 code:

DaggerAppCompatActivity: https://github.com/google/dagger/blob/e8d7cd4c29c1316c5bb1cf0737d4f29111fcb1c8/java/dagger/android/support/DaggerAppCompatActivity.java#L42-L45

DaggerAppCompatActivity: https://github.com/google/dagger/blob/e8d7cd4c29c1316c5bb1cf0737d4f29111fcb1c8/java/dagger/android/support/DaggerAppCompatActivity.java#L42-L45

protected void onCreate(@Nullable Bundle savedInstanceState) { 
    AndroidInjection.inject(this); 
    super.onCreate(savedInstanceState); 
}

AndroidInjection: https://github.com/google/dagger/blob/e8d7cd4c29c1316c5bb1cf0737d4f29111fcb1c8/java/dagger/android/AndroidInjection.java#L43-L52

AndroidInjection: https://github.com/google/dagger/blob/e8d7cd4c29c1316c5bb1cf0737d4f29111fcb1c8/java/dagger/android/AndroidInjection.java#L43-L52

public static void inject(Activity activity) { 
    checkNotNull(activity, "activity"); 
    Application application = activity.getApplication(); 
    if (!(application instanceof HasActivityInjector)) { 
        throw new RuntimeException( 
            String.format( 
                "%s does not implement %s", 
                application.getClass().getCanonicalName(), 
                HasActivityInjector.class.getCanonicalName())); 
    }

我不知道如何解决此崩溃问题,但是崩溃的数量太多了,无法忽略.由于Dagger 2的用法在所有其他Android版本和设备上均能完美运行,因此我认为这并非由我使用Dagger 2引起的,而是由某些特定于供应商的7.0实现引起的.如果有人遇到相同的问题并找到了解决方案,请帮助我!

I have no idea how to resolve this crash, but the amount of crashes is too significant to ignore. Since my Dagger 2 usage works perfectly on all other Android versions and devices I assume that it is not caused by the way I use Dagger 2 but somehow by some vendor specific 7.0 implementations. If anybody faced the same issue and found a solution please, please, please help me!

由于这个错误使我发疯,我向10万用户推出了一个测试版本,试图了解整个问题出在哪里.

Since this error is driving me nuts I rolled out a test version to 100k users trying to understand where this whole thing goes wrong.

public abstract class TestDaggerAppCompatActivity extends AppCompatActivity implements HasFragmentInjector, HasSupportFragmentInjector {

    @Inject DispatchingAndroidInjector<Fragment> supportFragmentInjector;
    @Inject DispatchingAndroidInjector<android.app.Fragment> frameworkFragmentInjector;

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

    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return supportFragmentInjector;
    }

    @Override
    public AndroidInjector<android.app.Fragment> fragmentInjector() {
        return frameworkFragmentInjector;
    }

    private void inject() {
        Application application = getApplication();

        if(application == null) {
            injectWithNullApplication();
            return;
        }

        if (!(application instanceof HasActivityInjector)) {
            injectWithWrongApplication();
            return;
        }

        // Everything seems ok...
        injectNow(application);
    }

    private void injectWithNullApplication() {
        Application application = (Application) getApplicationContext();
        injectNow(application);
    }

    private void injectWithWrongApplication() {
        Application application = (Application) getApplicationContext();
        injectNow(application);
    }

    private void injectNow(Application application) {
        checkNotNull(application, "Application must not be null");

        if (!(application instanceof HasActivityInjector)) {
            throw new RuntimeException(String.format("%s does not implement %s", application.getClass().getCanonicalName(), HasActivityInjector.class.getCanonicalName()));
        }

        AndroidInjector<Activity> activityInjector = ((HasActivityInjector) application).activityInjector();
        checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass().getCanonicalName());

        activityInjector.inject(this);
    }

}

该活动基于Dagger的活动以及内联的AndroidInjection代码.我的想法是,如果不能通过使用ApplicationContext而不是getApplication()来解决此问题,则我的堆栈跟踪应详细说明发生的情况:

The activity is based on Dagger's activity with inlined AndroidInjection code. My thinking was that if this issue would not be resolved by using ApplicationContext instead of getApplication() my stack traces should detail whats going on:

  • 如果问题是由getApplication()引起的,则堆栈跟踪将包含injectWithNullApplication()injectWithWrongApplication()
  • 抛出NPE将显示getApplicationContext()返回null
  • 抛出RuntimeException将显示getApplicationContext()不是我的应用程序
  • 如果没有异常,则getApplication()getApplicationContext()都将返回我的应用程序,而我不在乎实际上是什么解决了该问题
  • if the issue is caused by getApplication() the stack trace would contain injectWithNullApplication() or injectWithWrongApplication()
  • a thrown NPE would show that getApplicationContext() returned null
  • a thrown RuntimeException would show that the getApplicationContext() is not my Application
  • if no exception would be thrown either the getApplication() or getApplicationContext() returned my application and I would not care what actually solved the issue

这是堆栈跟踪:

Caused by: java.lang.RuntimeException: 
  at com.package.di.TestDaggerAppCompatActivity.inject (TestDaggerAppCompatActivity.java:49)
  at com.package.di.TestDaggerAppCompatActivity.onCreate (TestDaggerAppCompatActivity.java:31)
  at com.package.MainActivity.onCreate (MainActivity.java:83)
  at android.app.Activity.performCreate (Activity.java:6942)
  at android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1126)
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2880)

因此,inject()中的if子句!(application instanceof HasActivityInjector)不会重新路由到injectWithWrongApplication(),但是相同的if子句在同一应用程序实例上导致了injectNow(Application application)中的RuntimeException. WTF?我在代码中看了100次,但是如果那里有错误,请告诉我!否则,我猜想在7.0的某些Vendor实现中会发生一些不可思议的事情……

So the if clause !(application instanceof HasActivityInjector) in inject() did not reroute to injectWithWrongApplication() but the same if clause caused the RuntimeException in injectNow(Application application) on the same Application instance. WTF? I looked like 100 times at my code but if I have an error in there please let me know! Otherwise, I guess there are some really weird things going on in some Vendor implementations of 7.0 which are maybe not fixable...

基于 https://github.com/google/dagger/issues/748上的讨论我还推出了一个测试版本,该版本在所有Dagger组件中仅使用getApplicationContext()而不是getApplication().

Based on the discussions on https://github.com/google/dagger/issues/748 I also rolled out a test version that only uses getApplicationContext() instead of getApplication() in all Dagger components without any difference.

清单中的我的应用标签

<application
    android:name=".App"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/SplashScreenTheme"
    android:fullBackupContent="false">

    <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
    <meta-data android:name="com.google.android.gms.games.APP_ID" android:value="@string/app_id" />

    <meta-data android:name="android.max_aspect" android:value="2.1" />

    <activity
        android:name="com.package.MainActivity"
        android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service android:name="com.package.GeneratorService" android:exported="false"/>
</application>

推荐答案

最后,我找到了一种解决因我的应用程序在Android 7.0下使用Dagger 2导致崩溃的方法.请注意,这无法解决自定义应用程序未在Android 7.0下正确使用的问题.就我而言,除了实现Dagger 2之外,我的自定义应用程序中没有重要的逻辑,所以我只是将基于DaggerApplication的实现替换为下面的ApplicationlessInjection.

Finally I found a way to resolve the crashes caused by using Dagger 2 under Android 7.0 for my application. Please note that this does not resolve the issue with a custom application not being properly used under Android 7.0. In my case I did not have important logic in my custom Application besides getting Dagger 2 implemented and so I just replaced the DaggerApplication based implementation with the ApplicationlessInjection below.

已知问题

  • 在自定义应用程序类中不进行依赖注入(无论如何,对于怪异的Android 7.0 OEM实现来说,这不是一个好主意)
  • 不是我修改过的所有Dagger组件,我只替换了DaggerAppCompatActivityDaggerIntentServiceDaggerFragment.如果您使用的是DaggerDialogFragmentDaggerBroadcastReceiver之类的其他组件,则需要创建自己的工具,但我想这应该不会太难:)
  • No dependency injection in custom application classes (probably this isn't a good idea with the freaking Android 7.0 OEM implementations anyway)
  • Not all Dagger components where modified by me, I replaced only DaggerAppCompatActivity, DaggerIntentService and DaggerFragment. If you are using other components like DaggerDialogFragment or DaggerBroadcastReceiver you need to create your own implements but I guess that should not be too hard :)

实施

使用DaggerApplication停止.从标准Application再次扩展您的自定义应用程序,或者完全摆脱该自定义应用程序.对于使用Dagger 2进行依赖注入,不再需要它.只需扩展例如FixedDaggerAppCompatActivity,您很高兴与Dagger 2 DI一起进行活动.

Stop using DaggerApplication. Either extend your custom application again from the standard Application or get rid of the custom application entirely. For the dependency injection with Dagger 2 its not needed anymore. Just extend e.g. FixedDaggerAppCompatActivity and you are good to go with the Dagger 2 DI for activities.

您可能会注意到,我仍在将应用程序上下文传递给ApplicationlessInjection.getInstance().依赖项注入本身根本不需要上下文,但是我希望能够轻松地将应用程序上下文注入到我的其他组件和模块中.而且,我不在乎应用程序上下文是我的自定义应用程序还是Android 7.0中的其他疯狂东西,只要它是上下文即可.

You may notice that I am still passing the application context to the ApplicationlessInjection.getInstance(). The dependency injection itself does not need the context at all but I want to be able to easily inject the application context into my other components and modules. And there I do not care if the application context is my custom App or some crazy other stuff from Android 7.0 as long as it is a context.

无应用程序注入

public class ApplicationlessInjection
        implements
            HasActivityInjector,
            HasFragmentInjector,
            HasSupportFragmentInjector,
            HasServiceInjector,
            HasBroadcastReceiverInjector,
            HasContentProviderInjector {

    private static ApplicationlessInjection instance = null;

    @Inject DispatchingAndroidInjector<Activity> activityInjector;
    @Inject DispatchingAndroidInjector<BroadcastReceiver> broadcastReceiverInjector;
    @Inject DispatchingAndroidInjector<android.app.Fragment> fragmentInjector;
    @Inject DispatchingAndroidInjector<Fragment> supportFragmentInjector;
    @Inject DispatchingAndroidInjector<Service> serviceInjector;
    @Inject DispatchingAndroidInjector<ContentProvider> contentProviderInjector;

    public ApplicationlessInjection(Context applicationContext) {
        AppComponent appComponent = DaggerAppComponent.builder().context(applicationContext).build();
        appComponent.inject(this);
    }

    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
        return activityInjector;
    }

    @Override
    public DispatchingAndroidInjector<android.app.Fragment> fragmentInjector() {
        return fragmentInjector;
    }

    @Override
    public DispatchingAndroidInjector<Fragment> supportFragmentInjector() {
        return supportFragmentInjector;
    }

    @Override
    public DispatchingAndroidInjector<BroadcastReceiver> broadcastReceiverInjector() {
        return broadcastReceiverInjector;
    }

    @Override
    public DispatchingAndroidInjector<Service> serviceInjector() {
        return serviceInjector;
    }

    @Override
    public AndroidInjector<ContentProvider> contentProviderInjector() {
        return contentProviderInjector;
    }

    public static ApplicationlessInjection getInstance(Context applicationContext) {
        if(instance == null) {
            synchronized(ApplicationlessInjection.class) {
                if (instance == null) {
                    instance = new ApplicationlessInjection(applicationContext);
                }
            }
        }

        return instance;
    }

}

FixedDaggerAppCompatActivity

public abstract class FixedDaggerAppCompatActivity extends AppCompatActivity implements HasFragmentInjector, HasSupportFragmentInjector {

    @Inject DispatchingAndroidInjector<Fragment> supportFragmentInjector;
    @Inject DispatchingAndroidInjector<android.app.Fragment> frameworkFragmentInjector;

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

    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return supportFragmentInjector;
    }

    @Override
    public AndroidInjector<android.app.Fragment> fragmentInjector() {
        return frameworkFragmentInjector;
    }

    private void inject() {
        ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getApplicationContext());

        AndroidInjector<Activity> activityInjector = injection.activityInjector();

        if (activityInjector == null) {
            throw new NullPointerException("ApplicationlessInjection.activityInjector() returned null");
        }

        activityInjector.inject(this);
    }

}

FixedDaggerFragment

public abstract class FixedDaggerFragment extends Fragment implements HasSupportFragmentInjector {

    @Inject DispatchingAndroidInjector<Fragment> childFragmentInjector;

    @Override
    public void onAttach(Context context) {
        inject();
        super.onAttach(context);
    }

    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return childFragmentInjector;
    }


    public void inject() {
        HasSupportFragmentInjector hasSupportFragmentInjector = findHasFragmentInjector();

        AndroidInjector<Fragment> fragmentInjector = hasSupportFragmentInjector.supportFragmentInjector();

        if (fragmentInjector == null) {
            throw new NullPointerException(String.format("%s.supportFragmentInjector() returned null", hasSupportFragmentInjector.getClass().getCanonicalName()));
        }

        fragmentInjector.inject(this);
    }

    private HasSupportFragmentInjector findHasFragmentInjector() {
        Fragment parentFragment = this;

        while ((parentFragment = parentFragment.getParentFragment()) != null) {
            if (parentFragment instanceof HasSupportFragmentInjector) {
                return (HasSupportFragmentInjector) parentFragment;
            }
        }

        Activity activity = getActivity();

        if (activity instanceof HasSupportFragmentInjector) {
            return (HasSupportFragmentInjector) activity;
        }

        ApplicationlessInjection injection = ApplicationlessInjection.getInstance(activity.getApplicationContext());
        if (injection != null) {
            return injection;
        }

        throw new IllegalArgumentException(String.format("No injector was found for %s", getClass().getCanonicalName()));
    }

}

FixedDaggerIntentService

public abstract class FixedDaggerIntentService extends IntentService {

    public FixedDaggerIntentService(String name) {
        super(name);
    }

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

    private void inject() {
        ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getApplicationContext());

        AndroidInjector<Service> serviceInjector = injection.serviceInjector();

        if (serviceInjector == null) {
            throw new NullPointerException("ApplicationlessInjection.serviceInjector() returned null");
        }

        serviceInjector.inject(this);
    }

}

我的AppComponent

@Singleton
@Component(modules = {
        AppModule.class,
        ActivityBindingModule.class,
        AndroidSupportInjectionModule.class
})
public interface AppComponent extends AndroidInjector<ApplicationlessInjection> {

    @Override
    void inject(ApplicationlessInjection instance);

    @Component.Builder
    interface Builder {

        @BindsInstance
        AppComponent.Builder context(Context applicationContext);

        AppComponent build();

    }

}

我的AppModule

@Module
public abstract class AppModule {

    @Binds
    @ApplicationContext
    abstract Context bindContext(Context applicationContext);

}

为了完整起见,我的@ApplicationContext批注

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationContext {}


希望我也可以通过我的代码帮助其他人.对我来说,我可以解决所有与介绍Dagger 2和怪异的Android 7.0版本相关的崩溃.


Hopefully I can help someone else with my code as well. For me I could resolve all crashes related to introducing Dagger 2 and the weird Android 7.0 versions.

如果需要更多说明,请告诉我!

If more clarification is needed just let me know!

这篇关于Android 7.0和Samsung设备上带有Dagger 2的RuntimeException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆