作者:popular_linda
鏈接:https://juejin.im/post/5eb019b8e51d45338806f2c0
什么是組件
項目按功能拆分成功若干個組件,每個組件負責相應的功能,如login、pay、live。組件化與模塊化類似,但不同的是模塊化是以業務為導向,組件化是以功能為導向。組件化的顆粒度更細,一個模塊里可能包含多個組件。實際開發中一般是模塊化與組件化相結合的方式。
為什么要組件
(1)提高復用性避免重復造輪子,不同的項目可以共用同一組件,提高開發效率,降低維護成本。
(2)項目按功能拆分成組件,組件之間做到低耦合、高內聚,有利于代碼維護,某個組件需要改動,不會影響到其他組件。
組件化方案
組件化是一種思想,團隊在使用組件化的過程中不必拘泥于形式,可以根據自己負責的項目大小和業務需求的需要制定合適的方案,如下圖就是一種組件化結構設計。
-
宿主App
在組件化中,app可以認為是一個入口,一個宿主空殼,負責生成app和加載初始化操作。
-
業務層
每個模塊代表了一個業務,模塊之間相互隔離解耦,方便維護和復用。
-
公共層
既然是base,顧名思義,這里面包含了公共的類庫。如Basexxx、Arouter、ButterKnife、工具類等
-
基礎層
提供基礎服務功能,如圖片加載、網絡、數據庫、視頻播放、直播等。
注:以上結構只是示例,其中層級的劃分和層級命名并不是定性的,只為更好的理解組件化。
組件化面臨的五問題
一,跳轉和路由
Activity跳轉分為顯示和隱示:
//顯示跳轉Intent intent = new Intent(cotext,LoginActivity.class);startActvity(intent)
//隱示跳轉Intent intent = new Intent;intent.setClassName("app包名" , "activity路徑");intent.setComponent(new Component(new Component("app報名" , "activity路徑")));startActivity(intent);
1、顯示跳轉,直接依賴,不符合組件化解耦隔離的要求。
2、對于隱示跳轉,如果移除B的話,那么在A進行跳轉時就會出現異常崩潰,我們通過下面的方式來進行安全處理
//隱示跳轉Intent intent = new Intent;intent.setClassName("app包名" , "activity路徑");intent.setComponent(new Component(new Component("app報名" , "activity路徑")));if (intent.resolveActivity(getPackageManager) != ) {startActivity(intent);}startActivity(intent);
原生推薦使用隱示跳轉,不過在組件化項目中,為了更優雅的實現組件間的頁面跳轉可以結合路由神器ARouter,ARouter類似中轉站通過索引的方式無需依賴,達到了組件間解耦的目的。
Aouter使用方式如下:
1、因為ARouter是所有模塊層組件都會用到所以我們可以在Base中引入
api 'com.alibaba:arouter-api:1.5.0'annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
2、在每個子module里添加
Android {defaultConfig {...JAVACompileOptions {annotationProcessorOptions {arguments = [AROUTER_MODULE_NAME: project.getName()]}}}}
annotationProcessor會通過javaCompileOptions這個配置來獲取當前module的名字。
3、在Appliction里對ARouter進行初始化,因為ARouter是所有的模塊層組件都會用到,所以它的初始化放在BaseAppliction中完成。
public class BaseApplication extends Application {@Overridepublic void onCreate {super.onCreate;initRouter(this);}public void initRouter(Application application) {if (BuildConfig.DEBUG) { // 這兩行必須寫在init之前,否則這些配置在init過程中將無效ARouter.openLog; //打印日志ARouter.openDebug; // 開啟調試模式(如果在InstantRun模式下運行,必須開啟調試模式!線上版本需要關閉,否則有安全風險)}ARouter.init(application); //盡可能早,推薦在Application中初始化}}
4、在Activity中添加注解Route
public interface RouterPaths {String LOGIN_ACTIVITY = "/login/login_activity";}
// 在支持路由的頁面上添加注解(必選)// 這里的路徑需要注意的是至少需要有兩級,/xx/xx@Route(path = RouterPaths.LOGIN_ACTIVITY)public class LoginActivity extends BaseActivity {}
path是指跳轉路徑,要求至少兩級,即/xx/xx的形式,第一個xx是指group,如果不同module中出現相同的group會報錯,所以建議group用module名稱標識。
5、發起跳轉操作
ARouter.getInstance.build(RouterPaths. LOGIN_ACTIVITY).navigation;
ARouter的還有很多其他功能,這里不作詳細說明。
二,Aplication動態加載
Application作為程序的入口通常做一些初始化,如上面提到的ARouter,由于ARouter是所有模塊層組件都要用到,所以把它放在BaseApplication進行初始化。如果某個初始化操作只屬于某個模塊,為了降低耦合,我們應該把該初始化操作放在對應模塊module的Application里。如下:
1、在BaseModule定義接口
public interface BaseApplicationImpl {void init;...}
2、在ModuleConfig中進行配置
public interface ModuleConfig {String LOGIN = "com.linda.login.LoginApplication";String DETAIL = "com.linda.detail.DetailApplication";String PAY = "com.linda.pay.PayApplication";String modules = {LOGIN, DETAIL, PAY};}
3、在BaseApplicatiion通過反射的方式獲取各個module中Application的實例并調用init方法。
public abstract class BaseApplication extends Application implements BaseApplicationImpl {@Overridepublic void onCreate {super.onCreate;initComponent;initARouter;}/*** 初始化各組件*/public void initComponent {for (String module : ModuleConfig.modules) {try {Class clazz = Class.forName(module);BaseApplicationImpl baseApplication = (BaseApplicationImpl) clazz.newInstance;baseApplication.init;} catch (ClassNotFoundException e) {e.printStackTrace;} catch (IllegalAccessException e) {e.printStackTrace;} catch (InstantiationException e) {e.printStackTrace;}}}...}
4、子module中實現init方法,并進行相關初始化操作
public class LiveApplication extends BaseApplication {public void init {//在這里做一些的Live相關的初始化操作}}
三,模塊間通信
BroadcastReceiver:系統提供,比較笨重,使用不夠優雅。
EventBus:使用簡單優雅,將發送這與接收者解耦,2.x使用反射方式比較耗性能,3.x使用注解方式比反射快得多。
但是有些情況是BroadcastReceiver、EventBus解決不了的,例如想在detail模塊中獲取mine模塊中的數據。因為detail和mine都依賴了base,所以我們可以借助base來實現。
1、在base中定義接口并繼承ARouter的IProvider。
public interface IMineDataProvider extends IProvider {String getMineData;}
2、在mine模塊中新建MineDataProvider類實現IMineDataProvider,并實現getMineData方法
@Route(path = RouterPaths.MINE_DATA_PROVIDER)public class MineDataProvider implements IMineDataProvider {@Overridepublic String getMineData {return "***已獲取到mine模塊中的數據***";}@Overridepublic void init(Context context) {}}
3、在detail中獲取MineDataProvider實例并調用IMineDataProvider接口中定義的方法
IMineDataProvider mineDataProvider = (IMineDataProvider) ARouter.getInstance.build(RouterPaths.MINE_DATA_PROVIDER).navigation;if (mineDataProvider != ) {mGetMineData.setText(mineDataProvider.getMineData);}
四,資源沖突
組件化項目中有很多個module,這就難免會出現module中資源命名相同而引起引用錯誤的情況。為此我們可以在每個module的build.gradle文件進行如下配置(例如login模塊)。
resourcePrefix "login_"
所有的資源必須以指定的字符串(建議module名稱)做前綴,不然會報錯。不過這種方式只限定與xml文件,對圖片資源無效,圖片資源仍需要手動修改。
//布局文件命名示例login_activity_login.xml
<resources><!--字符串資源命名示例--><string name="login_app_name">Login</string></resources>
五,單個組件運行調試
當項目越來越龐大時,編譯或運行一次就需要花費很長時間,而組件化可以通過配置對每個模塊進行單獨調試,大大提高了開發效率。我們需要對每個module進行如下配置:
1、在項目根目錄新建common_config.gradle文件并聲明變量isModuleDebug;
project.ext {//是否允許module單獨調試isModuleDebug = false}
2、引入common_config配置,另外因為組件化中每個module都是一個library,如要單獨運行調試需要將library換成application,在module的build.gradle中文件中做如下修改:
//引入common_config配置apply from: "${rootProject.rootDir}/common_config.gradle"if (project.ext.isModuleDebug.toBoolean) {apply plugin: 'com.android.application'} else {apply plugin: 'com.android.library'}
android {defaultConfig {if (project.ext.isModuleDebug.toBoolean) {// 單獨調試時需要添加 applicationIdapplicationId "com.linda.login"}...}sourceSets {main {//在需要單獨調試的module的src/main目錄下新建manifest目錄和AndroidManifest文件// 單獨調試與集成調試時使用不同的 AndroidManifest.xml 文件if (project.ext.isModuleDebug.toBoolean) {manifest.srcFile 'src/main/manifest/AndroidManifest.xml'} else {manifest.srcFile 'src/main/AndroidManifest.xml'}}}}
關于兩個清單文件的不同之處如下:
<!--單獨調試--><manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.linda.login"><applicationandroid:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/login_app_name"android:supportsRtl="true"android:theme="@style/base_AppTheme"><activity android:name=".ui.LoginActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
<!-- 集成調試--><manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.linda.login"><applicationandroid:allowBackup="true"android:label="@string/login_app_name"android:supportsRtl="true"android:theme="@style/base_AppTheme"><activity android:name=".ui.LoginActivity" /></application></manifest>
3、如果module單獨調試,那么在app就不能再依賴此module,因為此時app和module都是project,project之間不能相互依賴,在app的build.gradle文件中做如下修改
dependencies {if (!project.ext.isModuleDebug) {implementation project(path: ':detail')implementation project(path: ':login')implementation project(path: ':pay')}implementation project(path: ':main')implementation project(path: ':home')implementation project(path: ':mine')}
4、最后將isModuleDebug改為true,然后編譯,便可以看到login、detail、pay模塊可以獨立運行調試了。

組件化Demo地址:https://github.com/zhoulinda/ComponentDemo
-
10個讓你用了大呼 “我*NB” 的網站
-
Android面經分享,失業兩個月,五一節前拿到offer
-
7 款 mac 工具,提高你的效率!
你了解多少組件化呢?






