Android組件化架構(gòu) —— 基礎(chǔ)(二) - 組件間通訊

xwzz.jpg

前篇回顧

鏈接:Android組件化 —— 基礎(chǔ)(一) - 組件化與集成化

上篇文章,我們了解了:

  • 組件化與集成化的區(qū)別;
  • 通過gradle自動(dòng)轉(zhuǎn)換組件環(huán)境和集成環(huán)境;
  • 解決AndroidManifest.xml共用問題。

本篇,我們將探討組件化架構(gòu)中組件間通訊是如何完成的。

Activity跳轉(zhuǎn)

我們知道,正常的Activity跳轉(zhuǎn)代碼大致如下:

     val intent = Intent(this, UserMainActivity::class.java)
     startActivity(intent)

然而,在組件環(huán)境下,各模塊相互獨(dú)立,它們之間不存在任何依賴關(guān)系,導(dǎo)致模塊之間Activity互不可見的,代碼在編譯期無法識別到對方的Activty,頁面的跳轉(zhuǎn)就成了問題。

Activity跳轉(zhuǎn)問題

其實(shí)我們知道,最終無論是哪個(gè)模塊的Activity最終都會(huì)打包到同一個(gè)apk中,在代碼文件層面上講,這些Class文件是相互可見的,這就需要我們繞點(diǎn)彎路將這些Activity提供給對方。設(shè)想一下,能不能找一個(gè)中間人,把需要跳轉(zhuǎn)的Activity都交給它來管理?

我大致想到一個(gè)方案:

  • 1、創(chuàng)建一個(gè)公共模塊lib_comm,該模塊用于存放各模塊間公共代碼或者說基礎(chǔ)功能代碼,并且其它模塊都依賴于該Library;
  • 2、在lib_comm中創(chuàng)建一個(gè)“路由”容器管理類,該類向外提供路由的注冊、查詢等功能;
  • 3、各模塊將需要外部跳轉(zhuǎn)的Activity,注冊到路由容器中;
  • 4、跳轉(zhuǎn)時(shí),由路由容器去查詢路由,完成跳轉(zhuǎn)。

什么是路由?前面提到,如果要跨模塊跳轉(zhuǎn)Activity,我們需要將這些Activity的Class對象提供到對方模塊,為了方便管理和調(diào)用,我們用一個(gè)簡化的字符串來標(biāo)識對應(yīng)的Activity.class對象,例如:"A" -> AActivty.class,這種通過字符串查詢到指定頁面的方案,可以稱之為路由。

按照上面思路,我把相關(guān)實(shí)現(xiàn)代碼貼在了下方:

  • 創(chuàng)建lib_comm公共模塊,以及路由管理類RouterManager
lib_comm公共模塊
/**
 * 路由管理類
 * 提供路由注冊、查詢等功能
 * */
object RouterManager {

    const val TAG = "RouterManager"

    // 存儲(chǔ)路由的的容器
    private val mRouterMap = HashMap<String, Class<*>>()

    /**
     * 添加路由
     * @param path 路由路徑
     * @param clazz 路由目標(biāo)
     * */
    fun addRouter(path: String, clazz: Class<*>) {
        mRouterMap[path] = clazz
    }

    /**
     * 開啟Activity
     * */
    fun startActivity(context: Context, path: String) {
        val clazz = mRouterMap[path]
        if (clazz == null) {
            val log  = "not found router by path !"
            Log.e(TAG, log)
            showToast(context , log)
            return
        }
        // 判斷是否是Activity的子類
        if (Activity::class.java.isAssignableFrom(clazz)) {
            val intent = Intent(context, clazz)
            context.startActivity(intent)
        } else {
            val log = "router's not Activity !"
            Log.e(TAG, log)
            showToast(context , log)
        }
    }

    private fun showToast(context:Context , log: String) {
        Toast.makeText(context , log , Toast.LENGTH_SHORT).show()
    }

}
  • 各模塊依賴lib_comm,并在startup中完成路由注冊
    // 各模塊build.gradle中添加依賴
    implementation project(":lib_comm")
// 以user模塊為例,通過startup完成路由注冊
class UserInitializer : Initializer<UserInit> {

    override fun create(context: Context): UserInit {
        UserInit.init(context)
        return UserInit
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        return mutableListOf()
    }
}
// user模塊初始化入口
object UserInit {

    fun init(context: Context) {
        initRouter()
    }

    private fun initRouter() {
        // 注冊路由
        RouterManager.addRouter("user/UserMainActivity", UserMainActivity::class.java)
    }
}
  • 最終各模塊使用路由完成跳轉(zhuǎn)
class AppMainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.btn_user).setOnClickListener {
            // 通過路由跳轉(zhuǎn)
            RouterManager.startActivity(this, "user/UserMainActivity")
        }
    }
}

跳轉(zhuǎn)演示

(諾GIF圖加載失敗,可點(diǎn)擊此處查看)

Fragment獲取

實(shí)現(xiàn)方案與Activity類似,將Fragment對應(yīng)的路由存儲(chǔ)到路由容器中,再暴露一個(gè)獲取Fragment API即可。

// 存儲(chǔ)到路由容器中
RouterManager.addRouter("user/UserFragment", UserFragment::class.java)
// 暴露獲取Fragment的API
object RouterManager {

    ...

    /**
     * 獲取Fragment
     * */
    fun getFragment(context: Context, path: String): Fragment? {
        val clazz = mRouterMap[path]
        if (clazz == null) {
            val log = "not found router by path !"
            Log.e(TAG, log)
            showToast(context, log)
            return null
        }
        // 判斷是否是Fragment的子類
        if (Fragment::class.java.isAssignableFrom(clazz)) {
            return clazz.newInstance() as Fragment
        } else {
            val log = "router's not Fragment !"
            Log.e(TAG, log)
            showToast(context, log)
        }
        return null
    }
  ...
}
// 獲取Fragment ,并使用
RouterManager.getFragment(this, "user/UserFragment")?.apply {
    val beginTransaction = supportFragmentManager.beginTransaction()
    beginTransaction.replace(R.id.fl_fragment, this)
    beginTransaction.commit()
}

獲取Fragment

(諾GIF圖加載失敗,可點(diǎn)擊此處查看)

跳轉(zhuǎn)攜帶參數(shù)

頁面跳轉(zhuǎn)過程中需要攜帶的參數(shù),可以通過Bundle對象進(jìn)行傳遞,代碼實(shí)現(xiàn)如下,此處就不做過多闡述:

object RouterManager {
/**
     * 開啟Activity
     * */
    fun startActivity(context: Context, path: String, bundle: Bundle? = null) {
        val clazz = mRouterMap[path]
        if (clazz == null) {
            val log = "not found router by path !"
            Log.e(TAG, log)
            showToast(context, log)
            return
        }
        // 判斷是否是Activity的子類
        if (Activity::class.java.isAssignableFrom(clazz)) {
            val intent = Intent(context, clazz)
            // 添加參數(shù)
            if (bundle != null) {
                intent.putExtras(bundle)
            }
            context.startActivity(intent)
        } else {
            val log = "router's not Activity !"
            Log.e(TAG, log)
            showToast(context, log)
        }
    }

    /**
     * 獲取Fragment
     * */
    fun getFragment(context: Context, path: String, bundle: Bundle? = null): Fragment? {
        val clazz = mRouterMap[path]
        if (clazz == null) {
            val log = "not found router by path !"
            Log.e(TAG, log)
            showToast(context, log)
            return null
        }
        // 判斷是否是Fragment的子類
        if (Fragment::class.java.isAssignableFrom(clazz)) {
            val fragment = clazz.newInstance() as Fragment
            //添加參數(shù)
            if (bundle != null) {
                fragment.arguments = bundle
            }
            return fragment
        } else {
            val log = "router's not Fragment !"
            Log.e(TAG, log)
            showToast(context, log)
        }
        return null
    }
}

跨模塊功能調(diào)用

實(shí)際開發(fā)中,部分模塊的功能可能需要提供給別的模塊使用。
例如:user模塊在用戶登錄后會(huì)保存用戶登錄狀態(tài),其他模塊部分業(yè)務(wù)可能需要校驗(yàn)該狀態(tài)才可繼續(xù)操作。
顯然,是否登錄功能需要暴露給其它模塊使用,如果直接將用戶是否登錄的業(yè)務(wù)代碼(校驗(yàn)token等)丟到lib_comm中,雖然可以解決問題,但我們知道這樣的代碼實(shí)際是屬于用戶業(yè)務(wù)線的,我們定義lib_comm的初衷是希望它存放公共代碼,并且業(yè)務(wù)線盡量少甚至不去修改這部分代碼,一旦用戶業(yè)務(wù)線的校驗(yàn)規(guī)則發(fā)生改變,那么業(yè)務(wù)線去修改lib_comm就不可避免。

仔細(xì)想想,我們只需將業(yè)務(wù)線需要暴露的功能,以接口的形式提供給lib_comm,別的模塊再通過這些接口來訪問對應(yīng)的功能即可,具體功能的實(shí)現(xiàn)還是保留在各自業(yè)務(wù)線模塊里,這樣就可避免業(yè)務(wù)線代碼入侵問題,詳細(xì)實(shí)現(xiàn)可以參考下方代碼:

  • user模塊的用戶是否登錄業(yè)務(wù)代碼
object UserUtils {
    
    /**
     * 用戶模塊是否登錄
     * 實(shí)際業(yè)務(wù)代碼
     * */
    fun isLogin(): Boolean {
        // 校驗(yàn)Token之類的業(yè)務(wù)邏輯
        // ...
        // ...
        return true
    }
}
  • lib_comm模塊
    • 定義表示功能標(biāo)記接口
    • 以及user模塊提供的功能接口
    • 并在RouterManager中提供對外獲取接口實(shí)例的方法
/**
 * 功能性路由標(biāo)記接口
 * */
interface IService
/**
 * User模塊對外提供的功能接口
 * */
interface IUserService : IService {

    /**
     * 是否登錄
     */
    fun isLogin(): Boolean
}
object RouterManager {
    ...

    /**
     * 獲取用戶模塊提供的服務(wù)
     * */
    fun getUserService(context: Context): IUserService? {
        val clazz = mRouterMap["user/UserService"]
        if (clazz == null) {
            val log = "not found service router by path !"
            Log.e(TAG, log)
            showToast(context, log)
            return null
        }
        // 判斷是否是Service & IUserService
        if (IService::class.java.isAssignableFrom(clazz)
            && IUserService::class.java.isAssignableFrom(clazz)
        ) {
            return clazz.newInstance() as IUserService
        } else {
            val log = "router's not IUserService !"
            Log.e(TAG, log)
            showToast(context, log)
        }
        return null
    }

    ...
}
  • user模塊實(shí)現(xiàn)IUserService接口,并注冊路由
/**
 * user模塊實(shí)現(xiàn)對外暴露的功能
 * */
class UserServiceImpl : IUserService {

    override fun isLogin(): Boolean {
        return UserUtils.isLogin()
    }
}
// 注冊功能到路由中
RouterManager.addRouter("user/UserService", UserServiceImpl::class.java)
  • app模塊調(diào)用user模塊暴露的isLogin()功能
RouterManager.getUserService(this)?.apply {
    Toast.makeText(
      this@AppMainActivity,
      "登錄狀態(tài):${this.isLogin()}",
      Toast.LENGTH_SHORT
      ).show()
}

跨模塊功能調(diào)用

(諾GIF圖加載失敗,可點(diǎn)擊此處查看)

關(guān)于lib_comm的修改問題

雖然通過上面的方案,我們將業(yè)務(wù)模塊對外提供的功能解耦到了自身模塊里,但不得不在lib_comm中對外提供對應(yīng)的IService子接口,一旦子業(yè)務(wù)線需要對外提供新的功能,或者刪除舊的功能,那么在lib_comm修改IService子接口就在所難免。
顯然IService子接口會(huì)隨著業(yè)務(wù)線的變動(dòng)發(fā)生修改,我們只是做到了盡量少的修改lib_comm代碼;在后續(xù)篇章中,我會(huì)提供一種方案來解決該問題,該問題先暫時(shí)保留下來。

小結(jié)

本篇就先到這里,我們主要了解了組件化架構(gòu)中Activity的跳轉(zhuǎn)、Fragment的獲取、以及跨模塊功能調(diào)用等開發(fā)中常會(huì)遇到的場景案例,并嘗試手寫代碼來解決這些問題。如果你一步步完成了這些功能,恭喜你,你已經(jīng)對“路由”的具體實(shí)現(xiàn)有了基本的認(rèn)識。

我們編寫的路由處理框架還存在很多問題,例如:

  • 我們在startup中注冊路由,這就會(huì)導(dǎo)致在App啟動(dòng)時(shí)會(huì)將所有模塊的路由全部注冊到內(nèi)存中,然而部分路由在用戶的實(shí)際使用中可能未被使用,這就導(dǎo)致額外內(nèi)存開銷;
  • 對于功能性的Service,每次都重新創(chuàng)建新的對象給調(diào)用者,這塊也可以進(jìn)行緩存優(yōu)化;
  • ...

路由中可能涉及到的其它功能,我們也沒做具體的實(shí)現(xiàn),但只要通過本篇對路由核心實(shí)現(xiàn)有了清晰認(rèn)識也收獲足以。市面上路由已經(jīng)有了很多成熟框架,例如美團(tuán)的WMRouter,阿里的ARouter等,如果項(xiàng)目對代碼的自研要求不高,使用這些框架來實(shí)現(xiàn)路由無論是在性能上,還是效率上都再好不過。

下篇,以ARouter為例繼續(xù)探討路由那些事。

Android組件化架構(gòu) —— 基礎(chǔ)(三) - ARouter

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 架構(gòu)系列:Android 架構(gòu)一:Android 架構(gòu)淺析Android 架構(gòu)二:縱向橫向結(jié)合構(gòu)建...
    IT前沿技術(shù)分享閱讀 1,887評論 2 16
  • 前言說明 以下內(nèi)容均為 Android 組件化架構(gòu)知識點(diǎn)的總結(jié)歸納、修正錯(cuò)誤和完善擴(kuò)展,非系統(tǒng)知識集,個(gè)人筆記,僅...
    Parallel_Lines閱讀 6,967評論 11 84
  • 現(xiàn)在規(guī)模比較大的app都實(shí)現(xiàn)了組件化方案,來解耦和方便協(xié)作。帶來的問題時(shí)模塊之間的相互通信比較麻煩。 一般App組...
    SimpleFunc閱讀 2,198評論 0 3
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,814評論 28 54
  • 人工智能是什么?什么是人工智能?人工智能是未來發(fā)展的必然趨勢嗎?以后人工智能技術(shù)真的能達(dá)到電影里機(jī)器人的智能水平嗎...
    ZLLZ閱讀 4,085評論 0 5

友情鏈接更多精彩內(nèi)容