设计模块化应用程序-导航中的循环依赖问题

时间:2019-01-04 10:34:51

标签: android

如您所知,将Android应用程序设计为模块是当今Android开发界的流行做法之一。但是这种趋势带来了一些挑战。其中之一是Circular Dependency.

例如,我有一个导航模块,该模块从Home Feature模块中打开HomeActivity。另外,我必须从产品模块中打开另一个活动,例如ProductListActivity。

如果我在以下活动之间进行导航,则首页功能必须包含导航模块,而导航模块应包含HomeFeature:

val intent = Intent(activity, HomeActivity::class.java)

这将导致circular dependency问题。

要找出这个问题的最快解决方案,就是创建如下所示的意图,并以此方法构建导航系统。

Intent(Intent.ACTION_VIEW).setClassName(PACKAGE_NAME, className)

所以我的问题是,使用这种导航方法还会面临哪些其他问题?有没有其他方法可以处理模块化Android应用程序中的导航?

2 个答案:

答案 0 :(得分:5)

这是我刺激的解决方案。这样可以使用显式意图。您也可以对navigation component进行一些修改,然后将此方法应用于单一活动应用程序。

这是模块B的导航对象

object ModuleBNavigator {

    internal lateinit var navigationImpl: ModuleBContract

    fun setNavigationImpl(navigationImpl: ModuleBContract) {
        this.navigationImpl = navigationImpl
    }

    interface ModuleBContract {
        fun navigateModuleA(self: Activity, bundle: Bundle?)
    }
}

这是模块B活动

class ModuleBActivity : Activity() {

    companion object {
        private const val BUNDLE = "BUNDLE"
        fun newIntent(context: Context, bundle: Bundle?) = Intent(context, ModuleBActivity::class.java).apply {
            putExtra(BUNDLE, bundle)
        }
    }
}

这是向导航模块A插入导航元素的应用程序模块类

class ApplicationModuleApp : Application() {

    // Can also inject with a DI library
    override fun onCreate() {
        super.onCreate()
        ModuleBNavigator.setNavigationImpl(object : ModuleBNavigator.ModuleBContract {
            override fun navigateModuleA(self: Activity, bundle: Bundle?) {
                self.startActivity(ModuleBActivity.newIntent(self, bundle))
            }
        })
    }
}

最后,您可以使用提供的实现从模块A->模块B导航

class ModuleAActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ... Some code 
        ModuleBNavigator.navigationImpl.navigateModuleA(this, Bundle())
        // .. Some code
    }
}

此方法避免了对圈子的依赖,并且您不再需要使用隐式意图。 希望这会有所帮助。

答案 1 :(得分:1)

对于另一种方法-实际上与我在问题中提到的类似-哪个实现属于sanogueralorenzo

创建一个loader来填充模块类

const val PACKAGE_NAME = "com.example.android"

private val classMap = mutableMapOf<String, Class<*>>()

private inline fun <reified T : Any> Any.castOrReturnNull() = this as? T

internal fun <T> String.loadClassOrReturnNull(): Class<out T>? =
    classMap.getOrPut(this) {
        try {
            Class.forName(this)
        } catch (e: ClassNotFoundException) {
            return null
        }
    }.castOrReturnNull()

创建一个String extension function以动态加载Intents

private fun intentTo(className: String): Intent =
    Intent(Intent.ACTION_VIEW).setClassName(BuildConfig.PACKAGE_NAME, className)

internal fun String.loadIntentOrReturnNull(): Intent? =
    try {
        Class.forName(this).run { intentTo(this@loadIntentOrReturnNull) }
    } catch (e: ClassNotFoundException) {
        null
    }

创建另一个String extension function以动态加载Fragments

internal fun String.loadFragmentOrReturnNull(): Fragment? =
    try {
        this.loadClassOrReturnNull<Fragment>()?.newInstance()
    } catch (e: ClassNotFoundException) {
        null
    }

为您的功能实现创建一个Feature界面

interface Feature<T> {
    val dynamicStart: T?
}

我假设您具有Messages功能。实施动态功能界面

object Messages : Feature<Fragment> {

    private const val MESSAGES = "$PACKAGE_NAME.messages.presentation.MessagesFragment"

    override val dynamicStart: Fragment?
        get() = MESSAGES.loadFragmentOrReturnNull()

}

最后在另一个模块中使用它无依赖

 Messages.dynamicStart?.let {
            if (savedInstanceState == null) {
                supportFragmentManager.beginTransaction()
                    .replace(R.id.fl_main, it)
                    .commit()
            }
        }