上周,在我当前的 Kotlin MVP 项目中实现 Dagger 时,我正在使用KitKat 4.4.2 的旧手机上测试它(是的,它仍然支持所有主要的材料特性和东西 :)),因为主要手机的维护。所以那一周我遇到了典型的问题,而不是一些不寻常的问题,并通过调查提供的错误或多或少地快速修复了它们。最后,代码编译,当前项目版本构建没有问题,并且在 KitKat 与 UI 交互上运行没有重大错误。
但是,当我从维修中心拿起带有Nougat 7.1.2 的主手机并在其上启动应用程序时,我遇到了与 DI 相关的奇怪问题。在那之后,我还在 mate 的Marshmallow 6.0 上启动了应用程序,又抓到了一个,完全一样。以这种方式简要描述了该问题:
应用程序(成功)启动;
我能够操作通过 context 和 fragmentManager 注入提供的 ViewPager/DrawerLayout/etc ui 功能;
所有服务也都可用并按预期通过注入运行;
当我抽动 Activity 的 Presenter 时,应用程序崩溃了。
现在最有趣的部分让我发疯了:所有可访问的类,不会带来任何问题,都是通过构造函数注入来注入的。
但是使用field injection注入的 Presenter 实例在需要时不会被初始化。
当然,我尝试不使用 lateinit 修饰符,并像使用 @JvmField 或没有它的可空字段一样注入它:结果是相同的 - 它根本没有注入。
由于问题与 相关Activity
,我有“自然约束”不使用主构造函数进行注入。
这是一个例外,除了第一个字符串外,它对我的信息量不是很大:
kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
at .ui.common.view.BaseViewActivity.getPresenter(BaseViewActivity.kt:14)
at .ui.main.view.MainActivity.onPlaceTypeClick(MainActivity.kt:143)
at .ui.types.nearby.view.NearbyPlaceTypeItemViewHolder$bind$1.onClick(NearbyPlaceTypeItemViewHolder.kt:32)
at android.view.View.performClick(View.java:5647)
at android.view.View$PerformClick.run(View.java:22462)
at android.os.Handler.handleCallback(Handler.java:754)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:163)
at android.app.ActivityThread.main(ActivityThread.java:6205)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794)
这是一些代码:
应用程序:
class MyApp : MultiDexApplication(), HasActivityInjector {
@Inject
@JvmField
var activityInjector: DispatchingAndroidInjector<Activity>? = null
override fun onCreate() {
super.onCreate()
DaggerMyAppComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity>? {
return activityInjector
}
}
应用组件:
@Singleton
@Component(modules = [
MyAppModule::class,
DataModule::class,
PreferencesModule::class,
ServiceModule::class,
NavigationModule::class
])
interface MyAppComponent : AndroidInjector<MyApp> {
@Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApp>()
}
应用模块:
@Module(includes = [AndroidSupportInjectionModule::class])
abstract class MyAppModule {
@Binds
@Singleton
abstract fun application(myApp: MyApp): Application
@PerActivity
@ContributesAndroidInjector(modules = [(MainActivityModule::class)])
abstract fun mainActivityInjector(): MainActivity
//... other activity injectors
}
基本活动模块:
@Module
abstract class BaseActivityModule {
@Binds
@PerActivity
internal abstract fun activity(appCompatActivity: AppCompatActivity): Activity
@Binds
@PerActivity
internal abstract fun activityContext(activity: Activity): Context
@Module
companion object {
const val ACTIVITY_FRAGMENT_MANAGER = "BaseActivityModule.activityFragmentManager"
@JvmStatic
@Provides
@Named(ACTIVITY_FRAGMENT_MANAGER)
@PerActivity
fun activityFragmentManager(activity: AppCompatActivity): FragmentManager {
return activity.supportFragmentManager
}
}
}
目前我有一个广泛分支的 MVP 模型,所以每个活动/片段都有 1 个视图模块和 1 个演示器模块,所有常见的注入都通过基类进行交互。这是活动 DI 部分的示例:
@Module(includes = [
BaseActivityModule::class,
MainPresenterModule::class
])
abstract class MainActivityModule {
@Binds
@PerActivity
abstract fun mainView(mainActivity: MainActivity): MainView
@Binds
@PerActivity
abstract fun appCompatActivity(mainActivity: MainActivity): AppCompatActivity
@PerFragment
@ContributesAndroidInjector(modules = [LocationFragmentModule::class])
abstract fun locationFragmentInjector(): LocationFragment
//... other related fragments injection methods
}
@Module
abstract class MainPresenterModule {
@Binds
@PerActivity
abstract fun mainPresenter(mainPresenterImpl: MainPresenterImpl): MainPresenter
}
基本活动:
abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
@field:Named(BaseActivityModule.ACTIVITY_FRAGMENT_MANAGER)
lateinit var fragmentManager: FragmentManager
@Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(@Nullable savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment>? {
return fragmentInjector
}
}
使用构造函数注入的示例 - MainPresenter(所有注入都有效 - 这里没有问题):
@PerActivity
class MainPresenterImpl @Inject constructor(
val navigationManager: NavigationManager,
val locationManager: LocationManagerImpl,
val sharedPrefsRepo: SharedPreferencesRepository,
view: MainActivity) : BaseViewPresenter<MainView>(view), MainPresenter {
}
下面是所有“魔法”开始的地方——var presenter
在任何情况下都不会初始化。
abstract class BaseViewActivity<T : MVPresenter> (): BaseActivity(), MVPView {
@Inject
lateinit var presenter: T
}
但是,如前所述, KitKat上不会发生这种情况- 应用程序正在其上运行,没有任何问题。所以,再一次:我认为当应用程序在Marshmallow+上运行时存在问题(不幸的是,还不知道 Lollipop)。
我真的想知道以前是否有经验丰富的人遇到过这样的问题。没有构建错误,完全没有 dagger 异常;只是未初始化的属性。
它可能与某种方式的许可有关吗?我拥有的唯一危险许可是设备的位置访问权限,并且已经过持有和测试。
毕竟,如果存在支持/版本控制问题的可能性,它可能与其他方面有关吗?
提前致谢。
升级版:
似乎演示者实例被绑定到Activity
它之后onCreate()
,但是当触发一些回调操作时,活动作为侦听器的角色被取消绑定。因此,看起来 Kotlin 再次查找 Presenter,但发现它未初始化。仍然不明白为什么这只发生在较新的平台上。