web-dev-qa-db-ja.com

Android 7.0およびSamsungデバイスでDagger 2を使用するRuntimeException

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実装によるものと思われます。同じ問題と解決策を見つけてください、お願い、助けてください!

このエラーが私を夢中にさせているので、この全体がどこでうまくいかないかを理解しようとしている10万人のユーザーにテスト版を公開しました。

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コードを使用したDaggerのアクティビティに基づいています。私の考えでは、getApplication()の代わりにApplicationContextを使用してこの問題を解決できない場合、スタックトレースで何が起こっているのかを詳しく説明する必要があります。

  • 問題の原因がgetApplication()の場合、スタックトレースにはinjectWithNullApplication()またはinjectWithWrongApplication()が含まれます。
  • スローされたNPEは、getApplicationContext()がnullを返したことを示します。
  • スローされた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)

したがって、!(application instanceof HasActivityInjector)のif節inject()injectWithWrongApplication()に再ルーティングしませんでしたが、同じif節によって同じアプリケーションインスタンスのinjectNow(Application application)でRuntimeExceptionが発生しました。 WTF?私は自分のコードで100回のように見えましたが、そこにエラーがある場合はお知らせください!そうでなければ、7.0のベンダー実装では、おそらく修正できないかもしれない、本当に奇妙なことが起こっていると思います...

https://github.com/google/dagger/issues/748 の議論に基づいて、getApplicationContext()ではなくgetApplication()のみを使用するテストバージョンも展開しました。 ]すべてのDaggerコンポーネントに違いはありません。

マニフェストからの私のアプリケーションタグ

<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>
30
Denis Knauer

最後に、アプリケーションでAndroid 7.0の下でDagger 2を使用することによって引き起こされるクラッシュを解決する方法を見つけました。これは、カスタムアプリケーションがAndroid 7.0の下で適切に使用されていません。私の場合、Dagger 2を実装する以外にカスタムアプリケーションに重要なロジックがありませんでした。 DaggerApplicationベースの実装を以下のApplicationlessInjectionに置き換えました。

既知の問題

  • カスタムアプリケーションクラスへの依存性注入はありません(恐らくAndroid 7.0 OEM実装とにかくこれは良い考えではないでしょう)
  • 私が変更したすべてのDaggerコンポーネントではなく、DaggerAppCompatActivityDaggerIntentService、およびDaggerFragmentのみを置き換えました。 DaggerDialogFragmentDaggerBroadcastReceiverなどの他のコンポーネントを使用している場合は、独自の実装を作成する必要がありますが、それほど難しくないはずです:)

実装

DaggerApplicationの使用を停止します。標準のApplicationからカスタムアプリケーションを再度拡張するか、カスタムアプリケーションを完全に削除します。 Dagger 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バージョンの導入に関連するすべてのクラッシュを解決できました。

さらに説明が必要な場合は、お知らせください!

13
Denis Knauer

私は私のアプリで同じ問題に遭遇し、以下のコードを使用してそれを解決しました:

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