Подтвердить что ты не робот

RuntimeException с Dagger 2 на устройствах Android 7.0 и Samsung

На моей консоли Google Play я вижу довольно много отчетов о сбоях, так как я начал использовать Dagger 2, но только на Android 7.0 и в основном на устройствах Samsung, некоторых устройствах Huawai и Motorola и некоторых редких устройствах Xperia:

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)

Я не могу воспроизвести проблему, так как у меня нет никакого затронутого устройства под рукой, также кажется, что затронуты не все устройства типа, более похожие на случайный сбой при запуске.

Из того, что я узнал в ходе исследований, является то, что, скорее всего, активность onCreate вызывается до того, как действие действительно привязано к приложению. Но я не могу доказать это утверждение...

Я слежу за планом архитектуры Google для 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:

public class MainActivity extends DaggerAppCompatActivity {

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

}

Код релевантного кинжала 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())); 
    }

Я не знаю, как разрешить этот сбой, но количество сбоев слишком значимо, чтобы игнорировать. Поскольку мое использование Dagger 2 отлично работает на всех других версиях и устройствах Android, я предполагаю, что это не связано с тем, как я использую Dagger 2, но каким-то образом с некоторыми версиями 7.0 для определенных поставщиков. Если кто-то столкнулся с той же проблемой и нашел решение, пожалуйста, пожалуйста, помогите мне!

UPDATE

Поскольку эта ошибка приводит меня в бешенство, я выложил тестовую версию для пользователей 100 тыс., пытающихся понять, где все это происходит не так.

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

}

Активность основана на активности кинжала с помощью встроенного кода AndroidInjection. Я думал, что если эта проблема не будет решена с помощью ApplicationContext, а не getApplication(), мои трассировки стека должны детализировать, что происходит:

  • если проблема вызвана getApplication(), трассировка стека будет содержать injectWithNullApplication() или injectWithWrongApplication()
  • брошенный NPE показал бы, что getApplicationContext() возвращает null
  • a throw RuntimeException показывает, что getApplicationContext() не является моим приложением
  • Если исключение не будет выбрано либо getApplication(), либо getApplicationContext() вернули мое приложение, и мне было бы все равно, что на самом деле решило проблему.

И вот трассировка стека:

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)

Итак, предложение if !(application instanceof HasActivityInjector) в inject() не перенаправлялось на injectWithWrongApplication(), но одно и то же предложение if вызвало RuntimeException в injectNow(Application application) в том же экземпляре приложения. WTF? Я выглядел как 100 раз в моем коде, но если у меня есть ошибка, пожалуйста, дайте мне знать! В противном случае, я думаю, есть некоторые действительно странные мысли, которые происходят в некоторых реализациях Vendor 7.0, которые, возможно, не могут быть исправлены...

ОБНОВЛЕНИЕ 2

Основываясь на обсуждениях https://github.com/google/dagger/issues/748, я также выкатил тестовую версию, которая использует getApplicationContext() вместо getApplication() в все компоненты Dagger без какой-либо разницы.

ОБНОВЛЕНИЕ 3 - мой тег приложения из манифеста

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

Ответ 1

Наконец, я нашел способ разрешить сбои, вызванные использованием Dagger 2 под Android 7.0 для моего приложения. Обратите внимание: это не устраняет проблему, когда пользовательское приложение не используется должным образом в Android 7.0. В моем случае у меня не было важной логики в моем настраиваемом приложении, кроме того, что был реализован Dagger 2, и поэтому я просто заменил реализацию DaggerApplication на ApplicationlessInjection ниже.

Известные проблемы

  • Никакой инъекции зависимостей в пользовательских классах приложений (возможно, это не очень хорошая идея с безумными версиями OEM 7.0 OEM).
  • Не все компоненты Dagger, модифицированные мной, я заменил только DaggerAppCompatActivity, DaggerIntentService и DaggerFragment. Если вы используете другие компоненты, такие как DaggerDialogFragment или DaggerBroadcastReceiver, вам нужно создать свои собственные инструменты, но я думаю, что это не должно быть слишком сложно:)

Реализация

Остановить использование DaggerApplication. Расширьте свое приложение снова из стандартного Application или полностью избавитесь от пользовательского приложения. Для инъекции зависимостей с кинжалом 2 он больше не нужен. Просто расширяйте, например. FixedDaggerAppCompatActivity, и вам хорошо пойти с Dagger 2 DI для занятий.

Вы можете заметить, что я все еще передаю контекст приложения в ApplicationlessInjection.getInstance(). Сам по себе инъекции зависимостей совсем не нужен контекст, но я хочу иметь возможность легко вставлять контекст приложения в мои другие компоненты и модули. И там меня не волнует, является ли контекст приложения моим пользовательским приложением или каким-то сумасшедшим другим материалом с Android 7.0, если это контекст.

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

}

Мой 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.

Если требуется больше разъяснений, просто дайте мне знать!

Ответ 2

Я столкнулся с той же проблемой в своем приложении, и я разрешил ее с помощью кода ниже:

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