項目按功能拆分紅功若干個組件,每一個組件負責相應的功能,如login、pay、live。組件化與模塊化相似,但不一樣的是模塊化是以業務爲導向,組件化是以功能爲導向。組件化的顆粒度更細,一個模塊裏可能包含多個組件。實際開發中通常是模塊化與組件化相結合的方式。java
(1)提升複用性避免重複造輪子,不一樣的項目能夠共用同一組件,提升開發效率,下降維護成本。android
(2)項目按功能拆分紅組件,組件之間作到低耦合、高內聚,有利於代碼維護,某個組件須要改動,不會影響到其餘組件。git
組件化是一種思想,團隊在使用組件化的過程當中沒必要拘泥於形式,能夠根據本身負責的項目大小和業務需求的須要制定合適的方案,以下圖就是一種組件化結構設計。 github
宿主app數據庫
在組件化中,app能夠認爲是一個入口,一個宿主空殼,負責生成app和加載初始化操做。api
業務層安全
每一個模塊表明了一個業務,模塊之間相互隔離解耦,方便維護和複用。bash
公共層網絡
既然是base,顧名思義,這裏麪包含了公共的類庫。如Basexxx、Arouter、ButterKnife、工具類等app
基礎層
提供基礎服務功能,如圖片加載、網絡、數據庫、視頻播放、直播等。
注:以上結構只是示例,其中層級的劃分和層級命名並非定性的,只爲更好的理解組件化。
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);
複製代碼
一、顯示跳轉,直接依賴,不符合組件化解耦隔離的要求。
二、對於隱示跳轉,若是移除B的話,那麼在A進行跳轉時就會出現異常崩潰,咱們經過下面的方式來進行安全處理
//隱示跳轉
Intent intent = new Intent();
intent.setClassName("app包名" , "activity路徑");
intent.setComponent(new Component(new Component("app報名" , "activity路徑")));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
startActivity(intent);
複製代碼
原生推薦使用隱示跳轉,不過在組件化項目中,爲了更優雅的實現組件間的頁面跳轉能夠結合路由神器ARouter,ARouter相似中轉站經過索引的方式無需依賴,達到了組件間解耦的目的。
Aouter使用方式以下:
一、由於ARouter是全部模塊層組件都會用到因此咱們能夠在Base中引入
api 'com.alibaba:arouter-api:1.5.0'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
複製代碼
二、在每一個子module裏添加
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
複製代碼
annotationProcessor會經過javaCompileOptions這個配置來獲取當前module的名字。
三、在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中初始化
}
}
複製代碼
四、在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名稱標識。
五、發起跳轉操做
ARouter.getInstance().build(RouterPaths. LOGIN_ACTIVITY).navigation();
複製代碼
ARouter的還有不少其餘功能,這裏不做詳細說明。
Application做爲程序的入口一般作一些初始化,如上面提到的ARouter,因爲ARouter是全部模塊層組件都要用到,因此把它放在BaseApplication進行初始化。若是某個初始化操做只屬於某個模塊,爲了下降耦合,咱們應該把該初始化操做放在對應模塊module的Application裏。以下:
一、在BaseModule定義接口
public interface BaseApplicationImpl {
void init();
...
}
複製代碼
二、在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
};
}
複製代碼
三、在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();
}
}
}
...
}
複製代碼
四、子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來實現。
一、在base中定義接口並繼承ARouter的IProvider。
public interface IMineDataProvider extends IProvider {
String getMineData();
}
複製代碼
二、在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) {
}
}
複製代碼
三、在detail中獲取MineDataProvider實例並調用IMineDataProvider接口中定義的方法
IMineDataProvider mineDataProvider = (IMineDataProvider) ARouter.getInstance().build(RouterPaths.MINE_DATA_PROVIDER).navigation();
if (mineDataProvider != null) {
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進行以下配置:
一、在項目根目錄新建common_config.gradle文件並聲明變量isModuleDebug;
project.ext {
//是否容許module單獨調試
isModuleDebug = false
}
複製代碼
二、引入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>
複製代碼
三、若是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')
}
複製代碼
四、最後將isModuleDebug改成true,而後編譯,即可以看到login、detail、pay模塊能夠獨立運行調試了。