Android 元件化探索與思考

吳小龍同學發表於2017-08-02

前言

開發中,我習慣性會把一個模組的功能放在一個包下,便於查詢,但煩於耦合性太高,後期維護太費勁,因此對專案進行元件化拆分勢在必行。元件化好處:便於開發,團隊成員只關注自己的開發的小模組,降低耦合性,後期維護方便等。相當於先有很多小元件,各自開發,最後組裝,成一個 app。

關係圖



app:殼工程;
module1:元件1;
module2:元件2;
common:第三方庫,公用工具、自定義 View、主題等。

效果預覽


元件化過程很容易想到一些問題,比如 module1 我想單獨除錯怎麼做?module1 有頁面需要跳轉到module2怎麼辦等。接下來,我一一探索,提供解決方案。

全域性設定 Gradle

如果有很多專案,可以設定全域性來統一管理版本號或依賴庫,這樣就不用一個個去改了,根目錄下 build.gradle 新增:

def androidSupportVersion = '25.3.1'

ext {
    //編譯的 SDK 版本,如API20
    compileSdkVersion = 25
    //構建工具的版本,其中包括了打包工具aapt、dx等,如API20對應的build-tool的版本就是20.0.0
    buildToolsVersion = "26.0.0"
    //相容的最低 SDK 版本
    minSdkVersion = 14
    //向前相容,儲存新舊兩種邏輯,並通過 if-else 方法來判斷執行哪種邏輯
    targetSdkVersion = 22
    appcompatV7 = "com.android.support:appcompat-v7:$androidSupportVersion"
    constraintLayout = 'com.android.support.constraint:constraint-layout:1.0.2'
}複製程式碼

其中module/build.gradle:

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
    //……
}複製程式碼

資源名重名

每個 module 都有 appname,為了不讓資源名重名,可以在每個元件的 build.gradle 中增加 resourcePrefix "xxx",固定每個元件的資源字首。但是 resourcePrefix 這個值只能限定 xml 裡面的資源,並不能限定圖片資源,所有圖片資源仍然需要你手動去修改資源名。不過我更建議把圖片、 strings、 colors、dimens 等資源放到 common 去,可以防止不同的資源名字卻對應了同一資源值。

元件單獨除錯

application 與 library 切換

module1 在開發階段應該 application,等 release 後才是 library,這裡可以設定一個變數控制下,在根專案 gradle.properties 加入:

# 元件單獨除錯開關,true 可以,false 不可以,需要點選 "Sync Project"。
isDebug=false複製程式碼

module1/build.gradle:

if (isDebug.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    //……    
}複製程式碼

applicationId

開發階段,module1 還必須有個 applicationId:

android {
     //……
    defaultConfig {
        // 作為library時不能有applicationId,只有作為一個獨立應用時才能夠如下設定
        if (isDebug.toBoolean()){
            applicationId "com.wuxiaolong.module1"
        }
        //……
        }
}複製程式碼

入口類

到這裡還不行,還得有 AndroidManifest 設定入口類,release 後這個 AndroidManifest 不需要打包進去,新建檔案 debug,然後在 build.gradle 指定路徑:

android {
    //……  
    sourceSets {
        main {
            if (isDebug.toBoolean()) {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
                java {
                    //release 時 debug 目錄下檔案不需要合併到主工程
                    exclude 'debug/**'
                }
            }
        }
    }
}複製程式碼

另外,module 可能會需要使用到自定義的 Application,release 同樣也不需要打包進去,不然合併會有衝突。

元件間通訊

元件間通訊包括兩個場景:(1)UI 跳轉;(2)呼叫元件某個類的某個方法。
這裡涉及路由,何為路由,就是頁面請求,都交給它處理。網上有很多路由庫,我這裡選的是阿里的 ARouter,ARouter 能解決上面的問題,但是也遺留一個問題,我獨立執行 module1 時,想訪問 module2 頁面就做不到了,Router 不支援跨程式訪問,這個問題待定,也可能是我使用 ARouter 姿勢不對,如果您能做到,望告知。

ARouter 使用

1、common

dependencies {
    //arouter
    compile rootProject.ext.arouterApi
}複製程式碼

2、元件
app 和 module 都需要加入:

android {
    defaultConfig {
        //arouter
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }

dependencies {
    //arouter
    annotationProcessor rootProject.ext.arouterCompiler
}複製程式碼

3、使用
sample 列出了元件跳轉、元件跳轉-帶引數、獲取 Frgment、呼叫元件某個類的使用方法,詳見我的 GitHub 分享。
詳細使用請閱讀 ARouter,不得不吐槽,文件寫的不是一般的爛。

library 重複依賴

module1 和 module2 分別都依賴了 common,會不會導致 library 重複依賴呢,想必大家也有這個疑問了,實際上在 release 構建 APP 的過程中 Gradle 會自動將重複的 aar 包排除,APP 中也就不會存在相同的程式碼了,可以打包反編譯驗證下,我試了,確實沒有重複依賴。

ButterKnife

Attribute value must be constant

在 Android Studio 的 library 的 module 中無法使用 ButterKnife。
網上說用 R2 替代(為什麼能用 R2?),但都沒有說 R2 怎麼生成的?這篇《butterknife在library中使用問題處理》文章說使用 android-apt,確實可行,但是帶來一個新坑,發現 apply plugin: 'android-apt' 與 arouter 衝突,這時候 arouter 失效了。正確姿勢,用 Android ButterKnife Zelezny 外掛生成,手動改成 R2,clean 下就 OK,感謝群裡的小夥伴提示。

OnClick 方法

ButterKnife 還有個坑,OnClick 方法中同樣使用 R2,但是找 id 的時候使用 R,然而 library 中是不能使用 switch- case 找 id 的(原因:《在Android library中不能使用switch-case語句訪問資源ID的原因分析及解決方案》),可以使用 if-else:

  @OnClick({R2.id.module1_button, R2.id.module1_button2})
    public void onViewClicked(View view) {
        int id = view.getId();
        Log.d("wxl","id="+id);
        if (id == R.id.module1_button) {
            toastShow("module1_button");
        } else if (id == R.id.module1_button2) {
            toastShow("module1_button2");
        }
    }複製程式碼

當你寫 switch- case 時,Android Studio 也有提示,可以一鍵轉換成 if-else。

原始碼

github.com/WuXiaolong/…

最後

1、擼了一次元件化,感覺自己好菜比,好多東西還需要學習,遺留:(1)、每個 module 的配置最好有個固定模版,這樣新建 module 就不用一一配置了;(2)、關於註解與依賴注入,不明不白,導致元件間通訊花費了太多時間,後續要系統學習下這塊知識。
2、可能還有未知的坑,大家可以 Star ModularSample,我會持續更新。
3、網上元件化文章不少,但優秀的文章屈指可數,很多隻是講元件化思想,點到即止,最討厭這種半藏著半掖式分享,感覺他們在耍流氓。對於那些無私願意分享的人,我一直都是很欽佩的,因為有他們,讓我們這些後人在開發的路上不孤單無助。
4、熟悉我的朋友,可能知道我在無錫,二線城市,總感覺技術很落後,所以我一直要保持學習,不知道元件化是不是在大城市在專案中運用很普遍?據說所知,無錫元件化用的很少,理論上在一線城市會處在技術前沿。
5、很多朋友說我文章總是會一個難點講的通俗易懂,其實不知道我在易懂的背後做了多少實踐做支撐,實踐得真理,我是相信這句話的。

參考

Android元件化方案

公眾號

相關文章