Android從0到1實現模塊化開發,封裝(MVP、Retrofit2、Rxjava2、Arouter等)組合框架

題外話:這一兩年來隨着人工智能的火爆,越來越多的人都去做AI、人工智能什麼的,移動互聯網的風口位置也已讓賢,但是不在風口,我們也得堅持安卓下去,不是什麼火我們就往那裏拱(拱 二聲,四川話:去),新技術總有一天也會失去衆人的焦點被其他新事物所取代,所以堅持下去,我們會做的更好的。

前言:在我們日常開發中,當項目業務逐漸變得多起來,在app目錄下懟代碼感覺會越來越臃腫,有時還會耦合的很嚴重,最要命最痛苦的是編譯的時候,一個小改動改個字符串什麼的可能幾秒鐘就完成了,但等待編譯卻要好幾分鐘,更甚至十幾分鍾,這肯定是不能忍的,畢竟我們工作時間很寶貴,能不拖時間就不拖,免得影響下班時間,對吧。所以這個時候,安卓模塊化就顯得格外重要了。

注意:這裏只着重講怎麼去架構,還有常用的思路方法,深層次的原理等不大會涉及到,如果大家有需要看原理什麼的就去看其他大牛的講解哦!

一、模塊化解決的問題:

1.各個子module間互不依賴,形成解耦。

2.各個模塊作爲application時可以單獨運行,作爲library時,只提供給app模塊依賴打包使用。

3.便於各個開發人員間的合作開發。

4.各個模塊作爲application單獨編譯時間減少,程序員心情值up。

下面將根據demo來講解具體用法:

二、首先看到android目錄架構

包括上方紅框的基礎組件lib_common以及lib_opensource,lib_opensource作用是所有三方庫的一個依賴組件,lib_opensource只被lib_common依賴。lib_common作用是提供所有base類,包括但不限於封裝的mvp、網絡框架、基本配置類、utils、一些公共的輕量業務。當然如果說業務具有登錄註冊的,那麼也推薦放到lib_common裏面,當我們單獨編譯某一個模塊又必須要用到登錄時,那麼此時所有業務子模塊就直接依賴lib_common就足夠了,當我們需要整體編譯打包時,app模塊只需要依賴各個業務子模塊,不需要單獨去依賴lib_common。

1.怎麼去配置是否單獨編譯還是整體打包編譯:

我們需要去定義一個全局的常量,讓我們各個子模塊自動去識別這個常量就行。以往我們定義一個全局常量可能就只需要一個靜態類就搞定,那麼對於整個工程來說有沒有一個常量能夠被所有module所識別呢?答案是肯定的,Gradle自動構建工具有一個重要屬性,可以幫助我們完成這個事情。每當我們用AndroidStudio創建一個Android項目後,就會在項目的根目錄中生成一個文件 gradle.properties,我們將使用這個文件的一個重要屬性:在Android項目中的任何一個build.gradle文件中都可以把gradle.properties中的常量讀取出來;那麼我們在上面提到解決辦法就有了實際行動的方法,首先我們在gradle.properties中定義一個常量值 isBuildModule(是否是組件開發模式,true爲是,false爲否),在項目根目錄的 gradle.properties 中定義變量:

這裏需要注意的是,gradle.properties 中的數據類型都是String類型,使用其他數據類型需要自行轉換,也就是說我們讀到 isBuildModule是個String類型的值,而我們需要的是Boolean值,所以需要在子模塊的build.gradle 使用的地方中加入紅框代碼:

上面的isBuildModule 爲true表示,子模塊會以application存在,即可單獨打包編譯等,當爲false時,作爲library供application依賴使用。

此時還需要注意兩點:

1)當子模塊作爲application時有自己的包名,需要這個字段。而當子模塊作爲library時,子模塊的build.gradle中不需要包名applicationId這個字段,我們同樣根據isBuildModule字段來判斷:

2)manifest合併問題。當子模塊作爲library時,是由app主模塊啓動的,此時子模塊的manifest裏面不需要配置launch也不需要配置application的label、icon等。而子模塊作爲application時是自己本身啓動,需要在manifest配置launch以及application等。此時的清單文件不能同時滿足兩個條件,那麼我們就通過配置兩個不同的清單文件release版和debug版來解決這個問題。

在子module的main目錄下新建兩個文件夾,並將清單文件放入其中,如下:

             

debug 模式下的 AndroidManifest.xml :

<application
   ...
   >
   <activity
       android:name="com.senon.module_one.MainActivity"
       android:label="module_one">
       <intent-filter>
           <action android:name="android.intent.action.MAIN" />
           <category android:name="android.intent.category.LAUNCHER" />
       </intent-filter>
   </activity>
</application>

realease 模式下的 AndroidManifest.xml :

<application
   ...
   >
   <activity
       android:name="com.senon.module_one.MainActivity"
       android:label="module_one">
       <intent-filter>
           <category android:name="android.intent.category.DEFAULT" />
           <category android:name="android.intent.category.BROWSABLE" />
           <action android:name="android.intent.action.VIEW" />
           <data android:host="com.senon.module_one"
               android:scheme="router" />
       </intent-filter>
   </activity>
</application>

當寫好了兩個不同版本的清單文件之後,我們需要在對應的每個子模塊的build.gradle去配置不同版本的清單文件

如果項目中出現
No signature of method: static org.gradle.api.java.archives.Manifest.srcFile() is applicable for argument types: (java.lang.String) values: [src/main/debug/AndroidManifest.xml]時,是因爲manifest寫成了Manifest,把大寫改成小寫就ok了。

2.三方依賴庫如何使用:

上面提到的lib_opensource模塊是專門存放三方開源等依賴,使用方法一般是在根目錄新建一個gradle腳本文件,我們命名爲dependencies.gradle,我們把所有的三方依賴全部放在裏面,並定義新的名稱方便調用,如下:

劃重點:然後必須在項目工程根build.gradle中去應用這個dependencies.gradle

接下來,lib_common的build.gradle中依賴lib_opensource這個模塊,如下:

注意到上面紅框裏面了嗎? 依賴用的是api 而不是implementation。爲什麼尼?

原因是安卓3.0以後google爲了安卓的規範,而使用了api和implementation來替代以前的compile,api和以前的compile作用是一樣的,而implementation的出現是爲了依賴私有化(我個人這樣理解的)。如果B implementation了A,那麼B可以調用到A中的api接口(也就是A中的各種類,接口之類的),此時如果C implementation了B,那麼C是不能夠調用到A中的api接口的,所以說implementation是保護了A中的api接口的,這樣做增加了項目的構建速度,非常棒。

回過來,如果我們想要C也可以調用A的接口,怎麼辦尼?這時就應該用api了,B api A,C implementation B,此時C就能夠調用A的api接口了。現在發散下思維(偷笑),如果D implementation C,D想調用A中的api接口怎麼辦?答案是B api A,C api B,D implementation/api C。

3.資源名稱命名

在模塊化中,會出現這種情況,主module依賴了module_one、module_two,one、two同時又一張叫logo的圖片,主module需要調用two中的logo圖片,調用並運行程序,此時打開app一看,發現logo顯示的不是two中的那一張,程序也沒有報錯,這是怎麼回事呢?原因是程序不知道到底要調用哪一個logo圖,所以調用出現混亂。

解決這個的辦法,使用resourcePrefix約束資源前綴,來防止衝突,此時在所有子模塊的build.gradle的android下增加各自的資源前綴 ,一般使用module+下劃線_組成,比如one_

此時我們子模塊下面res文件夾下面的所有文件命名全部要用one_爲前綴,比如one_activity_main.xml,包括layout、value、drawable、mipmap等裏面的所有文件。

 

三、模塊間的跳轉問題

由於子模塊之間是禁止互相依賴的,所以他們之間不能跳轉、傳值等,一般來說解決這個問題有幾種方法,一是隱式跳轉,但不方便傳值及後期維護修改,二是最常用的路由跳轉,我們項目中所用的路由跳轉爲阿里巴巴開源ARouter,使用起來比較簡單:

1,添加依賴和配置

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}
dependencies {
    // x.x.x替換成最新版本, 需要注意的是api
    // 要與compiler匹配使用,均使用最新版可以保證兼容
    compile 'com.alibaba:arouter-api:x.x.x'
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
    ...
}

2,初始化路由,一般在Application中。

if (isDebug()) {           // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
    ARouter.openLog();     // 打印日誌
    ARouter.openDebug();   // 開啓調試模式(如果在InstantRun模式下運行,必須開啓調試模式!線上版本需要關閉,否則有安全風險)
}
ARouter.init(mApplication); // 儘可能早,推薦在Application中初始化

3,定義爲被跳轉的目標頁面註冊路由:

一般的路由地址由兩級或多級構成,我們可以統一規定路徑地址爲 /模塊名/包名/類名,也可以直接定義爲  /模塊名/類名,只要類名不重複就ok,具體如何使用請查看ARouter官方文檔。

我這裏要講的是如何封裝路由地址,封裝過後對以後的迭代有幫助。我們在lib_common建一個常量類,所以路由跳轉均寫入本ConstantArouter方便調用, 我們規定路由跳轉命名統一用:path+模塊名+Activity名

我們在目標頁面註冊路由就可以改爲 @Route(path = ConstantArouter.PATH_APP_FRAGMENTHOMEACTIVITY)

注意:如果出現如下錯誤

com.android.dex.DexException: Multiple dex files define Lcom/alibaba/android/arouter/routes/ARouter$$Group$$module

運行時出這個異常是因爲不同模塊有相同分組導致的,例如AMoudle定義了@Route(path = “/module/**”),BMoudle也定義了@Route(path = “/module/***)就會出現這個問題。

附上demo的github地址 https://github.com/senonwx/ModularApp

最後我也用此架構完成了玩安卓app,github地址 https://github.com/senonwx/WanAndroid

 

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