На моей консоли 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>