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)圖 ??
- 谷歌 Android 團(tuán)隊(duì) Jetpack 視圖模型 ??
框架技術(shù)棧
組件化 架構(gòu)
MVVM 架構(gòu)
Repository 設(shè)計(jì)模式
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
窟扑、Fragment
或 Service
)的生命周期。這種感知能力可確保 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)組件(如 Activity
或 Fragment
)的生命周期狀態(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
}
}
Activity
中 ViewBinding
的使用
// 之前設(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"
Fragment
中 ViewBinding
的使用
// 原來的寫法
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
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??吧??