組件化開發規範指導意見

零、背景介紹

組件化技術,對於經常開發同類型需求的開發團隊來說,再提高開發效率和代碼維護上是一個十分有利的工具。對於相同的業務流程來說,其高效複用(不改動或少量改動)的前提主要有兩個緯度:

  • 1.所有的組件擁有相同的基礎庫
  • 2.編碼風格的一致化

這裏面基礎庫的一致性是較爲根本和重要的,他可以讓我們的組件在啓用時,只需要添加組件的依賴就可以引入組件,而不用關心,由於依賴導致的報錯。這讓我們的精力可以更聚焦於具體的業務。

而編碼風格的統一,無論是在開發階段,還是打包階段,都能有效幫我們規避掉很多煩人的小問題。

風格統一的好處

  1. 舉一反三,看完一個組件,即可仿照此組件的模式進行規範化開發和維護
  2. 由於風格的統一,能有規律的找到承載某個業務的類文件
  3. 解決由於引入某個第三方庫版本不一致導致的衝突
  4. 不同組件裏資源文件不用擔心被覆蓋
  5. 避免APP殼工程在打包時AndroidManifest.xml合併發生錯誤

一、Module命名 Module池

Module作爲組件化過程的中基本元素,這裏主要根據Module的性質進行命名上的規範,方便快速區分和找到對應業務。

組件性質 建議名稱 示例
基礎組件拆分管理 lib_業務 lib_common(公用下沉業務) lib_base(自定義的基礎類) lib_opensource(開源的第三方依賴)
基礎組件統一管理 lib_業務 lib_base(通用基礎依賴的集合體)
業務組件 module_業務 module_main
對外暴露接口 interface_業務 interface_main

對於基礎組件的管理,較爲常見的就是統一管理,和分開管理,兩種管理模式在依賴後,作爲組件化的基礎,其對於上層組件是相等的。分開管理中對公用業務下沉和開源依賴的分開,其實主要是更好的體現底層庫的構成結構,方便了解底層庫的主要指責。

組件化對外暴露業務接口,常見的形式一般就是將此部分接口和數據對象做下沉,下沉至lib_base層,那麼對於上層組件來說,這些對外服務就都是可見的了,不過這種形式有兩個缺點:

  • 1.所有接口和數據對象對上層組件都可見-》不能準確的知道使用對象
  • 2.間接的導致了底層的膨脹

所以爲了解決以上問題和保證對外暴露業務的獨立性和私有性而推出的解決方案。這裏我們使用的是Jar Library的形式創建Module這裏面主要存放,對外開放的業務接口,和接口涉及的基礎數據對象。此部分由純Java組成,只有接口和數據Bean。此部分Build之後會生成.jar這個就是交付給其他組件的對外暴露接口。
當其他組件需要你的服務的時候,只需要申請.jar就可以了。

如果使用了EventBus,那麼Event也屬於對外開發的對象。

二、包命名

組件內的包按照Module命名進行命名避免打包時產生衝突。
其命名公式爲::com.項目名稱.組件名稱

包所在爲止 命名規則 示例
基礎組件 com.project.*** com.project.base、com.project.common
業務組件 com.project.*** com.project.login
對外暴露接口 com.project.interfaces.*** com.project.interfaces.login

注:interface爲關鍵字不能出現在包名中,所以此處使用interfaces!

三、類命名 補充Service下的類處理建議

組件內類命名在Google推薦的命名方式下,應該儘量體現此類的歸屬性。

示例
Application LoginApplication
Activity LoginAuthCodeActivity
Fragment LoginAuthCodeFragment
Adapter LoginAuthCodeAdapter

這裏應該儘量避免抽象命名。以LoginAuthCodeActivity舉例,一個應用中可能存在不止一個驗證碼頁面,如果直接使用AuthCodeActivity的形式,雖然由於組件間包名不同的原因,在打包時不會報錯,但在我們全局查找的時候就會出現多個AuthCodeActivity的類,這樣對於判斷類所處的組件有需要查看處於包名末尾的包名,這樣顯然不夠直觀高效。而具體業務命名可以直接快速的鎖定業務類。

類的開頭部分,應儘量保證存在註釋,註釋應該體現

  • 類的編寫時間
  • 類的作者
  • 類的大致功能,如果涉及核心業務類,應該有設計概述。

此處只作爲建議。

/**
* Create Time :   2020-5-5 11:11:11
* Author      :   XXX
* Describe    :   登錄業務,主要包含登錄時登錄信息驗證和跳轉註冊頁等業務。
*/

四、資源命名

由於Android Studio對於上下層依賴Module中的同名資源是不做衝突處理的,所以此處同樣建議使用組件名稱對資源名稱進行歸屬,避免上下級同名資源被覆蓋。

資源類型 示例
layout文件 login_activity_quicklogin.xml、login_activity_register.xml
anim文件 login_slide_in.xml
mipmap文件 login_btn_submit.png
string文件 < string name=“login_submit”>提交< /string>

另外對於良好設計的應用UI一般會給出主體顏色,和標準字體大小,以及對應的標準色。此部分建議下沉至lib_base中使用如上的命名方法命名,以此來統一應用整體風格。
在這裏插入圖片描述
另外我們也可能存在,一個組件內部是一個獨立的UI顯示風格,那麼此時建議將獨立的UI風格設計移回組件內部。

五、版本號依賴統一管理 切換到最新和AndroidX進行展示

隨着組件化的推進組件的數量會逐漸增減,此時如果使用一個組件一個build.gradle的形式對組件進行管理,顯然是低效,且容易出錯的。

此處給出通用配置gradle的模版。

文件名稱:config.gradle
存放位置:Project的最外層

/**
 *  全局統一配置文件
 */
ext {
    //true 每個業務Module可以單獨開發
    //false 每個業務Module以lib的方式運行
    //修改之後需要Sync方可生效
    isModule = false
    //版本號
    versions = [
            applicationId           : "com.wss.amd",        //應用ID
            versionCode             : 1,                    //版本號
            versionName             : "1.0.0",              //版本名稱

            compileSdkVersion       : 27,
            buildToolsVersion       : "27.0.3",
            minSdkVersion           : 17,
            targetSdkVersion        : 23,

            androidSupportSdkVersion: "27.1.1",
            constraintLayoutVersion : "1.1.1",
            runnerVersion           : "1.0.1",
            espressoVersion         : "3.0.1",
            junitVersion            : "4.12",
            annotationsVersion      : "24.0.0",
            javaSDKVersion          : 1.8,//javaSDK版本

            multidexVersion         : "1.0.2",
            butterknifeVersion      : "8.4.0",
            arouterApiVersion       : "1.4.0",
            arouterCompilerVersion  : "1.2.1",
            arouterannotationVersion: "1.0.4",
            eventbusVersion         : "3.0.0",
            novateVersion           : "1.5.5",
            loggerVersion           : "2.2.0",
            fastjsonVersion         : "1.1.54",
            immersionbarVersion     : "2.3.2-beta05",
            glideVersion            : "4.8.0",
            bannerVersion           : "2.1.4",
            javaxVersion            : "1.2",
            lombokVersion           : "1.16.6",
            greendaoVersion         : "3.2.2",
            pickerViewVersion       : "4.1.6",
            superAdapterVersion     : "3.6.8",
            scaleImageViewVersion   : "3.10.0",
            zxingViewVersion        : "1.3",
            xxpermissionsVersion    : "5.5",
            rx2JavaVersion          : "2.1.5",
            rx2AndroidVersion       : "2.0.1",

    ]
    dependencies = [
            "appcompat_v7"        : "com.android.support:appcompat-v7:${versions["androidSupportSdkVersion"]}",
            "constraint_layout"   : "com.android.support.constraint:constraint-layout:${versions["constraintLayoutVersion"]}",
            "runner"              : "com.android.support.test:runner:${versions["runnerVersion"]}",
            "espresso_core"       : "com.android.support.test.espresso:espresso-core:${versions["espressoVersion"]}",
            "junit"               : "junit:junit:${versions["junitVersion"]}",
            "support_annotations" : "com.android.support:support-annotations:${versions["annotationsVersion"]}",
            "design"              : "com.android.support:design:${versions["androidSupportSdkVersion"]}",
            "support-v4"          : "com.android.support:support-v4:${versions["androidSupportSdkVersion"]}",
            "cardview-v7"         : "com.android.support:cardview-v7:${versions["androidSupportSdkVersion"]}",
            "recyclerview-v7"     : "com.android.support:recyclerview-v7:${versions["androidSupportSdkVersion"]}",

            //方法數超過65535解決方法64K MultiDex分包方法
            "multidex"            : "com.android.support:multidex:${versions["multidexVersion"]}",

            //路由
            "arouter_api"         : "com.alibaba:arouter-api:${versions["arouterApiVersion"]}",
            "arouter_compiler"    : "com.alibaba:arouter-compiler:${versions["arouterCompilerVersion"]}",
            "arouter_annotation"  : "com.alibaba:arouter-annotation:${versions["arouterannotationVersion"]}",

            //黃油刀
            "butterknife_compiler": "com.jakewharton:butterknife-compiler:${versions["butterknifeVersion"]}",
            "butterknife"         : "com.jakewharton:butterknife:${versions["butterknifeVersion"]}",

            //事件訂閱
            "eventbus"            : "org.greenrobot:eventbus:${versions["eventbusVersion"]}",

            //網絡
            "novate"              : "com.tamic.novate:novate:${versions["novateVersion"]}",

            //日誌
            "logger"              : "com.orhanobut:logger:${versions["loggerVersion"]}",

            //fastJson
            "fastjson"            : "com.alibaba:fastjson:${versions["fastjsonVersion"]}.android",

            //沉浸式狀態欄
            "immersionbar"        : "com.gyf.immersionbar:immersionbar:${versions["immersionbarVersion"]}",

            //banner
            "banner"              : "com.bigkoo:ConvenientBanner:${versions["bannerVersion"]}",

            //圖片加載
//                    "picasso"             : "com.squareup.picasso:picasso:${versions["picassoVersion"]}",
            "glide"               : "com.github.bumptech.glide:glide:${versions["glideVersion"]}",

            //lombok
            "lombokJavax"         : "javax.annotation:javax.annotation-api:${versions["javaxVersion"]}",
            "lombok"              : "org.projectlombok:lombok:${versions["lombokVersion"]}",

            //數據庫
            "greenDao"            : "org.greenrobot:greendao:${versions["greendaoVersion"]}",

            //時間,地址,條件選擇器
            "pickerView"          : "com.contrarywind:Android-PickerView:${versions["pickerViewVersion"]}",

            //萬能Adapter
            "superAdapter"        : "org.byteam.superadapter:superadapter:${versions["superAdapterVersion"]}",

            //展示大圖+手勢滑動
            "scaleImageView"      : "com.davemorrissey.labs:subsampling-scale-image-view:${versions["scaleImageViewVersion"]}",

            //二維碼掃描
            "zxing"               : "com.github.0xZhangKe:QRCodeView:${versions["zxingViewVersion"]}",

            //危險權限庫
            "xxpermissions"       : "com.hjq:xxpermissions:${versions["xxpermissionsVersion"]}",

            //RX家族
            "rx2_java"            : "io.reactivex.rxjava2:rxjava:${versions["rx2JavaVersion"]}",
            "rx2_android"         : "io.reactivex.rxjava2:rxandroid:${versions["rx2AndroidVersion"]}",
    ]

}

此模版由Groovy語言編寫,主要使用的是List,Map數據結構。
如果感覺讀起來吃力可以看這篇帖子:https://blog.csdn.net/u010451990/article/details/105382861

六、依賴控制

Gradle 3.0開始,使用 implementation, api, runtimeOnly, compileOnly 進行依賴控制。

使用規範:
implementation: 用於組件範圍內的依賴,不與其他組件共享。(作用域是組件內,建議)
api: 用於基礎層的依賴,要穿透基礎層,暴露給組件層。(作用域是所有,不建議)
runtimeOnly: 用於 app 宿主殼的依賴,組件間是絕對隔離的,編譯時不可見,但參與打包。(無作用域,建議)
compileOnly: 用於高頻依賴,防止 already present 錯誤。一般是開源庫用來依賴 support 庫防止與客戶的 support 庫版本衝突的。

七、數據操作

數據庫模塊建議在各個組件內獨立實現,儘量不要做下沉處理。一個組件擁有一個獨立的數據庫,庫內可以創建相關的表。當我們使用註解類型的數據庫時,應該有意識的將含有註解的數據對象和原生數據對象區分開,即還有註解的對象只做數據庫交互操作,而UI刷新,數據回調,組件內外應該統一到無註解的原生數據對象上。

所在位置 示例
所在包 com.project.module.db
實體 com.project.module.db.bean
dao com.project.module.db.dao

其他規範可以參考:Auligelite 進行開發

八、組件版本控制

每個組件統一使用gradle.properties統一管理版本
初始版本爲:0.0.1,不要以1.0.0開始
測試版本格式:0.0.1-SNAPSHOT(SNAPSHOT 爲快照版本)
正式版本格式:0.0.1

九、關於Maven發佈

一個組件相對穩定後,把該組件打包成 aar,發佈到 github 或 maven,在本地 setting.gradle 註釋掉該組件的聲明,用 aar 替換原來的,可大幅減少編譯時間和運存佔用。

發佈可以在module的build.gradle中添加如下代碼,進行發佈。參數請換成自己的。

//////// 打包發佈配置開始 ////////
apply plugin: 'maven'
ext {
    // 從Github上clone下來的項目的本地地址
    GITHUB_REPO_PATH = ""       //這裏指定的就是剛剛clone下來的在本地的路徑
    PUBLISH_GROUP_ID = 'com.timecat.widget'
    PUBLISH_ARTIFACT_ID = 'widget-calendar'
    PUBLISH_VERSION = '0.0.1'
}
uploadArchives {
    repositories.mavenDeployer {
        def deployPath = file(project.GITHUB_REPO_PATH)
        repository(url: "file://${deployPath.absolutePath}")
        pom.project {
            groupId project.PUBLISH_GROUP_ID
            artifactId project.PUBLISH_ARTIFACT_ID
            version project.PUBLISH_VERSION
            // 使用:
            // implementation "com.timecat.widget:widget-calendar:1.0.0"
        }
    }
}
//////// 打包發佈配置結束 ////////

十、組件升級

組件的升級應該遵從:開放關閉原則

即我們對外開放的方法有改變時,不建議直接進行修改。而是通過重載或者新建方法的方式來實現。
如:

public void login(String username,String password);

增加參數時:

public void login(String username,String password,String token);

public void login(String username,String password);

對於廢棄的方法應該給出註解並給出取消原因

@Deprecated
public void login(String username,String password);

十一、組件加載順序

如果組件間存在先後加載順序依賴,如組件A加載的基礎是組件B先加載,那麼此時需要明確組件的優先級。
對於組件的優先級建議使用區間形式定義,如10000、20000、30000
方便優先級變化時,修改一個組件的優先級就可以了。儘量不要簡單的使用1、2、3、4這種形式。
另外建議將此部分也寫入到統一配置文件當中,方便在調整優先級或劃分優先級的時候產生問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章