RuntimeException avec Dagger 2 sur Android 7.0 et Samsung

sur ma console de jeu Google je vois beaucoup de rapports d'accident depuis que j'ai commencé à utiliser Dagger 2, mais seulement sur Android 7.0 et principalement sur les appareils Samsung, certains appareils Huawai et Motorola et certains appareils Xperia rares:

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)

Je ne peux pas reproduire le problème puisque je n'ai pas de périphérique affecté en main, il semble aussi que tous les périphériques d'un type ne sont pas affectés, plus comme une défaillance de démarrage aléatoire.

D'après ce que j'ai appris par la recherche est que très probablement l'oncréat de l'activité est appelé avant que l'activité soit réellement attachée à une application. Mais je ne peux pas prouver cette déclaration...

je suis le plan d'architecture de Google pour MVP+Dagger.

ma classe D'Application:

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

}

ma classe D'activité principale:

public class MainActivity extends DaggerAppCompatActivity {

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

}

Poignard Pertinent 2 code:

Daggerappcompatactivité: 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

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

je n'ai aucune idée de comment résoudre ce crash, mais le montant de les plantages sont trop importants pour être ignorés. Depuis mon utilisation Dagger 2 fonctionne parfaitement sur toutes les autres versions et appareils Android, je suppose que ce n'est pas causé par la façon dont j'utilise Dagger 2, mais en quelque sorte par certains fournisseurs spécifiques 7.0 implémentations. Si quelqu'un a fait face à la même question et trouvé une solution s'il vous plaît, s'il vous plaît, aidez-moi!

depuis que cette erreur me rend dingue, j'ai déployé une version test pour 100k utilisateurs en essayant de comprendre où tout cela va. mauvais.

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

}

l'activité est basée sur L'activité de Dagger avec le code d'AndroidInjection inlined. Je pensais que si cette question n'était pas résolue en utilisant ApplicationContext au lieu de getApplication() mes traces de pile doit décrire en détail ce qui se passe:

  • si le problème est causé par getApplication() la trace de la pile contiendra injectWithNullApplication() ou injectWithWrongApplication()
  • un NPE jeté montrerait que getApplicationContext() retourné null
  • une RuntimeException lancée voudrais montrer que l' getApplicationContext() n'est pas ma Demande
  • si aucune exception ne serait jeté getApplication() ou getApplicationContext() retourné ma demande et je ne me soucierais pas de ce qui a réellement résolu le problème

Et voici la trace de la pile:

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)

donc la clause if !(application instanceof HasActivityInjector)inject() ne pas rediriger injectWithWrongApplication() mais la même chose si clause a causé la RuntimeException dans injectNow(Application application) sur la même instance D'Application. WTF? J'ai regardé 100 fois à mon le code, mais si j'ai une erreur s'il vous plaît laissez-moi savoir! Sinon, je suppose qu'il se passe des choses vraiment bizarres dans certaines implémentations de fournisseurs de 7.0 qui ne sont peut-être pas réparables...

basé sur les discussions sur https://github.com/google/dagger/issues/748 j'ai aussi déployé une version de test qui n'utilise que getApplicationContext() au lieu de getApplication() dans tous les composants Dagger sans aucune différence.

mon étiquette de demande de manifeste

<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>
27
demandé sur Rann Lifshitz 2017-10-17 10:27:29

2 réponses

finalement j'ai trouvé un moyen de résoudre les accidents causés par L'utilisation de Dagger 2 sous Android 7.0 pour mon application. Veuillez noter que ceci ne résout pas le problème avec une application personnalisée qui n'est pas correctement utilisée sous Android 7.0. Dans mon cas, je n'ai pas eu la logique importante dans mon application personnalisée à part obtenir Dagger 2 mis en œuvre et donc je viens de remplacer le DaggerApplication implémentation basée sur ApplicationlessInjection ci-dessous.

Connu

  • pas d'injection de dépendances dans les classes d'applications personnalisées (CE n'est probablement pas une bonne idée avec les implémentations Android 7.0 OEM de toute façon)
  • tous les composants de Dagger n'ont pas été modifiés par moi, je n'ai remplacé que DaggerAppCompatActivity,DaggerIntentService et DaggerFragment. Si vous utilisez d'autres composants comme des DaggerDialogFragment ou DaggerBroadcastReceiver vous devez créer vos propres outils mais je suppose que cela ne devrait pas être trop difficile :)

mise en oeuvre

Arrêt à l'aide de DaggerApplication. Prolonger votre application personnalisée à nouveau à partir de la norme Application ou se débarrasser entièrement de l'application personnalisée. Pour l'injection de dépendance avec Dagger 2 Il n'est plus nécessaire. Juste étendre par exemple,FixedDaggerAppCompatActivity et vous êtes bon pour aller avec le Poignard 2 DI pour les activités.

vous remarquerez que je passe encore le contexte de l'application à ApplicationlessInjection.getInstance(). Dépendance l'injection elle-même n'a pas besoin du tout du contexte mais je veux pouvoir facilement injecter le contexte de l'application dans mes autres composants et modules. Et là Je ne me soucie pas si le contexte de l'application est mon application personnalisée ou d'autres choses folles D'Android 7.0 aussi longtemps que c'est un cadre.

ApplicationlessInjection

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

}

My 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();

    }

}

Mon Applmodule

@Module
public abstract class AppModule {

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

}

et par souci d'exhaustivité, mon @ApplicationContext annotation

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

J'espère pouvoir aider quelqu'un d'autre avec mon code. Pour moi, je pouvais résoudre tous les accidents liés à l'introduction de Dagger 2 et les versions bizarres Android 7.0.

Si des précisions sont nécessaires laissez-moi savoir!

10
répondu Denis Knauer 2017-10-25 06:49:34

j'ai rencontré le même problème dans mon application et je l'ai résolu en utilisant le code ci-dessous :

Application app = activity.getApplication();
if(app == null) {
     app = (Application)activity.getApplicationContext();
}
0
répondu chandrakant sharma 2017-10-24 05:48:37