31 votes

RuntimeException avec Dagger 2 sur les appareils Android 7.0 et Samsung

Sur mon Google Play de la console, je vois beaucoup les rapports de plantage depuis que j'ai commencé à utiliser la Dague 2, mais seulement sur Android 7.0 et principalement sur les appareils Samsung, certains Huawai et appareils Motorola et quelques rares Xperia appareils:

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 depuis, je n'ai pas touché appareil à portée de main, aussi, il semble que tous les appareils d'un type qui sont touchés, plus comme un hasard échec de démarrage.

De ce que j'ai appris par le biais de la recherche est que le plus probable de l'activité onCreate est appelée avant que l'activité est réellement attaché à une application. Mais je ne peux pas prouver cette affirmation...

Je suis de Google suivant l'architecture du plan de MVP+Dague.

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

public class MainActivity extends DaggerAppCompatActivity {

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

}

Pertinentes de la Dague 2 code:

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

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 nombre d'accidents est trop important pour qu'on l'ignore. Depuis ma Dague 2 utilisation fonctionne parfaitement sur toutes les autres versions d'Android et les périphériques je suppose qu'elle n'est pas causée par la façon dont j'utilise la Dague 2, mais en quelque sorte par certains fournisseurs spécifiques 7.0 implémentations. Si quelqu'un a connu le même problème et trouvé une solution s'il vous plaît, veuillez, s'il vous plaît aidez-moi!

Depuis cette erreur est me rend fou, j'ai roulé une version de test à 100k utilisateurs à essayer de comprendre où tout cela va mal.

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 la Dague de l'activité avec inline AndroidInjection code. Mon raisonnement était que si ce problème ne serait pas résolu par l'utilisation de ApplicationContext au lieu de getApplication() de mon stack trace 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()
  • la levée d'entrées en phase nationale devrait montrer qu' getApplicationContext() retourné null
  • la levée d'une RuntimeException voudrais montrer que l' getApplicationContext() n'est pas ma Demande
  • si aucune exception ne serait jeté le getApplication() ou getApplicationContext() renvoyé ma demande et je n'ai pas de soins de ce qui s'est réellement résolu le problème

Et ici, c'est 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)

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

Sur la base des discussions sur https://github.com/google/dagger/issues/748 j'ai aussi sorti une version de test qui utilise uniquement l' getApplicationContext() au lieu de getApplication() dans tous Poignard composants, sans aucune différence.

Ma balise application de manifester

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

14voto

Denis Knauer Points 840

J'ai enfin trouvé un moyen de résoudre les accidents causés par l'utilisation de Poignard 2 sous Android 7.0 pour mon application. Veuillez noter que cela ne résout pas le problème avec une application personnalisée n'est pas correctement utilisé sous Android 7.0. Dans mon cas, je n'ai pas important logique dans mon Application personnalisée en plus d'avoir Poignard 2 mise en œuvre et donc j'ai juste remplacé l' DaggerApplication en fonction de la mise en œuvre avec l' ApplicationlessInjection ci-dessous.

Problèmes connus

  • Pas d'injection de dépendance dans des classes de l'application (probablement ce n'est pas une bonne idée avec le flipper Android 7.0 OEM implémentations de toute façon)
  • Pas tous Poignard de composants modifiés par moi, je l'ai remplacé seulement DaggerAppCompatActivity, DaggerIntentService et DaggerFragment. Si vous utilisez d'autres composants tels que l' DaggerDialogFragment ou DaggerBroadcastReceiver vous avez besoin pour créer vos propres outils mais je suppose que ça ne devrait pas être trop dur :)

La mise en œuvre

Cesser d'utiliser DaggerApplication. Prolonger votre application personnalisée à nouveau à partir de la norme Application , ou de se débarrasser de l'application personnalisée entièrement. Pour l'injection de dépendance avec la Dague 2 sa n'a plus besoin. Juste étendre par exemple, FixedDaggerAppCompatActivity et vous êtes bon pour aller avec le Poignard 2 DI pour les activités.

Vous remarquerez peut-être que je suis encore en passant le contexte de l'application de l' ApplicationlessInjection.getInstance(). L'injection de dépendance elle-même n'a pas besoin du contexte, mais je veux être en mesure de facilement injecter le contexte de l'application dans mes autres modules et de composants. Et là, je ne se soucient pas si le contexte de l'application est mon Appli personnalisée ou quelques fous d'autres choses à partir d'Android 7.0 tant qu'il s'agit d'un contexte.

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

}

Mon 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 AppModule

@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 que je peux aider quelqu'un d'autre avec mon code. Pour moi, j'ai pu résoudre tous les accidents liés à l'introduction de Poignard 2 et l'étrange Android versions 7.0.

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

0voto

chandrakant sharma Points 1159

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X