一個(gè) Android MVVM 組件化架構(gòu)框架

AndroidBaseFrameMVVM ??

  • AndroidBaseFrameMVVM 是一個(gè) Android 項(xiàng)目 MVVM 架構(gòu) 開箱即用的框架

  • 該框架基于 Kotlin + Flow + Jetpack + MVVM + 組件化 + Repository 模式實(shí)現(xiàn)

  • 該框架存在的意義一方面是秉承著我和大部分程序猿/媛"懶"的天性,實(shí)現(xiàn)可復(fù)用含末、不用重復(fù)搭項(xiàng)目架構(gòu)、開箱微小修改即可上手新開項(xiàng)目,另一方面也是想把自己會(huì)的東西寫出來,供其他學(xué)習(xí)這方面知識(shí)的同學(xué)借鑒和參考

話不多說上鏈接 GitHub: https://github.com/Quyunshuo/AndroidBaseFrameMVVM

  • 下面展示該框架的架構(gòu)圖 ??
架構(gòu)圖
  • 谷歌 Android 團(tuán)隊(duì) Jetpack 視圖模型 ??
Jetpack 視圖模型

框架技術(shù)棧

  • 組件化 架構(gòu)

  • MVVM 架構(gòu)

  • Repository 設(shè)計(jì)模式

  • Kotlin

  • Kotlin-Coroutines-Flow

  • Lifecycle

  • ViewModel

  • LiveData

  • ViewBinding

  • Android KTX

  • OkHttp:網(wǎng)絡(luò)請求

  • Retrofit:網(wǎng)絡(luò)請求

  • MMKV:騰訊基于 mmap 內(nèi)存映射的 key-value 本地存儲(chǔ)組件

  • Glide:快速高效的Android圖片加載庫

  • ARoute:阿里用于幫助 Android App 進(jìn)行組件化改造的框架 —— 支持模塊間的路由怎顾、通信菱皆、解耦

  • BaseRecyclerViewAdapterHelper:一個(gè)強(qiáng)大并且靈活的RecyclerViewAdapter

  • StatusBarUtil:狀態(tài)欄

  • EventBus:適用于Android和Java的發(fā)布/訂閱事件總線

  • Bugly:騰訊異常上報(bào)及熱更新(只集成了異常上報(bào))

  • PermissionX:郭霖權(quán)限請求框架

  • Chuck:適用于Android OkHttp 客戶端的應(yīng)用內(nèi)HTTP攔截器 顯示請求信息

  • LeakCanary:Android的內(nèi)存泄漏檢測庫

使用方式

  • 1.下載本項(xiàng)目刪除無用的文件

  • 2.修改項(xiàng)目包名及各組件包結(jié)構(gòu),修改 AppName

  • 3.填寫自己的 Bugly key 在 BaseApplication initialize() 方法中

  • 這樣就可以使用了,當(dāng)然可以刪除不用的第三方,或者添加相應(yīng)要使用的第三方,具體規(guī)范看下面的框架解讀

Demo

  • 項(xiàng)目里有一個(gè)demo分支,這是我寫的一個(gè)小例子,可以結(jié)合Demo去熟悉這個(gè)框架

框架解讀

組件化相關(guān)

  • 本框架采用的是組件化架構(gòu),核心組件就是 Base 和 Common ,這兩個(gè)組件都屬于公共組件,負(fù)責(zé)為功能業(yè)務(wù)組件提供支持

  • Base 組件主要集成了各種需要使用的第三方庫和依賴或者公用的 aar/jar,并將依賴向依賴該組件的組件傳遞,需要集成的依賴,全部集成在 Base 組件內(nèi),Base 組件也提供了各種基類封裝以及工具類忍宋、擴(kuò)展函數(shù)、頂層函數(shù),這些都應(yīng)該是項(xiàng)目無關(guān)性的,可以達(dá)到 Base 模塊直接拷貝復(fù)用的效果

  • Common 組件主要是與項(xiàng)目有關(guān)的公用庫,比如網(wǎng)絡(luò)接口,全局常量, bean 類等,和項(xiàng)目有關(guān)的東西因該放在 Common 組件內(nèi),不要侵入 Base 組件,因?yàn)楹晚?xiàng)目有關(guān)的東西一旦放在了 Base 組件內(nèi),想要直接拷貝復(fù)用 Base 組件就不可能了,肯定會(huì)有一堆和項(xiàng)目相關(guān)的東西,項(xiàng)目的資源文件或者公用的資源文件最好統(tǒng)一放在 Common 組件內(nèi),方便公用,方便管理

  • Base 和 Common 都屬于公共組件,區(qū)別就在于 Base 比 Common 更底層,偏于與項(xiàng)目無關(guān)性,而 Common 是與項(xiàng)目有關(guān)性

  • 項(xiàng)目的依賴版本管理和項(xiàng)目參數(shù)等配置統(tǒng)一寫在了 buildSrc 文件夾內(nèi),內(nèi)部維護(hù)了幾個(gè) kt 文件進(jìn)行對依賴庫版本及項(xiàng)目參數(shù)統(tǒng)一存放管理

  • 功能組件應(yīng)該依賴 Common 組件,殼 App 依賴所有的功能組件,要盡量避免各組件互相依賴,殼 App 內(nèi)不要寫東西,只當(dāng)一個(gè)殼負(fù)責(zé)集成各個(gè)組件,每個(gè)組件都應(yīng)該在 build.gradle 文件內(nèi)設(shè)置資源命名規(guī)范,目的是為了避免資源沖突


android {

    resourcePrefix "資源名前綴"

}

  • 各個(gè)功能業(yè)務(wù)組件可以單獨(dú)運(yùn)行,通過 buildSrc/BuildConfig.kt 中的 isAppMode 參數(shù)控制,項(xiàng)目業(yè)務(wù)復(fù)雜起來后,就需要為每個(gè)組件單獨(dú)編寫供其正常單獨(dú)運(yùn)行的邏輯代碼

MVVM相關(guān)

  • MVVM 采用 Jetpack 組件 + Repository 設(shè)計(jì)模式 實(shí)現(xiàn),所使用的 Jetpack 并不是很多,像 DataBinding、Hilt(不支持多模塊)然痊、Room 等并沒有使用,如果需要可以添加革骨。采用架構(gòu)模式目的就是為了解偶代碼,對代碼進(jìn)行分層,各模塊各司其職,所以既然使用了架構(gòu)模式那就要遵守好規(guī)范

  • Repository 倉庫層負(fù)責(zé)數(shù)據(jù)的提供,ViewModel 無需關(guān)心數(shù)據(jù)的來源,Repository 內(nèi)避免使用 LiveData,框架里使用了 Kotlin 協(xié)程的 Flow 進(jìn)行處理請求或訪問數(shù)據(jù)庫,最后將數(shù)據(jù)發(fā)射到 ViewModel 調(diào)用者,Flow上游負(fù)責(zé)提供數(shù)據(jù),下游也就是ViewModel獲取到數(shù)據(jù)使用LiveData進(jìn)行存儲(chǔ),View層訂閱LiveData,實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)視圖

  • 三者的依賴都是單向依賴,View -> ViewModel -> Repository

示例代碼及注意事項(xiàng)??

ViewModel

ViewModel 類旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù)农尖。ViewModel 類讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)留存。

使用方式:


class MainViewModel : ViewModel(){ 

}

class MainActivity : AppCompatActivity() {

// 獲取無參構(gòu)造的ViewModel實(shí)例

    val mViewModel = ViewModelProvider(this).get(MainViewModel::class.java)

}

資料:

官方文檔: https://developer.android.com/topic/libraries/architecture/viewmodel

Android ViewModel良哲,再學(xué)不會(huì)你砍我: https://juejin.im/post/6844903919064186888

LiveData

LiveData是一種可觀察的數(shù)據(jù)存儲(chǔ)器類卤橄。與常規(guī)的可觀察類不同,LiveData 具有生命周期感知能力臂外,意指它遵循其他應(yīng)用組件(如 Activity窟扑、FragmentService)的生命周期。這種感知能力可確保 LiveData 僅更新處于活躍生命周期狀態(tài)的應(yīng)用組件觀察者

LiveData 分為可變值的MutableLiveData和不可變值的LiveData

常用方法:


fun test() {

        val liveData = MutableLiveData<String>()

        // 設(shè)置更新數(shù)據(jù)源

        liveData.value = "LiveData"

        // 將任務(wù)發(fā)布到主線程以設(shè)置給定值

        liveData.postValue("LiveData")

        // 獲取值

        val value = liveData.value

        // 觀察數(shù)據(jù)源更改(第一個(gè)參數(shù)應(yīng)是owner:LifecycleOwner 比如實(shí)現(xiàn)了LifecycleOwner接口的Activity)

        liveData.observe(this, {

            // 數(shù)據(jù)源更改后觸發(fā)的邏輯

        })

    }

資料:

官方文檔: https://developer.android.com/topic/libraries/architecture/livedata

Lifecycle

Lifecycle 是一個(gè)類漏健,用于存儲(chǔ)有關(guān)組件(如 ActivityFragment)的生命周期狀態(tài)的信息嚎货,并允許其他對象觀察此狀態(tài)。

LifecycleOwner 是單一方法接口蔫浆,表示類具有 Lifecycle殖属。它具有一種方法(即 getLifecycle()),該方法必須由類實(shí)現(xiàn)瓦盛。

實(shí)現(xiàn) LifecycleObserver 的組件可與實(shí)現(xiàn) LifecycleOwner 的組件無縫協(xié)同工作洗显,因?yàn)樗姓呖梢蕴峁┥芷冢^察者可以注冊以觀察生命周期原环。

資料:

官方文檔: https://developer.android.com/topic/libraries/architecture/lifecycle

ViewBinding

通過視圖綁定功能挠唆,您可以更輕松地編寫可與視圖交互的代碼。在模塊中啟用視圖綁定之后嘱吗,系統(tǒng)會(huì)為該模塊中的每個(gè) XML 布局文件生成一個(gè)綁定類玄组。綁定類的實(shí)例包含對在相應(yīng)布局中具有 ID 的所有視圖的直接引用。

在大多數(shù)情況下谒麦,視圖綁定會(huì)替代 findViewById

使用方式:

按模塊啟用ViewBinding


// 模塊下的build.gradle文件

android {

// 開啟ViewBinding

    // 高版本AS

    buildFeatures {

        viewBinding = true

    }

    // 低版本AS 最低3.6

    viewBinding {

        enabled = true

    }

}

ActivityViewBinding 的使用


// 之前設(shè)置視圖的方法

setContentView(R.layout.activity_main)

// 使用ViewBinding后的方法

val mBinding = ActivityMainBinding.inflate(layoutInflater)

setContentView(mBinding.root)

// ActivityMainBinding類是根據(jù)布局自動(dòng)生成的 如果沒有請先build一下項(xiàng)目

// ViewBinding會(huì)將控件id轉(zhuǎn)換為小駝峰命名法,所以為了保持一致規(guī)范,在xml里聲明id時(shí)也請使用小駝峰命名法

// 比如你有一個(gè)id為mText的控件,可以這樣使用

mBinding.mText.text = "ViewBinding"

FragmentViewBinding 的使用


// 原來的寫法

return inflater.inflate(R.layout.fragment_blank, container, false)

// 使用ViewBinding的寫法

mBinding = FragmentBlankBinding.inflate(inflater)

return mBinding.root

資料:

官方文檔: https://developer.android.com/topic/libraries/view-binding

CSDN: https://blog.csdn.net/u010976213/article/details/104501830?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5

ARoute

ARoute是阿里巴巴的一個(gè)用于幫助 Android App 進(jìn)行組件化改造的框架 —— 支持模塊間的路由俄讹、通信、解耦

使用方式:


// 1.在需要進(jìn)行路由跳轉(zhuǎn)的Activity或Fragment上添加 @Route 注解

@Route(path = "/test/activity")

public class YourActivity extend Activity {

    ...

}

// 2.發(fā)起路由跳轉(zhuǎn)

ARouter.getInstance()

    .build("目標(biāo)路由地址")

    .navigation()



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

ARouter.getInstance()

.build("目標(biāo)路由地址")

    .withLong("key1", 666L)

    .withString("key3", "888")

    .withObject("key4", new Test("Jack", "Rose"))

    .navigation()

// 4.接收參數(shù)

@Route(path = RouteUrl.MainActivity2)

class MainActivity : AppCompatActivity() {

    // 通過name來映射URL中的不同參數(shù)

    @Autowired(name = "key")

    lateinit var name: String



override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(mBinding.root)

        // ARouter 依賴注入 ARouter會(huì)自動(dòng)對字段進(jìn)行賦值绕德,無需主動(dòng)獲取

        ARouter.getInstance().inject(this)

    }

}

// 5.獲取Fragment

Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();

資料:

官方文檔:https://github.com/alibaba/ARouter

屏幕適配 AndroidAutoSize

屏幕適配使用的是 JessYan 大佬的 今日頭條屏幕適配方案終極版

GitHub: https://github.com/JessYanCoding/AndroidAutoSize

使用方式:


// 在清單文件中聲明

<manifest>

    <application>

    // 主單位使用dp 沒設(shè)置副單位

        <meta-data

            android:name="design_width_in_dp"

            android:value="360"/>

        <meta-data

            android:name="design_height_in_dp"

            android:value="640"/>         

    </application>         

</manifest>

// 默認(rèn)是以豎屏的寬度為基準(zhǔn)進(jìn)行適配

// 如果是橫屏項(xiàng)目要適配Pad(Pad適配盡量使用兩套布局 因?yàn)槭謾C(jī)和Pad屏幕寬比差距很大 無法完美適配)

<manifest>

    <application>           

    // 以高度為基準(zhǔn)進(jìn)行適配 (還需要手動(dòng)代碼設(shè)置以高度為基準(zhǔn)進(jìn)行適配) 目前以高度適配比寬度為基準(zhǔn)適配 效果要好

        <meta-data

            android:name="design_height_in_dp"

            android:value="400"/>         

    </application>         

</manifest>

// 在Application 中設(shè)置

// 屏幕適配 AndroidAutoSize 以橫屏高度為基準(zhǔn)進(jìn)行適配

AutoSizeConfig.getInstance().isBaseOnWidth = false

EventBus APT

事件總線這里選擇的還是EventBus,也有很多比較新的事件總線框架,還是選擇了這個(gè)直接上手的

在框架內(nèi)我對EventBus進(jìn)行了基類封裝,自動(dòng)注冊和解除注冊,在需要注冊的類上添加@EventBusRegister注解即可,無需關(guān)心內(nèi)存泄漏及沒及時(shí)解除注冊的情況,基類里已經(jīng)做了處理


@EventBusRegister

class MainActivity : AppCompatActivity() {

}

很多使用EventBus的其實(shí)都沒有發(fā)現(xiàn)APT的功能,這是EventBus3.0的重大更新,使用EventBus APT可以編譯期生成訂閱類,這樣就可以避免使用低效率的反射,很多人不知道這個(gè)更新,用著3.0的版本,實(shí)際上卻是2.0的效率

項(xiàng)目中已經(jīng)在各模塊中開啟了EventBus APT,EventBus會(huì)在編譯器對各模塊生成訂閱類,需要我們手動(dòng)代碼去集成這些訂閱類


// 因?yàn)锳pp包依賴了所有的模塊所以選擇在App包下的Application中進(jìn)行設(shè)置

// 開啟EventBus APT

EventBus

    .builder()

.addIndex("各模塊生成的訂閱類的實(shí)例 類名在 moduleBase.gradle 腳本中進(jìn)行了設(shè)置 比如 Lib_Main 生成的訂閱類就是 MainEventIndex")

    .installDefaultEventBus()

PermissionX

PermissionX 是郭霖的一個(gè)權(quán)限申請框架

使用方式:


PermissionX.init(this)

    .permissions("需要申請的權(quán)限")

    .request { allGranted, grantedList, deniedList -> }

資料:

GitHub: https://github.com/guolindev/PermissionX

組件化資源命名沖突

組件化方案中有一個(gè)坑就是資源命名沖突的問題,小則導(dǎo)致合并清單文件時(shí)資源丟失,大則直接導(dǎo)致崩潰

為了解決這個(gè)問題,需要在build.gradle文件中,對各模塊聲明資源前綴規(guī)則


android {

// 前綴最好以組件包名規(guī)定

    resourcePrefix "main_"

}

Kotlin 協(xié)程

Kotlin Coroutines 是一套解決異步編程的方案,在 Android 平臺(tái)就是一個(gè)線程框架,用了都說好

使用示例:

  • Activity

// 在 Activity 中 可以使用 lifecycleScope 協(xié)程作用域 進(jìn)行開啟協(xié)程

// lifecycleScope 是 LifecycleOwner 的擴(kuò)展屬性 使用 lifecycleScope 你就無需關(guān)心Activity聲明周期的問題 無需關(guān)心因?yàn)樯芷趯?dǎo)致的協(xié)程泄漏的問題 可以說是真方便

// 使用示例

lifecycleScope.launch(Dispatchers.Default) {

    delay(1000L)

    ARouter.getInstance()

        .build(RouteUrl.MainActivity)

        .navigation()

    delay(100L)

    finish()

}

// launch() 函數(shù)開啟了一個(gè)協(xié)程 調(diào)度器默認(rèn)是Main 這里我指定了 Dispatchers.Default

// 使用默認(rèn)調(diào)度器可以直接使用 launch{}

// Activity 中還有一個(gè)協(xié)程作用域可以使用 就是 MainScope() 它會(huì)返回一個(gè)協(xié)程作用域?qū)嵗?使用這個(gè)就需要我們手動(dòng)去處理聲明周期的問題了

  • ViewModel 中使用

// 在 ViewModel 中可以使用

viewModelScope.launch {}

// 默認(rèn)的調(diào)度器也是Main 同樣也不需要我們?nèi)プ錾芷诘奶幚?
  • Flow

Flow類似于RxJava,它也有一系列的操作符

資料:

Google 推薦在 MVVM 架構(gòu)中使用 Kotlin Flow: https://juejin.im/post/6854573211930066951

即學(xué)即用Kotlin - 協(xié)程: https://juejin.im/post/6854573211418361864

Kotlin Coroutines Flow 系列(1-5): https://juejin.im/post/6844904057530908679

結(jié)語

  • 本人是一個(gè)剛大學(xué)畢業(yè)的 Android 開發(fā)者,技術(shù)相對來說比較薄弱,寫這個(gè)框架主要是為了在以后自己使用患膛、對自己技術(shù)做一個(gè)總結(jié)、對其他想學(xué)習(xí)的同學(xué)提供一個(gè)例子耻蛇、也是想把一些東西開源出去,讓這個(gè)社區(qū)更加的美好踪蹬。文檔中可能有些描述不太專業(yè)和生硬,我也很少寫這些東西,所以表達(dá)起來很生疏胞此。

  • 如果你絕對該項(xiàng)目對你有用,那就請給一個(gè) star??吧??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市延曙,隨后出現(xiàn)的幾起案子豌鹤,更是在濱河造成了極大的恐慌,老刑警劉巖枝缔,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件布疙,死亡現(xiàn)場離奇詭異,居然都是意外死亡愿卸,警方通過查閱死者的電腦和手機(jī)灵临,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趴荸,“玉大人儒溉,你說我怎么就攤上這事》⒍郏” “怎么了顿涣?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酝豪。 經(jīng)常有香客問我涛碑,道長,這世上最難降的妖魔是什么孵淘? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任蒲障,我火速辦了婚禮,結(jié)果婚禮上瘫证,老公的妹妹穿的比我還像新娘揉阎。我一直安慰自己,他們只是感情好背捌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布毙籽。 她就那樣靜靜地躺著,像睡著了一般载萌。 火紅的嫁衣襯著肌膚如雪惧财。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天扭仁,我揣著相機(jī)與錄音,去河邊找鬼厅翔。 笑死乖坠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刀闷。 我是一名探鬼主播熊泵,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼仰迁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了顽分?” 一聲冷哼從身側(cè)響起徐许,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卒蘸,沒想到半個(gè)月后雌隅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缸沃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年恰起,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趾牧。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡检盼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出翘单,到底是詐尸還是另有隱情吨枉,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布哄芜,位于F島的核電站貌亭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏忠烛。R本人自食惡果不足惜属提,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望美尸。 院中可真熱鬧冤议,春花似錦、人聲如沸师坎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胯陋。三九已至蕊温,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遏乔,已是汗流浹背义矛。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盟萨,地道東北人凉翻。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像捻激,于是被迫代替她去往敵國和親制轰。 傳聞我的和親對象是個(gè)殘疾皇子前计,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355