λ:
倉庫地址: https://github.com/lzyprime/android_demos
之前想復用data層, ui層分別用compose和傳統view分別實現。所以通過gradle moudle組織工程:
-
core: 通用部分, 包括data層,viewModel,共用資源文件等 -
view: view實現 -
compose: compose實現
但是實際體驗之后,發(fā)現還是有很多弊端:
- Hilt依賴注入跨moudle的問題
- Application, Manifest文件維護兩份(view, compose),但大部分邏輯相同
- gralde 依賴聲明,module 存在相同依賴,管理繁瑣
初衷本來只是隔離ui層實現和部分資源文件,所以改為通過sourceSet實現:
val useCompose by project.extra(false)
android {
sourceSets {
getByName("main") {
if (useCompose) {
kotlin.srcDir("src/ui/compose")
res.srcDir("src/ui/compose/res")
} else {
res.srcDir("src/ui/view/res")
kotlin.srcDir("src/ui/view")
}
}
}
}
- buildTypes
- dependencies
- productFlavors
- sourceSets
實現 配置,源碼,資源文件 多版本控制
sourceSet 源碼集
sourceSet 是 gradle 本身就提供的接口,用來組織項目源碼。 gradle sourceSets
// build.gradle
plugins {
id 'java'
}
sourceSets {
main {
java {
exclude 'some/unwanted/package/**'
}
}
}
android gradle plugin 自己也有一個sourceSet, 目的很簡單,就是先塞一些默認行為:android sourceSet 默認源碼集。 kts版本的api相比groovy要少一部分,沒有exclude 等操作
-
src/main/此源代碼集包含所有變體共用的代碼和資源。 -
src/<buildType>/創(chuàng)建此源代碼集可加入特定 buildType 專用的代碼和資源。 比如常用的debug,release。在 android.buildTypes 中配置 -
src/<productFlavor>/創(chuàng)建此源代碼集可加入特定產品變種專用的代碼和資源。在 android.productFlavors 配置
// build.gradle.kts
android {
...
sourceSets { // NamedDomainObjectContainer<out AndroidSourceSet>
getByName("main") { // AndroidSourceSet
...
}
}
}
NamedDomainObjectContainer:buildTypes,sourceSets都是此類型。kv容器。
@Incubating
interface AndroidSourceSet : Named {
/** Returns the name of this source set. */
override fun getName(): String
/** The Java source for this source-set */
val java: AndroidSourceDirectorySet
/** The Java source for this source-set */
fun java(action: AndroidSourceDirectorySet.() -> Unit)
...
...
fun setRoot(path: String): Any
}
@Incubating
interface AndroidSourceDirectorySet : Named {
override fun getName(): String
// 追加規(guī)則, set += srcDir
fun srcDir(srcDir: Any): Any
// 追加規(guī)則 set += srcDirs
fun srcDirs(vararg srcDirs: Any): Any
// 覆蓋規(guī)則 set = srcDirs
fun setSrcDirs(srcDirs: Iterable<*>): Any
}
productFlavors 產品變種
如果用 productFlavors 分離ui不同版本:
- 創(chuàng)建
src/view,src/compose源碼集 - 以
uiType為維度, 添加view,compose變種
android {
...
flavorDimensions += "uiType" // 變種維度
productFlavors { // NamedDomainObjectContainer<out ProductFlavorT>
create("view") { // ApplicationProductFlavor
dimension = "uiType"
applicationIdSuffix = ".view"
versionNameSuffix = "-view"
}
create("compose") {
dimension = "uiType"
applicationIdSuffix = ".compose"
versionNameSuffix = "-compose"
}
}
}
當查看productFlavors支持的可配置項時,會發(fā)現與android.defaultConfig, andoird.buildTypes中內容很像。defaultConfig實際上屬于productFlavors,提供所有變體的默認配置。buildType也可視作一個變種維度flavorDimensions, 并且默認有debug和release兩個變體
interface ApplicationProductFlavor : ApplicationBaseFlavor, ProductFlavor
interface ApplicationBaseFlavor : BaseFlavor, ApplicationVariantDimension
interface ProductFlavor : Named, BaseFlavor, ExtensionAware, HasInitWith<BaseFlavor>
interface BaseFlavor : VariantDimension, HasInitWith<BaseFlavor>
// ==>
interface ApplicationProductFlavor : BaseFlavor, VariantDimension, ApplicationVariantDimension
buildTypes
// app build.gradle.kts
android {
defaultConfig { // ApplicationDefaultConfig
...
}
buildTypes { // NamedDomainObjectContainer<out BuildTypeT>
getByName("release") { // ApplicationBuildType
isMinifyEnabled = true
}
getByName("debug") {
applicationIdSuffix = ".debug"
isDebuggable = true
}
create("staging") {
initWith(getByName("debug"))
applicationIdSuffix = ".debugStaging"
}
}
}
ApplicationDefaultConfig, ApplicationBuildType 追蹤完繼承關系會發(fā)現和 ApplicationProductFlavor 的基本一致
interface ApplicationDefaultConfig : ApplicationBaseFlavor, DefaultConfig {}
interface ApplicationBaseFlavor : BaseFlavor, ApplicationVariantDimension
interface BaseFlavor : VariantDimension, HasInitWith<BaseFlavor>
interface ApplicationVariantDimension : VariantDimension
interface DefaultConfig : BaseFlavor {}
// 所以跟到最后,有用的基類:
interface ApplicationDefaultConfig:BaseFlavor, VariantDimension, ApplicationVariantDimension,
interface ApplicationBuildType : BuildType, ApplicationVariantDimension
interface BuildType : Named, VariantDimension, ExtensionAware, HasInitWith<BuildType>
// 所以跟到最后,有用的config基類:
interface ApplicationBuildType : VariantDimension, ExtensionAware, ApplicationVariantDimension
變種構建
所謂的變種最后都會被提交成一個task, 并且維度會自動進行組合。由于創(chuàng)建了新的維度uiType, 所以會得到四種構建方式:debugView, debugCompose, releaseView, releaseCompose。
以debugCompose為例,sourceSet會默認加入src/debugCompose, src/debug, src/compose, src/main
可以在androidComponents.beforeVariants 中配置過濾規(guī)則:
variantBuilder 攜帶了 android 塊中配置的內容
android {
...
}
androidComponents {
beforeVariants { variantBuilder -> // ApplicationVariantBuilder
if (variantBuilder.productFlavors.containsAll(listOf("uiType" to "view"))) {
variantBuilder.enabled = false
}
}
}
dependencies 依賴管理
源碼管理解決后,就是依賴部分:
- 公共依賴的庫
- 只有
view需要的庫 - 只有
compose需要的庫
自然可以通過 useCompose 變量判斷:
val useCompose: String by rootProject.extra
dependencies {
if (useCompose) {
...
} else {
...
}
...
}
但當我們創(chuàng)建buildType和productFlavors的同時, android gradle plugin 會通過 gradle dependency configurations 提供對應的依賴配置: <buildType>Implementation, <productFlavor>Implementation。 但是對于buildType + productFlavors 的組合型構建變體沒有自動創(chuàng)建,需要自己聲明。
val composeDebugImplementation by configurations
// configurations {
// composeDebugImplementation {}
// }
dependencies {
releaseImplementation("....")
composeImplementation("....")
composeDebugImplementation("....")
}
這套機制在多版本分流可能用的到, 當然可以通過一堆變量去控制。 productFlavors的弊端就是多維度是要組合的,派生出一堆變體版本。并且所有代碼在sync時都會檢查,也有可能管理不當,使用其他變體的源碼。
而用變量控制,相當于沒有創(chuàng)建變體,gradle之維護當前條件范圍內的源碼。而我的項目也不用同時出view和compose包。所以直接變量控制,屏蔽掉不關注的另一半
~λ:
已經好長一段時間沒有做真正意義上的android開發(fā)了。去年年底結束了上一個項目后,空了幾周休息。之后接到了SDK項目,就是一堆安全相關的組件(安全鍵盤,掃描,網關等),遠古級項目。sdk的包和gradle plugin估計稍微新點的項目直接引不了。也沒有新的開發(fā)內容,只是作為客服的存在,解答使用者的問題。
同時接到的還有一個 上報觸達sdk, 和上面的sdk一樣的狀況。 不過這個還有新的功能還要開發(fā)。但是看已有的代碼,風險問題一堆:文件讀寫不考慮多線程,樹型結構壓平直接遞歸,連個尾遞歸優(yōu)化都沒有,是真的敢啊。
class Node {
val leafs = listOf<Node>(....)
val val : Config
val isLeaf = false
}
fun rd(Node node): List<Config> {
if(!node->isLeaf) {
... rd() ...
}
}
當然既然是考古,那一定是 java 項目,各種context和handle胡亂持有,IDE各種警告會有內存泄漏風險。 也不會加@Nullable @Nonull標簽 .... 真是一點都不想干
之后項目不需要這么多人,客服工作可以兼職。所以我又解放了一陣。 公司一直沒什么正經android項目,與其做這種玩意兒不如回后端。所以又做了一個月后端,go開發(fā),k8s運維。騰訊自己做容器服務框架, 我們負責把普通產品接進去,做適配器工作。熟悉完項目之后我把產品版本升級和部署等做自動化。 就是根據現有配置生成框架下配置,這東西為什么每次都要人工從頭做,不就一個腳本的事? 還有部署過程,總之一通優(yōu)化。結果這個項目也不做了,適配基本完成,總部收回自己維護就行。
接著就是QQ-TIM, 涉及保密問題不變多說,只能說同上邊兩個sdk沒什么不同,考古,龐大,問題風險多。沒有什么有價值的需求。至于架構設計也并沒有多么出色。代碼插樁加了一條if(xxx != null)的判斷就降了不少crash率,自己品。 不過應該也快收回了。
鑒于這種情況,我只能自己提需求練手, 自己寫個IM。不然水平越放越完蛋。
- 后端kotlin ktor, rust 兩個版本,功能一致,就想練練rust。
- 客戶端
- 與后端通信部分使用ktor client做成跨端sdk (之后也嘗試rust跨端)
- android app: view 和 compose 實現ui
- flutter
- github repo:
寫的很慢,不斷的調整方案。目前勉強寫完登錄,還一個socket都沒有。
這是最后的掙扎吧:每天堅持刷leetcode每日一題,邊寫IM邊學新內容。
真不想越放越費。感覺越來越難改變現狀。