1. 程式人生 > >從零開始搭建Android元件化框架

從零開始搭建Android元件化框架

問題

在已經開發過幾個專案的童鞋,如果這時需要重新開發一個新專案,是否需要自己重新搭建框架呢,還是從老專案中拷貝貼上? 我們是否可以封裝一個底層的lib庫,這個底層的公共基礎庫 包括了一些第三方庫(如: okhttp, retrofit2, glide 等)的初始化及簡單的封裝和一些公共的base類.這樣我們重新開發一個新專案只要依賴這個庫就馬上可以進行業務邏輯的開發了.

什麼是元件化

元件化簡單概括就是把一個功能完整的 App 或模組拆分成多個子模組, 每個子模組可以獨立編譯和執行, 也可以任意組合成另一個新的 App 或模組, 每個模組即不相互依賴但又可以相互互動, 遇到某些特殊情況甚至可以升級或者降級.
大家可以點選該文章檢視 [元件化框架簡介] (

https://www.jianshu.com/p/40e745038471)

前言

從今年開始接觸元件化專案,剛開始感覺元件化非常的高大上,經過一段時間的瞭解,現在對元件化終於有了一定的瞭解,為了能更熟悉運用於實際的專案中,決定自己寫一個demo框架,也想解決上述的問題,這章文件主要是對我所學習的內容做一個總結,鞏固Android的一些基礎知識,非常適合初學者,並且架構簡單,學習成本低,對於一個急需快速元件化拆分的專案是很適合的. 望高手們多指教!
附上 Demo地址 Github : 您的 Star 是我堅持的動力 ✊

請使用 Android studio 3.0 以上版本

老規則,先上效果圖

專案效果圖.gif
備註:該Demo只完成十分之一,有時間會一直更新

一,demo 架構圖詳解

系統架構設計.png

上圖是元件化工程模型,下面會列舉一些元件化工程中用到的名詞的含義:

整合模式: 所有的業務元件被“app殼工程”依賴,組成一個完整的APP;
元件模式: 可以獨立開發業務元件,每一個業務元件就是一個APP;
app殼工程: 負責管理各個業務元件,和打包apk,沒有具體的業務功能;
業務元件: 根據公司具體業務而獨立形成一個的工程;
Main元件:屬於業務元件,指定APP啟動頁面、主介面 ;
Common元件: 也就是功能元件(component_base 模組),支撐業務元件的基礎,提供多數業務元件需要的功能,例如提供網路請求功能;
component_data元件: 這裡我存放了與專案相關的公共資料,例如bean的基類,IntentKV存資料的鍵值對等.
SDK元件: 整合微信,支付寶支付,分享,推送等常用的第三方框架.

元件化的優點

Android APP元件化架構的目標是告別結構臃腫,讓各個業務變得相對獨立,業務元件在元件模式下可以獨立開發,而在整合模式下又可以變為arr包整合到“app殼工程”中,組成一個完整功能的APP;
從元件化工程模型中可以看到,業務元件之間是獨立的,沒有關聯的,這些業務元件在整合模式下是一個個library,被app殼工程所依賴,組成一個具有完整業務功能的APP應用,但是在元件開發模式下,業務元件又變成了一個個application,它們可以獨立開發和除錯,由於在元件開發模式下,業務元件們的程式碼量相比於完整的專案差了很遠,因此在執行時可以顯著減少編譯時間。

Arouter路由.png

這是元件化工程模型下的業務關係,業務之間將不再直接引用和依賴,而是通過“路由”這樣一箇中轉站間接產生聯絡,而Android中的路由實際就是對URL Scheme的封裝;
對阿里巴巴的Arouter不熟悉的可以點選瞭解:https://github.com/alibaba/ARouter

二, 專案中base基類和Libraries的簡介

專案目錄.png component_base基礎庫.png 第三方依賴.png

對於Android中常用的基類庫,主要包括開發常用的一些框架。

  • 該專案基於目前比較流行的框架:Material Design + MVP + Rxjava2 + Retrofit2 + GreenDao + Glide

  • 部分 Base基類,Libraries 簡介

1、Base 基類(BaseMVPActivity, BaseMVPFragment, BasePermissionActivity,
BaseListFragment, BaseTabListFragment, BaseObserver…)
2、MVP 基類(BaseView, BasePresenter …)
3、Retrofit2 + RxJava2 的封裝 https://github.com/square/retrofit
4、Autolayout 鴻洋大神的Android全尺寸適配框架.
5、安卓除錯神器-Stetho(Facebook出品 建議使用) https://github.com/facebook/stetho
6、LocaleHelper 的封裝,多語言包括阿拉伯語,從右到左佈局,參考:https://juejin.im/entry/599397c5f265da2480332362
7、Logger 網路日誌的簡單封裝 https://github.com/orhanobut/logger
8、通用的工具類
9、自定義view(包括對話方塊,ToolBar佈局,圓形圖片等view的自定義)
11、其他等等

三,元件化搭建流程

好了,前面廢話了很多,下面才開始我們真正的元件化搭建過程,首先我們來看看元件模式和整合模式切換的實現:

app殼的config.gradle部分程式碼

ext是自定義屬性,把所有關於版本的資訊都利用ext放在另一個自己新建的gradle檔案中集中管理

1)元件模式和整合模式的轉換最終使用方式

    isModule = false
    moduleShopMall = false
    moduleShopCart = false
    moduleWelfare = false

    isModule false;  表示整個app執行, true: 表示單獨執行某一個module
    moduleShopMall;  false:作為Lib元件存在,true:作為application存在(其他  
    module同理)
整合模式

1, 首先需要在 config.gradle 檔案中設定 appDebug = false
2, 然後 Sync 下
3, 最後選擇 app 執行即可

元件模式

1, 首先需要在 config.gradle 檔案中設定 appDebug = true
2, 然後 Sync 下
3, 最後相應的模組(moduleShopMall 、moduleShopCart 、moduleWelfare )進行執行即可

2)元件化的配置流程

Android Studio中的Module主要有兩種屬性,分別為:

application屬性,可以獨立執行的Android程式,也就是我們的APPapply plugin: ‘com.android.applicationlibrary屬性,不可以獨立執行,一般是Android程式依賴的庫檔案;
apply plugin: ‘com.android.library

Module的屬性是在每個元件的 build.gradle 檔案中配置的,當我們在元件模式開發時,業務元件應處於application屬性,這時的業務元件就是一個 Android App,可以獨立開發和除錯;而當我們轉換到整合模式開發時,業務元件應該處於 library 屬性,這樣才能被我們的“app殼工程”所依賴,組成一個具有完整功能的APP;

1: 元件模式和整合模式的轉換

我們在AndroidStudio建立一個Android專案後,新建了config.gradle檔案,並配置ext 管理整個專案的常量,那麼在Android專案中的任何一個build.gradle檔案中都可以把config.gradle中的常量讀取出來, 如下程式碼:

app殼的build.gradle

注意:每次改變isModule的值後,都要同步專案才能生效;

2: 元件之間AndroidManifest合併

在 AndroidStudio 中每一個元件都會有對應的 AndroidManifest.xml,用於宣告需要的許可權、Application、Activity、Service、Broadcast等,當專案處於元件模式時,業務元件的 AndroidManifest.xml 應該具有一個 Android APP 所具有的的所有屬性,尤其是宣告 Application 和要 launch的Activity,但是當專案處於整合模式的時候, 我們要為元件開發模式下的業務元件再建立一個AndroidManifest.xml,然後根據isModule指定AndroidManifest.xml的檔案路徑,讓業務元件在整合模式和元件模式下使用不同的AndroidManifest.xml,這樣表單衝突的問題就可以規避了.如下module目錄結構及:

module目錄結構

上圖是元件化專案中一個標準的業務元件目錄結構,首先我們在main資料夾下建立一個module資料夾用於存放元件開發模式下業務元件的 AndroidManifest.xml,而 AndroidStudio 生成的 AndroidManifest.xml 則依然保留,並用於整合開發模式下業務元件的表單;然後我們需要在業務元件的 build.gradle 中指定表單的路徑,程式碼如下:

   /*java外掛引入了一個概念叫做SourceSets,通過修改SourceSets中的屬性,可以指定哪些原始檔
    (或資料夾下的原始檔)要被編譯,哪些原始檔要被排除。*/
    sourceSets {
        main {
            if (rootProject.ext.moduleShopMall) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    //排除java/debug資料夾下的所有檔案
                    exclude '*module'
                }
            }
        }
    }

這樣在不同的開發模式下就會讀取到不同的 AndroidManifest.xml ,然後我們需要修改這兩個表單的內容以為我們不同的開發模式服務。

3: 全域性Context的獲取及元件資料初始化

當Android程式啟動時,Android系統會為每個程式建立一個 Application 類的物件,並且只建立一個,application物件的生命週期是整個程式中最長的,它的生命週期就等於這個程式的生命週期。在預設情況下應用系統會自動生成 Application 物件,但是如果我們自定義了 Application,那就需要在 AndroidManifest.xml 中宣告告知系統,例項化的時候,是例項化我們自定義的,而非預設的,如下圖:

元件化的AndroidManifest.xml

android:name屬性——是用來設定所有activity屬於哪個application的,預設是android.app.Application,那麼當我們是元件化專案我們就指定我們建立的module下的MyApp,該MyApp繼承於基類的BaseApplication.

元件化的appliction

BaseApplication 主要用於各個業務元件和app殼工程中宣告的 Application 類繼承用的,只要各個業務元件和app殼工程中宣告的Application類繼承了 BaseApplication,當應用啟動時 BaseApplication 就會被動例項化,這樣從 BaseApplication 獲取的 Context 就會生效,也就從根本上解決了我們不能直接從各個元件獲取全域性 Context 的問題;

4: library依賴問題

在元件化工程模型圖中,我是把所有公共的功能元件全部放在component_base裡面,我是想後期其他專案需要用到只依賴這個module就可以了,不需要依賴多個,這也有致命的缺點,就是有過多的依賴,不夠單一,目前為了方便先這樣做了.

上面我們有介紹了自定義 config.gradle 配置來統一管理我們的版本, 所有在每個元件化專案中都依賴這個基礎庫component_base,如下程式碼

//公用依賴包
    implementation project(':component_base')
    implementation project(':component_data')

而我們的component_base 的build.gradle的程式碼如下:

apply plugin: 'com.android.library'
apply plugin: 'me.tatarka.retrolambda'  //lambda配置
apply plugin: 'com.jakewharton.butterknife'

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion rootProject.ext.android.buildToolsVersion

    defaultConfig {
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        //MultiDex分包方法
        multiDexEnabled true

        //Arouter路由配置
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }

    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    //防止編譯的時候oom、GC
    dexOptions {
        javaMaxHeapSize "4g"
    }

    //解決.9圖問題
    aaptOptions {
        cruncherEnabled = false
        useNewCruncher = false
    }

}
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    api rootProject.ext.dependencies["appcompat_v7"]
    api rootProject.ext.dependencies["constraint_layout"]
    api rootProject.ext.dependencies["cardview-v7"]
    api rootProject.ext.dependencies["design"]
    testApi rootProject.ext.dependencies["junit"]
    androidTestApi rootProject.ext.dependencies["runner"]
    androidTestApi rootProject.ext.dependencies["espresso_core"]

    //MultiDex分包方法
    api rootProject.ext.dependencies["multidex"]
    //Arouter路由
    annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
    api rootProject.ext.dependencies["arouter_api"]
    api rootProject.ext.dependencies["arouter_annotation"]
    //網路
    api rootProject.ext.dependencies["retrofit2"]
    api rootProject.ext.dependencies["converter-gson"]
    api rootProject.ext.dependencies["adapter-rxjava2"]
    api rootProject.ext.dependencies["rxjava2:rxandroid"]
    api rootProject.ext.dependencies["rxjava2"]
    api rootProject.ext.dependencies["logging-interceptor"]
    //黃油刀
    annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]
    api rootProject.ext.dependencies["butterknife"]
    //日誌
    api rootProject.ext.dependencies["logger"]
    //仿ios進度條
    api rootProject.ext.dependencies["kprogresshud"]
    //6.0許可權
    api rootProject.ext.dependencies["permissionsdispatcher"]
    api rootProject.ext.dependencies["baseRecyclerViewAdapterHelper"]
    //圖片
    api rootProject.ext.dependencies["glide"]
    //圖片縮放,View Pager中瀏覽庫
    api rootProject.ext.dependencies["photoview"]
    //仿ios 的PickerView時間選擇器和條件選擇器
    api rootProject.ext.dependencies["pickerView"]
    //上拉載入
    api rootProject.ext.dependencies["smartRefreshLayout"]
    api rootProject.ext.dependencies["SmartRefreshHeader"]
    //eventbus 釋出/訂閱事件匯流排
    api rootProject.ext.dependencies["eventbus"]
    //banner輪播圖
    api rootProject.ext.dependencies["banner"]
    //RecyclerView萬能介面卡
    api rootProject.ext.dependencies["baseRecyclerViewAdapterHelper"]
    //Android螢幕適配
    api rootProject.ext.dependencies["autolayout"]
    //安卓除錯神器-Stetho
    api rootProject.ext.dependencies["stetho"]
    api rootProject.ext.dependencies["stetho-okhttp3"]

    //公共資料
    implementation project(':component_data')
    }

我們還是要考慮另一個情況,我們在build.gradle中compile的第三方庫,例如AndroidSupport庫經常會被一些開源的控制元件所依賴,而我們自己一定也會compile AndroidSupport庫 ,這就會造成第三方包和我們自己的包存在重複載入,解決辦法就是找出那個多出來的庫,並將多出來的庫給排除掉,而且Gradle也是支援這樣做的,分別有兩種方式:根據元件名排除或者根據包名排除,下面以排除support-v4庫為例:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
        exclude module: 'support-v4'//根據元件名排除
        exclude group: 'android.support.v4'//根據包名排除
    }
}
5 Android Studio 3.0開始Gradle的配置
  • 這裡介紹一下:android gradle tools 3.X 中依賴,implement、api 和compile區別

2017 年google 後,Android studio 版本更新至3.0,更新中,連帶著com.android.tools.build:gradle 工具也升級到了3.0.0,在3.0.0中使用了最新的Gralde 4.0 里程碑版本作為gradle 的編譯版本,該版本gradle編譯速度有所加速,更加欣喜的是,完全支援Java8。當然,對於Kotlin的支援,在這個版本也有所體現,Kotlin外掛預設是安裝的。

在com.android.tools.build:gradle 3.0 以下版本依賴在gradle 中的宣告寫法

compile fileTree(dir: 'libs', include: ['*.jar'])

但在3.0後的寫法為

implementation fileTree(dir: 'libs', include: ['*.jar'])
或
api fileTree(dir: 'libs', include: ['*.jar'])

在3.0版本中,compile 指令被標註為過時方法,而新增了兩個依賴指令,一個是implement 和api,這兩個都可以進行依賴新增,他們的區別是:

  • api 指令: 完全等同於compile指令,沒區別,你將所有的compile改成api,完全沒有錯,它是對外部公開的。
  • implement指令: 這個指令的特點就是,對於使用了該命令編譯的依賴,對該專案有依賴的專案將無法訪問到使用該命令編譯的依賴中的任何程式,也就是將該依賴隱藏在內部,而不對外部公開。
  • 從Android Studio 3.0開始,使用annotationProcessor代替apt。不可再使用apt,否則會編譯報錯。
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //把implementation 用api代替,它是對外部公開的, 所有其他的module就不需要    
    //新增該依賴
    api rootProject.ext.dependencies["appcompat_v7"]
    api rootProject.ext.dependencies["constraint_layout"]
    api rootProject.ext.dependencies["cardview-v7"]
    api rootProject.ext.dependencies["design"]
    testApi rootProject.ext.dependencies["junit"]
    androidTestApi rootProject.ext.dependencies["runner"]
    androidTestApi rootProject.ext.dependencies["espresso_core"]
}
6 元件之間呼叫和通訊

在元件化開發的時候,元件之間是沒有依賴關係,我們不能在使用顯示呼叫來跳轉頁面了,因為我們元件化的目的之一就是解決模組間的強依賴問題,假如現在要從A業務元件跳轉到業務B元件,並且要攜帶引數跳轉,這時候就需要引入“路由”的概念了.目前專案使用了阿里巴巴的Arouter路由,有興趣的童鞋也可以去了解其他的”路由”框架,比如開源庫的ActivityRouter, LiteRouter 路由框架 , AndRouter 路由框架 等.

7 元件化中的butterKnife的坑
  • 特別注意不要使用最新的8.8.1版本,而應該使用8.4.0 ,因為最新的版本好像不相容元件化模式.按照8.4.0版本的方式依賴.
    第一步:
    在專案的buid.gradle中新增這兩個依賴.
buildscript {
  dependencies {
        classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
   classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
  }
}

第二步:
在module的build.gredle 檔案中的dependencies標籤中新增

 annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0',
 implementation 'com.jakewharton:butterknife:8.4.0',

第三步:
在module的build.gredle 檔案中新增

apply plugin: 'com.jakewharton.butterknife'

這三步就完成了butterKnife的接入了,如果你的Android studio 3.x 還沒安裝butterKnife的外掛的,就先安裝一下外掛.

  • 元件化中的butterKnife的使用

1、用R2代替R findviewid

  @BindView(R2.id.view_pager)
    ViewPager mViewPager;
    @BindView(R2.id.bottom_navigation_view)
    BottomNavigationView mBottomNavigationView;
    @BindView(R2.id.nav_view)
    NavigationView mNavView;

每次都要syn一下,才會生效的.

2、在click方法中同樣使用R2,但是找id的時候使用R。

@OnClick({R2.id.textView, R2.id.button1})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.textView:
                break;
            case R.id.button1:
                break;
        }
    }

3、特別注意library中switch-case的使用,在library中是不能使用switch- case 找id的,解決方法就是用if-else代替。

@OnClick({R2.id.textView, R2.id.button1, R2.id.button2})
    public void onViewClicked(View view) {
        int i = view.getId();
        if (i == R.id.textView) {

        } else if (i == R.id.button1) {

        } else if (i == R.id.button2) {

        } 

就這樣通過幾個簡單的步驟基本就完成了元件化的配置了. 具體可以執行專案檢視.

元件化專案的混淆方案

元件化專案的Java程式碼混淆方案採用在整合模式下集中在app殼工程中混淆,各個業務元件不配置混淆檔案。整合開發模式下在app殼工程中build.gradle檔案的release構建型別中開啟混淆屬性,其他buildTypes配置方案跟普通專案保持一致,Java混淆配置檔案也放置在app殼工程中,各個業務元件的混淆配置規則都應該在app殼工程中的混淆配置檔案中新增和修改。

之所以不採用在每個業務元件中開啟混淆的方案,是因為 元件在整合模式下都被 Gradle 構建成了 release 型別的arr包,一旦業務元件的程式碼被混淆,而這時候程式碼中又出現了bug,將很難根據日誌找出導致bug的原因;另外每個業務元件中都保留一份混淆配置檔案非常不便於修改和管理,這也是不推薦在業務元件的 build.gradle 檔案中配置 buildTypes (構建型別)的原因。

四,知識點彙總

1) Retrofit2 的封裝

2) Greendao 的使用

該demo會持續優化更新,把知識點及工具類都彙總於該demo中,便於學習及日後查詢.

** 如果你覺得這篇文章對你有幫助或啟發,請點下關注,謝謝 _ **

感謝以下文章提供的幫助: