Android組件化實(shí)踐
demo地址:https://github.com/syg13579/assembleDemo
概述
軟件開發(fā)進(jìn)程也是架構(gòu)的演進(jìn)過程燃辖,就拿Android來說,從最開始的MVC ,MVP ,MVVP ,再到后來的組件化面殖,插件化,但歸根到底一切的一切,都是為了項(xiàng)目更好的維護(hù)、迭代坎吻,降低開發(fā)成本。
在一個(gè)項(xiàng)目的開發(fā)過程中宇葱,前期我們可能把所有的功能模塊都放到了一個(gè)moudle中瘦真,這樣能夠快速的開發(fā)刊头,但隨著項(xiàng)目壯大,開發(fā)人員和功能的增加诸尽,就回導(dǎo)致代碼越來越臃腫原杂,各個(gè)模塊之間的耦合越來越重,牽一發(fā)而動(dòng)全身弦讽,這個(gè)時(shí)候?yàn)榱吮WC項(xiàng)目質(zhì)量污尉,我們就需要對(duì)項(xiàng)目進(jìn)行重構(gòu)。
我們可以根據(jù)業(yè)務(wù)模塊進(jìn)行查分往产,把不同的業(yè)務(wù)模塊放到不同的moudle中被碗,實(shí)現(xiàn)各個(gè)業(yè)務(wù)之間的結(jié)構(gòu),他們又共同依賴底層公共庫仿村,這就是模塊化的概念锐朴,但是當(dāng)多個(gè)模塊中涉及到相同功能時(shí)代碼的耦合又會(huì)增加,例如有兩個(gè)模塊都需要視頻播放的功能蔼囊,把視頻播放放到兩個(gè)組件中就會(huì)出現(xiàn)代碼重復(fù)的問題焚志,放到公共庫感覺也不是很好,這時(shí)候就用組件化來解決這個(gè)問題
模塊化和組件化
模塊化
具體的業(yè)務(wù)模塊畏鼓,例如商品詳情模塊酱酬,商品發(fā)布模塊 ,搜索模塊
組件化
單一的功能組件云矫,如視頻播放組件膳沽、分享組件等,每個(gè)組件都可以以一個(gè)單獨(dú)的 module 開發(fā)让禀,并且可以單獨(dú)抽出來作為 SDK 對(duì)外發(fā)布使用
模塊化和組件化的思想是一樣的挑社,都是對(duì)代碼進(jìn)行拆分,但模塊化是按功能模塊進(jìn)行查分(業(yè)務(wù)導(dǎo)向)巡揍,組件化是按功能模塊進(jìn)行查分(功能導(dǎo)向)痛阻,模塊化的顆粒度更大一些,組件的顆粒度更小一些腮敌,一個(gè)項(xiàng)目中模塊和組件同時(shí)存在也是很常見的阱当,各自負(fù)責(zé)各自的事情
如上圖所示 是個(gè)組件化項(xiàng)目的基本架構(gòu)
- 基礎(chǔ)庫、公共庫:項(xiàng)目所需要的基礎(chǔ)操作類糜工,工具類 斗这,第三方庫的引入封裝 ,app宿主功能啤斗,各個(gè)模塊,各個(gè)組件都依賴這個(gè)庫
- 組件層:項(xiàng)目用的功能模塊或者業(yè)務(wù)模塊赁咙,如:登錄模塊钮莲,視頻播放組件免钻,分享組件等
- 應(yīng)用層:宿主工程,APP的主項(xiàng)目崔拥,APP入口和主架子
組件化Demo
地址如下: https://github.com/syg13579/assembleDemo
我根據(jù)demo項(xiàng)目從以下幾個(gè)方面來講解
- 1:項(xiàng)目分析
- 2:組件application和library動(dòng)態(tài)切換
- 3:組件間的數(shù)據(jù)傳遞和方法調(diào)用
- 4:組件類(例如:Fragment)的獲取,以及夸組件頁面跳轉(zhuǎn)和通訊
1:項(xiàng)目分析
如上圖所示极舔,項(xiàng)目的主要結(jié)構(gòu)
- 應(yīng)用層:app 項(xiàng)目的主入口
- 組件層:goods login 商品詳情頁和登錄組件
- 基礎(chǔ)庫層:assemblebase用來各個(gè)組件數(shù)據(jù)和方法交互的 ,base是常用的工具類链瓦,各種類庫的封裝
2:組件application和library動(dòng)態(tài)切換
在開發(fā)過程中拆魏,為了能夠?qū)崿F(xiàn)快速開發(fā),組件能夠獨(dú)立運(yùn)行就顯的特別重要,moudle一般分為兩種
- App 插件慈俯,id: com.android.application
- Library 插件渤刃,id: com.android.library
我們可以通過配置可動(dòng)態(tài)進(jìn)行application和library的切換,我們?cè)诟鱾€(gè)組件的gradle.properties文件中配置一個(gè)控制切換的變量
然后在build.gradle中就可以通過isRunAlone變量來進(jìn)行application和library的切換了贴膘,主要設(shè)計(jì)的點(diǎn)有三部分
- plugin屬性的配置
- applicationId的配置
- AndroidManifest的配置
if (isRunAlone.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion 26
defaultConfig {
if (isRunAlone.toBoolean()) {
applicationId "ppzh.jd.com.goods"
}
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
如果以上配置就可以實(shí)現(xiàn)application和library的切換了
3:組件間的數(shù)據(jù)傳遞和方法調(diào)用
由于主項(xiàng)目卖子、組件之間,組件和組件之間不能直接通過引用進(jìn)行數(shù)據(jù)傳遞和方法調(diào)用刑峡,那么在開發(fā)的過程中怎么進(jìn)行數(shù)據(jù)傳遞和方法調(diào)用呢洋闽,可以通過「接口」+「實(shí)現(xiàn)」的方式進(jìn)行,
assemblebase基礎(chǔ)庫就是用來進(jìn)行數(shù)據(jù)傳遞和方法調(diào)用的突梦,它被所有組件所依賴诫舅,assemblebase提供各個(gè)組件對(duì)外提供數(shù)據(jù)和方法調(diào)用的抽象service ,同時(shí)還有serviceFactory對(duì)service進(jìn)行操作,各個(gè)組件在初始化的時(shí)候?qū)Ω髯缘膕ervice進(jìn)行實(shí)現(xiàn)宫患。同時(shí)中也會(huì)提供所有的 Service 的空實(shí)現(xiàn)刊懈,以避免引起的空指針異常
就以登錄模塊為例,對(duì)外提供兩個(gè)數(shù)據(jù)
public interface ILoginService {
/**
* 是否已經(jīng)登錄
*
* @return
*/
boolean isLogin();
/**
* 獲取登錄用戶的 AccountId
*
* @return
*/
String getAccountId();
}
相關(guān)的serviceFactory類如下撮奏,可以通過serviceFactory拉取相關(guān)service的實(shí)例
public class ServiceFactory {
private ILoginService loginService;
private IGoodsService goodsService;
/**
* 禁止外部創(chuàng)建 ServiceFactory 對(duì)象
*/
private ServiceFactory() {
}
/**
* 通過靜態(tài)內(nèi)部類方式實(shí)現(xiàn) ServiceFactory 的單例
*/
public static ServiceFactory getInstance() {
return Inner.serviceFactory;
}
private static class Inner {
private static ServiceFactory serviceFactory = new ServiceFactory();
}
// ------------------------LoginService------------------------
/**
* 接收 Login 組件實(shí)現(xiàn)的 Service 實(shí)例
*/
public void setLoginService(ILoginService loginService) {
this.loginService = loginService;
}
/**
* 返回 Login 組件的 Service 實(shí)例
*/
public ILoginService getLoginService() {
if (loginService == null) {
loginService = new EmptyLoginService();
}
return loginService;
}
在login組件中只需要實(shí)現(xiàn)ILoginService俏讹,并通過serviceFactory進(jìn)行設(shè)置
public class LoginService implements ILoginService {
@Override
public boolean isLogin() {
return false;
}
@Override
public String getAccountId() {
return null;
}
}
在login的appliction中進(jìn)行service的設(shè)置
public class LoginApp extends BaseApp {
@Override
public void onCreate() {
super.onCreate();
initModuleApp(this);
initModuleData(this);
}
@Override
public void initModuleApp(Application application) {
ServiceFactory.getInstance().setLoginService(new LoginService());
}
@Override
public void initModuleData(Application application) {
}
}
但是有這樣一個(gè)問題:在集成到app中,LoginApp是沒有被執(zhí)行的畜吊,這個(gè)怎么解決呢泽疆,我們可以通過反射進(jìn)行解決
public class AssembleApplication extends BaseApp {
@Override
public void onCreate() {
super.onCreate();
initModuleApp(this);
initModuleData(this);
initComponentList();
}
@Override
public void initModuleApp(Application application) {
}
@Override
public void initModuleData(Application application) {
}
//初始化組件
//通過反射初始化
private void initComponentList(){
for (String moduleApp : AppConfig.moduleApps) {
try {
Class clazz = Class.forName(moduleApp);
BaseApp baseApp = (BaseApp) clazz.newInstance();
baseApp.initModuleApp(this);
baseApp.initModuleData(this);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
如上所示就完成了
4:組件類(例如:Fragment)的獲取,以及夸組件頁面跳轉(zhuǎn)和通訊
fragment的獲取也是通過service來完成的
public interface IGoodsService {
/**
* 創(chuàng)建 GoodsFragment
* @param bundle
* @return
*/
Fragment newGoodsFragment(Bundle bundle);
}
相關(guān)組件實(shí)現(xiàn)該接口就行
各個(gè)組件間頁面的跳轉(zhuǎn)可以通過阿里的ARouter實(shí)現(xiàn),我是通過設(shè)置ComponentName來實(shí)現(xiàn)的玲献,但這種方式好像并沒有實(shí)現(xiàn)真正的代碼隔離
/**
*
* 去登陸
*
* 跨組件頁面跳轉(zhuǎn)
*/
private void toLogin(){
Intent intent = new Intent();
intent.setComponent(new ComponentName(mContext, "ppzh.jd.com.login.LoginActivity"));
startActivityForResult(intent,LOGIN_REQUEST_CODE);
}
總結(jié)
通過上面就整體實(shí)現(xiàn)了項(xiàng)目組件化殉疼,在以后也可以更多的運(yùn)用組件化來進(jìn)行項(xiàng)目開發(fā)