android dagger-2 samsung-mobile android-7.0-nougat

RuntimeException con Dagger 2 en dispositivos Android 7.0 y Samsung



dagger-2 samsung-mobile (2)

Encontré el mismo problema en mi aplicación y lo resolví usando el siguiente código:

Application app = activity.getApplication(); if(app == null) { app = (Application)activity.getApplicationContext(); }

En mi consola Google Play veo muchos informes de fallos desde que comencé a usar Dagger 2, pero solo en Android 7.0 y principalmente en dispositivos Samsung, algunos dispositivos Huawai y Motorola y algunos dispositivos Xperia raros:

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)

No puedo reproducir el problema ya que no tengo ningún dispositivo afectado a la mano, también parece que no todos los dispositivos de un tipo están afectados, más como un error de inicio aleatorio.

De lo que aprendí a través de la investigación es que lo más probable es que se llame a onCreate de la actividad antes de que la actividad se adjunte realmente a una aplicación. Pero no puedo probar esta afirmación ...

Estoy siguiendo el plan de arquitectura de Google para MVP + Dagger.

Mi clase de aplicación:

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

Mi clase MainActivity:

public class MainActivity extends DaggerAppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } }

Código Daga relevante 2:

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

No tengo idea de cómo resolver este bloqueo, pero la cantidad de bloqueos es demasiado importante como para ignorarlo. Dado que mi uso de Dagger 2 funciona perfectamente en todas las demás versiones de dispositivos y dispositivos con Android, asumo que no se debe a la forma en que uso Dagger 2, sino de alguna manera a algunas implementaciones 7.0 específicas del proveedor. Si alguien enfrentó el mismo problema y encontró una solución, por favor, ¡ayúdenme!

Dado que este error me está volviendo loco, presenté una versión de prueba para usuarios de 100k que intentaban entender dónde va todo esto 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); } }

La actividad se basa en la actividad de Dagger con el código de inyección de Android en línea. Mi idea era que si este problema no se resolvía utilizando ApplicationContext en lugar de getApplication() los rastros de mi pila deberían detallar qué está pasando:

  • Si el problema es causado por getApplication() el seguimiento de la pila contendrá injectWithNullApplication() o injectWithWrongApplication()
  • un NPE lanzado mostraría que getApplicationContext() devolvió un valor nulo
  • una getApplicationContext() RuntimeException lanzada mostraría que getApplicationContext() no es mi aplicación
  • si no se lanzara ninguna excepción, getApplication() o getApplicationContext() devolvieron mi solicitud y no me importaría lo que realmente resolviera el problema

Y aquí está el rastro de la pila:

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)

Por lo tanto, la cláusula if !(application instanceof HasActivityInjector) en !(application instanceof HasActivityInjector) inject() no se redireccionó a injectWithWrongApplication() pero la misma cláusula if causó la injectNow(Application application) RuntimeException en injectNow(Application application) en la misma instancia de aplicación. WTF? Parecía 100 veces mi código, pero si tengo un error ahí, ¡por favor avíseme! De lo contrario, creo que hay algunas cosas realmente extrañas que suceden en algunas implementaciones de proveedores de 7.0 que tal vez no sean solucionables ...

Basándome en las discusiones en https://github.com/google/dagger/issues/748 , también getApplicationContext() una versión de prueba que solo usa getApplicationContext() lugar de getApplication() en todos los componentes de Dagger sin ninguna diferencia.

Mi etiqueta de aplicación de manifiesto

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


Finalmente, encontré una manera de resolver los bloqueos causados ​​por el uso de Dagger 2 en Android 7.0 para mi aplicación. Tenga en cuenta que esto no resuelve el problema con una aplicación personalizada que no se utiliza correctamente en Android 7.0 . En mi caso, no tenía una lógica importante en mi aplicación personalizada, además de implementar Dagger 2, por lo que simplemente reemplacé la implementación basada en DaggerApplication con la ApplicationlessInjection continuación.

Problemas conocidos

  • No hay inyección de dependencia en las clases de aplicaciones personalizadas (de todas formas, probablemente no sea una buena idea con las malditas implementaciones de Android 7.0 OEM)
  • No todos los componentes de Dagger fueron modificados por mí, solo DaggerAppCompatActivity , DaggerIntentService y DaggerFragment . Si está utilizando otros componentes como DaggerDialogFragment o DaggerBroadcastReceiver , necesita crear sus propios implementos, pero creo que no debería ser demasiado difícil :)

Implementación

Deja de usar DaggerApplication . Vuelva a extender su aplicación personalizada desde la Application estándar o elimínela por completo. Para la inyección de dependencia con Dagger 2 ya no es necesario. Simplemente extienda, por ejemplo, FixedDaggerAppCompatActivity y FixedDaggerAppCompatActivity para ir con el Dagger 2 DI para actividades.

Puede notar que todavía estoy pasando el contexto de la ApplicationlessInjection.getInstance() a ApplicationlessInjection.getInstance() . La inyección de dependencia en sí no necesita el contexto en absoluto, pero quiero poder inyectar fácilmente el contexto de la aplicación en mis otros componentes y módulos. Y ahí no me importa si el contexto de la aplicación es mi aplicación personalizada o alguna otra cosa loca de Android 7.0, siempre y cuando sea un contexto.

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

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

Mi módulo de aplicación

@Module public abstract class AppModule { @Binds @ApplicationContext abstract Context bindContext(Context applicationContext); }

Y para completar mi anotación @ApplicationContext

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

Espero poder ayudar a alguien más con mi código también. Para mí, pude resolver todos los fallos relacionados con la introducción de Dagger 2 y las extrañas versiones de Android 7.0.

Si se necesita más aclaración, házmelo saber!