作者: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 {
@Override
public 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 {
@Override
public 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 {
@Override
public String getMineData {
return "***已獲取到mine模塊中的數據***";
}
@Override
public 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) {
// 單獨調試時需要添加 applicationId
applicationId "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">
<application
android: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">
<application
android: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 工具,提高你的效率!
你了解多少組件化呢?