組件化優(yōu)點(diǎn)
1、代碼解耦
2待牵、方便多人協(xié)作開(kāi)發(fā)
3其屏、可復(fù)用性高,不同的APP可復(fù)用不同組件偎行,提高開(kāi)發(fā)效率
4、每個(gè)組件可獨(dú)立運(yùn)行贰拿,減少編譯時(shí)間珍德,方便開(kāi)發(fā)調(diào)試
組件化工程結(jié)構(gòu)
第一層:空殼app虑稼。應(yīng)用的入口,可存放啟動(dòng)頁(yè)且改,依賴所有業(yè)務(wù)組件
第二層:業(yè)務(wù)組件。根據(jù)不同業(yè)務(wù)橫向拆分出來(lái)的業(yè)務(wù)組件犀忱。任何一個(gè)業(yè)務(wù)組件都可以獨(dú)立出來(lái)成為一個(gè)應(yīng)用
第三層:功能組件未斑。通用業(yè)務(wù)是從應(yīng)用業(yè)務(wù)中抽取出來(lái)的交集钠糊,從應(yīng)用上說(shuō)攀甚,他屬于業(yè)務(wù)刑赶,而針對(duì)應(yīng)用業(yè)務(wù)而言則更像是一種功能捏浊,好比登錄這種業(yè)務(wù)功能,不需要關(guān)心有沒(méi)有界面撞叨,當(dāng)中是怎樣的邏輯金踪,只需要提供結(jié)果即可
第四層:公共業(yè)務(wù)組件和公共服務(wù)組件。公共業(yè)務(wù)組件:業(yè)務(wù)相關(guān)Base代碼牵敷。公共服務(wù)組件:可存放各個(gè)組件對(duì)外暴露的接口胡岔,接口實(shí)現(xiàn)在組件內(nèi)部,可通過(guò)ARouter或者DI(依賴注入)實(shí)現(xiàn)跨組件服務(wù)調(diào)用枷餐;可存放路由跳轉(zhuǎn)等信息和路由服務(wù)靶瘸。
第五層:業(yè)務(wù)無(wú)關(guān)基礎(chǔ)組件。網(wǎng)絡(luò)請(qǐng)求毛肋、圖片加載怨咪、存儲(chǔ)、utils润匙、通用View的封裝
項(xiàng)目組件化
1诗眨、代碼解耦
代碼解耦主要是從兩個(gè)方面,其一是公共代碼的抽取和歸納趁桃,其二是面向接口編程辽话,接口下沉。
公共代碼的抽取和歸納:
部分通用的功能性的代碼抽出成utils卫病,上層只關(guān)心結(jié)果油啤,不關(guān)心具體的實(shí)現(xiàn)邏輯
面向接口編程:
當(dāng)上層需要底層的某項(xiàng)服務(wù)時(shí),將服務(wù)抽象成一個(gè)接口蟀苛,上層持有這個(gè)接口益咬,而不是具體的類,那么當(dāng)?shù)讓影l(fā)生了改變或是實(shí)現(xiàn)的時(shí)候帜平,上層只需要實(shí)例化對(duì)應(yīng)的新實(shí)現(xiàn)類即可幽告,如果把這層實(shí)例化也作為接口去作梅鹦,那么上層完全不用改變就能擁抱變化。
依賴注入:
橫向的業(yè)務(wù)代碼或者功能實(shí)現(xiàn)可以進(jìn)行依賴注入的方式來(lái)達(dá)到解耦的目的冗锁。
工程結(jié)構(gòu)解耦:
結(jié)構(gòu)的解耦其實(shí)一般針對(duì)應(yīng)用的整體業(yè)務(wù)而言進(jìn)行的一個(gè)"分Module"操作齐唆,根據(jù)業(yè)務(wù)類型的橫向拆分,業(yè)務(wù)屬性的縱向拆分以及功能SDK下沉冻河。
2箍邮、組件module gradle管理
- 在根目錄下建立一個(gè)config.gradle文件
- 編寫對(duì)應(yīng)的依賴常量代碼
- 在app module 的build.gradle中引用
- 注意,如果想要在別的.gradle中使用聲明的這些常量叨叙,一定要在抽取的xx.gradle文件中將對(duì)應(yīng)的代碼塊用"ext"進(jìn)行包裹
config.gradle
ext {
android = [
compileSdkVersion: 28,
targetSdkVersion : 28,
minSdkVersion : 21,
]
version = [
retrofitSdkVersion : "2.4.0",
androidSupportSdkVersion: "28.0.0",
butterknifeSdkVersion : "8.8.1",
espressoSdkVersion : "3.0.1",
canarySdkVersion : "1.5.4",
glideSdkVersion : "4.8.0"
]
dependencies = [
//support
"appcompat-v7" : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
"design" : "com.android.support:design:${version["androidSupportSdkVersion"]}",
"support-v4" : "com.android.support:support-v4:${version["androidSupportSdkVersion"]}",
"cardview-v7" : "com.android.support:cardview-v7:${version["androidSupportSdkVersion"]}",
"annotations" : "com.android.support:support-annotations:${version["androidSupportSdkVersion"]}",
]
}
在工程根目錄的build.gradle中加上如下代碼:
apply from: "config.gradle"
在app module中的build.gradle中引用(其他組件module中引用類似):
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
defaultConfig {
applicationId "cn.com.xxx"
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionName "1.0.0"
versionCode getVersionCode(versionName)
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation rootProject.ext.dependencies["appcompat-v7"]
implementation rootProject.ext.dependencies["cardview-v7"]
implementation rootProject.ext.dependencies["support-v4"]
implementation rootProject.ext.dependencies["design"]
implementation rootProject.ext.dependencies["annotations"]
implementation rootProject.ext.dependencies["constraint-layout"]
implementation rootProject.ext.dependencies["arch-lifecycle"]
implementation rootProject.ext.dependencies["FlycoTabLayout_Lib"]
implementation rootProject.ext.dependencies["FlycoPageIndicator_Lib"]
implementation rootProject.ext.dependencies["nineoldandroids"]
implementation rootProject.ext.dependencies["jiecaovideoplayer"]
implementation rootProject.ext.dependencies["SmartRefreshLayout"]
}
如此以后在查看或者更換依賴的時(shí)候也方便查看和維護(hù)锭弊,注意在進(jìn)行依賴的過(guò)程中,因?yàn)橐蕾嚥煌娜嚼薮恚赡軙?huì)出現(xiàn)重復(fù)依賴相同庫(kù)而版本不一致的情況味滞,這里有兩種解決辦法,一種是在對(duì)應(yīng)依賴的三方中剔除對(duì)應(yīng)的pom依賴钮呀,如:
api('com.facebook.fresco:fresco:0.10.0') {
exclude module: 'support-v4'
}
另外一種是強(qiáng)制依賴的相同庫(kù)的版本剑鞍,如:
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
def requested = details.requested
if (requested.group == 'com.android.support') {
if (requested.name.startsWith("support-") ||
requested.name.startsWith("animated") ||
requested.name.startsWith("cardview") ||
requested.name.startsWith("design") ||
requested.name.startsWith("gridlayout") ||
requested.name.startsWith("recyclerview") ||
requested.name.startsWith("transition") ||
requested.name.startsWith("appcompat")) {
details.useVersion SUPPORT_LIB_VERSION
} else if (requested.name.startsWith("multidex")) {
details.useVersion OTHER_VERSION.multiDex
}
}
}
}
3、組件路由
路由其實(shí)是組件化的核心組件行楞,網(wǎng)上也有很多優(yōu)秀的開(kāi)源庫(kù)攒暇,這里就直接使用阿里的開(kāi)源庫(kù)ARouter,地址如下:
https://github.com/alibaba/ARouter
ARouter配置
1.每一個(gè)模塊都必須引入compiler sdk子房,只需在base模塊中依賴 api sdk
base module
api "com.alibaba:arouter-api:1.4.1"
其他組件 module
annotationProcessor "com.alibaba:arouter-compiler:1.2.2"
2.每一個(gè)module 的分組都必須不同,分組就是path的第一個(gè)"/"與第二個(gè)"/"之間就轧。
3.每個(gè)module中的build.gradle中都要加入如下配置
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath = true
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
ARouter使用
在common-service模塊下建一個(gè)router包和一個(gè)service包证杭,router包存放路由相關(guān)代碼,service包存放各模塊對(duì)外提供的服務(wù)相關(guān)代碼妒御,不同的模塊提供的服務(wù)代碼放在service包下面不同的子包中
1.ModulePath用來(lái)存在組件的路由地址解愤,一級(jí)路由用模塊名,二級(jí)路由用“module”(無(wú)特別含義乎莉,ARouter要求至少要有二級(jí)路由)送讲,根據(jù)實(shí)際情況確定是否需要三級(jí)路由
@StringDef(
ModulePath.LOGIN,
ModulePath.MAIN
)
annotation class ModulePath {
companion object {
/**
* 登錄模塊
*/
const val LOGIN = "/login/module"
/**
* 首頁(yè)模塊
*/
const val MAIN = "/main/module"
}
}
2.RouterManager用來(lái)提供各模塊間的路由和路由參數(shù)的解析,每個(gè)模塊的開(kāi)發(fā)者應(yīng)該將該模塊的路由跳轉(zhuǎn)的代碼寫在RouterManager來(lái)提供別的模塊跳轉(zhuǎn)到該模塊
object RouterManager {
fun goMain(index: Int) {
val params = HashMap<String, Any>()
.apply {
put("position", index)
}
postcard(ModulePath.MAIN, "tabSelect", params.toJson())
.navigation()
}
private fun postcard(@ModulePath module: String, target: String, params: String): Postcard {
return ARouter.getInstance().build(module)
.withString(TARGET, target)
.withString(PARAMS, params)
}
}
3.ServiceName用來(lái)存放組件對(duì)外提供服務(wù)的地址
@StringDef(
ServiceName.SERVICE_LOGIN,
ServiceName.SERVICE_SHARE
)
annotation class ServiceName {
companion object {
/**
* 登錄信息服務(wù)
*/
const val SERVICE_LOGIN = "/login/user"
/**
* 分享服務(wù)
*/
const val SERVICE_SHARE = "/share/service"
}
}
4.ServiceManager用來(lái)存放組件對(duì)外提供服務(wù)的方法惋啃,若模塊開(kāi)發(fā)者對(duì)其他模塊需暴露自己的服務(wù)哼鬓,需要將服務(wù)相關(guān)代碼寫在ServiceManager中,并加上注釋
object ServiceManager {
/**
* 獲取通用服務(wù)
*/
fun <T> getService(service: Class<out T>): T? {
return ARouter.getInstance().navigation(service)
}
/**
* 獲取個(gè)人信息服務(wù)
*/
fun getUserInfo(): IAppUserInfo? {
return getService(IAppUserInfo::class.java)
}
/**
* 獲取main模塊服務(wù)
*/
fun getMainService(): IMainService? {
return getService(IMainService::class.java)
}
}
5.若一個(gè)模塊要對(duì)外暴露自己的服務(wù)边灭,需要在service包下新建自己的包异希,一般以模塊名作為報(bào)名威始,在該包下提供服務(wù)的接口和相關(guān)常量
interface IShareService : IProvider {
fun showShareDialog(fragmentManager: FragmentManager, shareType: Int, shareParams: HashMap<String, Any>, miniProgramShareParams: HashMap<String, Any>)
fun getShareImageUrl(shareParams: HashMap<String, Any>, consumer: Consumer<String>, error: Consumer<Throwable>): Disposable
}
6.各業(yè)務(wù)模塊通過(guò)RouterManager來(lái)跳轉(zhuǎn)
RouterManager.goGoodsDetail(model.pitemId, model.exhibitionParkType)
7.各業(yè)務(wù)模塊通過(guò)ServiceManager調(diào)用服務(wù)
private val appUserInfo by lazy {
ServiceManager.getUserInfo()
}
4棒搜、單獨(dú)調(diào)試
當(dāng)工程被拆分為組件化的時(shí)候,那么Module的單獨(dú)調(diào)試就顯得尤為重要,無(wú)論是對(duì)問(wèn)題的跟蹤還是業(yè)務(wù)線的并行開(kāi)發(fā)八千,都需要工程具備單獨(dú)運(yùn)行調(diào)試的能力。這里單獨(dú)調(diào)試同樣是對(duì)gradle的操作与殃,通過(guò)對(duì)編譯腳本的編寫來(lái)達(dá)到組件單獨(dú)運(yùn)行的目的母赵。
1.新建appConfig.gradle對(duì)需要單獨(dú)運(yùn)行的Module抽取變量進(jìn)行記錄:
ext {
app = [
versionName: "1.5.0"
]
runAlone = [
login : false,
mine : false,
main : false
]
}
并在工程的build.gradle中apply
apply from: "appConfig.gradle"
2.在對(duì)應(yīng)module的編譯腳本文件中添加判斷module是以lib形式依賴還是以app方式進(jìn)行依賴,如下代碼:
def runAlone = rootProject.ext.runAlone.ssbb.toBoolean()
if (runAlone) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
添加Application中一些必要的元素授药,清單文件Manifest.xml文件频轿,但是這個(gè)xml文件是組件在單獨(dú)運(yùn)行的過(guò)程中所需要的,所以這里要放到一個(gè)runalone的目錄下烁焙,在java目錄下建一個(gè)runalone包航邢,存放該組件單獨(dú)編譯時(shí)需要的代碼:
同時(shí)在此基礎(chǔ)上通過(guò)編譯腳本配置單獨(dú)運(yùn)行時(shí)獲取的Android相關(guān)文件以及在作為library時(shí)剔除相關(guān)文件:
android {
sourceSets {
main {
if (runAlone) {
manifest.srcFile 'src/main/runAlone/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
java{
exclude 'src/main/runAlone/*'
}
}
}
}
}
單獨(dú)調(diào)試時(shí)增加applicationId,集成調(diào)試時(shí)移除
android {
defaultConfig {
if (runAlone) {
applicationId "com.jokerwan.baby"
}
}
}
根據(jù)以上配置骄蝇,就可以使得業(yè)務(wù)模塊單獨(dú)運(yùn)行起來(lái)膳殷,調(diào)試起來(lái)非常方便,但我們調(diào)用接口都是需要cookie的九火,也就是單獨(dú)編譯調(diào)試時(shí)是需要先登錄的赚窃,繼續(xù)往下看
3.修改業(yè)務(wù)組件依賴關(guān)系:
業(yè)務(wù)組件依賴基礎(chǔ)業(yè)務(wù)組件common和基礎(chǔ)服務(wù)組件common_service,再根據(jù)單獨(dú)編譯時(shí)是否需要依賴其他組件對(duì)相應(yīng)的組件進(jìn)行依賴:
dependencies {
kapt rootProject.ext.dependencies["arouter-compiler"]
implementation project(path: ':common')
implementation project(path: ':common_service')
if (runAlone) {
implementation project(path: ':login')
implementation project(path: ':exhibition')
implementation project(path: ':share')
}
}
4.模塊單獨(dú)編譯打包時(shí)登錄處理:
在base模塊新建一個(gè)delayaction岔激,用來(lái)處理需要驗(yàn)證某些條件后才能出發(fā)的動(dòng)作勒极,比如登錄后才能跳轉(zhuǎn)到某個(gè)Activity,沒(méi)有登錄時(shí)先跳轉(zhuǎn)到登錄頁(yè)面虑鼎,SingleCall具體實(shí)現(xiàn)邏輯如下辱匿,通過(guò)單例存儲(chǔ)一個(gè)Action,在Action執(zhí)行前先判斷執(zhí)行條件炫彩,執(zhí)行條件沒(méi)通過(guò)就調(diào)用doMakeValid()去使執(zhí)行條件有效匾七,執(zhí)行條件通過(guò)后再手動(dòng)觸發(fā)call()來(lái)執(zhí)行Action
/**
* Created by JokerWan on 2019-10-31.
* Function: 延遲任務(wù)處理的類需要實(shí)現(xiàn)的接口
*/
public interface Action {
/**
* 前置條件通過(guò)后執(zhí)行的回調(diào)
*/
void call();
}
/**
* Created by JokerWan on 2019-10-31.
* Function: 一個(gè)執(zhí)行單元
*/
public class CallUnit {
//目標(biāo)行為
private Action action;
//驗(yàn)證模型隊(duì)列
private Queue<Condition> conditionQueue = new ArrayDeque<>();
//上一個(gè)執(zhí)行的Condition
private Condition lastCondition;
public CallUnit() {
}
public CallUnit(Action action) {
this.action = action;
}
public Action getAction() {
return action;
}
public void setAction(Action action) {
this.action = action;
}
public Condition getLastCondition() {
return lastCondition;
}
public void setLastCondition(Condition lastCondition) {
this.lastCondition = lastCondition;
}
public CallUnit addCondition(Condition condition) {
conditionQueue.add(condition);
return this;
}
public Queue<Condition> getConditionQueue() {
return conditionQueue;
}
}
/**
* Created by JokerWan on 2019-10-31.
* Function: 條件驗(yàn)證模型
*/
public interface Condition {
/**
* 是否滿足檢驗(yàn)器的要求,如果不滿足的話江兢,則執(zhí)行doMakeValid方法昨忆。如果滿足,則執(zhí)行目標(biāo)action
*/
boolean check();
/**
* 不滿足檢驗(yàn)器時(shí)執(zhí)行
*/
void doMakeValid();
}
/**
* Created by JokerWan on 2019-10-31.
* Function: Condition與Action管理
*/
public class SingleCall {
private CallUnit callUnit = new CallUnit();
/**
* 獲取單例
*/
public static SingleCall getInstance() {
return SingletonHolder.mInstance;
}
private static class SingletonHolder {
private static SingleCall mInstance = new SingleCall();
}
public SingleCall addAction(Action action) {
clear();
callUnit.setAction(action);
return this;
}
/**
* 添加需要驗(yàn)證的條件
*
* @param condition 需驗(yàn)證的條件
* @return this
*/
public SingleCall addCondition(Condition condition) {
// 只添驗(yàn)證不通過(guò)的
if (condition.check()) {
return this;
}
callUnit.addCondition(condition);
return this;
}
public void call() {
// 上一個(gè)condition沒(méi)有驗(yàn)證通過(guò)的話杉允,是不允許再發(fā)起call的
if (callUnit.getLastCondition() != null && !callUnit.getLastCondition().check()) {
return;
}
// 執(zhí)行action
if (callUnit.getConditionQueue().size() == 0 && callUnit.getAction() != null) {
callUnit.getAction().call();
// 清空
clear();
} else {
// 執(zhí)行驗(yàn)證邑贴。
Condition condition = callUnit.getConditionQueue().poll();
callUnit.setLastCondition(condition);
if (condition != null) {
condition.doMakeValid();
}
}
}
/**
* 清空回調(diào),建議在Activity/Fragment生命周期的onDestroy
* 回調(diào)中調(diào)用該方法避免內(nèi)存泄漏
*/
public void clear() {
callUnit.getConditionQueue().clear();
callUnit.setAction(null);
callUnit.setLastCondition(null);
}
}
下面我們以baby模塊的代碼來(lái)具體看下在模塊單獨(dú)打包時(shí)是如何獲取登錄狀態(tài)
首先在login模塊中加入如下代碼
// 登錄成功之后調(diào)用
private fun finishLogin() {
var goMain = false
// 'goMain' set value
if (goMain) {
RouterManager.goMain(0, "Login")
} else {
// 在這里觸發(fā)call()來(lái)執(zhí)行登錄成功之后的Action
SingleCall.getInstance().call()
}
finish()
}
xxx模塊runalone.xxx包下
BabyApp繼承WApp叔磷,WApp是定義在common模塊下的通用Application拢驾,包括一些通用的第三方組件的初始化
class BabyApp : WApp()
open class WApp : Application() {
override fun onCreate() {
super.onCreate()
app = this
initARouter()
initUpgradeManager()
initThirdServiceInBackground()
initRefreshLayout()
initUmengPush()
initLeakCanary()
}
}
LoginCondition是登錄驗(yàn)證條件
class LoginCondition : Condition {
/**
* 驗(yàn)證條件
*/
override fun check(): Boolean {
val userInfo =
ARouter.getInstance().build(ServiceName.SERVICE_LOGIN).navigation() as IAppUserInfo
return userInfo.getId() != 0L
}
/**
* 執(zhí)行驗(yàn)證條件
*/
override fun doMakeValid() {
RouterManager.goLoginWillBack("sst")
}
}
BabyRouterActivity是登錄跳轉(zhuǎn)中間類,主要作用是路由轉(zhuǎn)發(fā)或者提供裝載模塊Fragment容器(該模塊只有Fragment世澜,沒(méi)有Activity)
class BabyRouterActivity : BaseActivity(),Action{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
StatusBarUtil.setStatusBarColorWhite(this)
// 執(zhí)行登錄條件驗(yàn)證
SingleCall.getInstance()
.addAction(this)
.addCondition(LoginCondition())
.call()
}
/**
* onDestroy()時(shí)清空SingleCall
*/
override fun onDestroy() {
super.onDestroy()
SingleCall.getInstance().clear()
}
/**
* 登錄條件驗(yàn)證通過(guò)的回調(diào)
*/
override fun call() {
startActivity(Intent(this, BabyActivity::class.java))
finish()
// addFragment(android.R.id.content, MineFragment())
}
}
當(dāng)驗(yàn)證條件通過(guò)后會(huì)回調(diào)call()方法独旷,在call()方法里可以跳轉(zhuǎn)到模塊的Activity或者直接用此Activity裝載Fragment
5、整體調(diào)試
將appConfig.gradle中的模塊單獨(dú)調(diào)試變量全部改為false
根據(jù)上述方案,app殼其實(shí)只需要依賴業(yè)務(wù)組件即可:
dependencies {
implementation project(path: ':common')
implementation project(path: ':common_service')
if (!rootProject.ext.runAlone.login.toBoolean()) {
implementation project(path: ':login')
}
if (!rootProject.ext.runAlone.mine.toBoolean()) {
implementation project(path: ':mine')
}
}
6嵌洼、資源名沖突
color案疲,shape,drawable麻养,圖片資源褐啡,布局資源,或者anim資源等等鳖昌,都有可能造成資源名稱沖突备畦。有時(shí)候大家負(fù)責(zé)不同的模塊,如果不是按照統(tǒng)一規(guī)范命名许昨,則會(huì)偶發(fā)出現(xiàn)該問(wèn)題
可以通過(guò)設(shè)置 resourcePrefix 來(lái)避免懂盐。設(shè)置了這個(gè)值后,你所有的資源名必須以指定的字符串做前綴糕档,否則會(huì)報(bào)錯(cuò)莉恼。但是 resourcePrefix 這個(gè)值只能限定 xml 里面的資源,并不能限定圖片資源速那,所有圖片資源仍然需要你手動(dòng)去修改資源名俐银。
android {
// 所有xml資源命名以 "login_" 開(kāi)頭、否則編譯報(bào)紅
resourcePrefix "login_"
}
7端仰、組件Application初始化
自定義 Application 需要聲明在 AndroidManifest.xml 中捶惜。其次,每個(gè) Module 都有該清單文件荔烧,但是最終的 APK 文件只能包含一個(gè)吱七。因此,在構(gòu)建應(yīng)用時(shí)茴晋,Gradle 構(gòu)建會(huì)將所有清單文件合并到一個(gè)封裝到 APK 的清單文件中陪捷。
合并的優(yōu)先級(jí)是:
App Module > Library Module
合并的規(guī)則:
結(jié)合我們的情況,是值 A 合并值 B诺擅,會(huì)產(chǎn)生沖突錯(cuò)誤:
Execution failed for task ':app:processDebugManifest'.
> Manifest merger failed : Attribute application@name value=(com.baseres.BaseApplication) from AndroidManifest.xml:8:9-51
is also present at [:carcomponent] AndroidManifest.xml:14:9-55 value=(com.carcomponent.CarApplication).
Suggestion: add 'tools:replace="android:name"' to <application> element at AndroidManifest.xml:7:5-24:19 to override.
錯(cuò)誤信息中給出了解決建議,在高優(yōu)先級(jí)的 App Module 中使用 tools:replace="android:name"啡直,但這樣做是直接用值 A 替換了值 B烁涌,并非我們想要的結(jié)果。另外再推薦給大家一個(gè)方法酒觅,打開(kāi) App Module 的 AndroidManifest.xml 文件撮执,選擇下方 Merged Manifest 選項(xiàng)卡,可以看到預(yù)合并結(jié)果舷丹。
解決方法一:每個(gè)業(yè)務(wù)模塊通過(guò)Arouter暴露出模塊初始化服務(wù)抒钱,app模塊可在適當(dāng)?shù)臅r(shí)機(jī)(為了增強(qiáng)秒開(kāi)體驗(yàn),盡量別在app模塊的Application中初始化,可以在MainActivity中進(jìn)行初始化或者在跳轉(zhuǎn)業(yè)務(wù)模塊前初始化)調(diào)用各模塊的服務(wù)進(jìn)行初始化
解決方法二:通過(guò)反射在app模塊的Application的onCreate()方法中調(diào)用組件Application的初始化方法
1.在base模塊中新增BaseAPP
public abstract class BaseApp extends Application {
/**
* Application 初始化
*/
public abstract void initModuleApp(Application application);
private static Context appContext;
@Override
public void onCreate() {
super.onCreate();
appContext = getApplicationContext();
}
public static Context getAppContext() {
return appContext;
}
}
2.在live模塊中的LiveApplication繼承BaseApp實(shí)現(xiàn)initModuleApp()方法谋币,并在此方法中做初始化操作仗扬,作為library時(shí)初始化操作在initModuleApp()方法中,作為獨(dú)立app時(shí)蕾额,初始化操作在onCreate()中早芭。
public class LiveApplication extends BaseApp {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void initModuleApp(Application application) {
// init
}
}
3.在base模塊中增加AppConfig類,用來(lái)配置需要初始化的組件Application
public class AppConfig {
private static final String LiveApp = "cn.com.live.LiveApplication";
public static String[] moduleApps = {
LiveApp
};
}
4.在app的Application的onCreate()方法中通過(guò)反射初始化在AppConfig中聲明類全路徑的組件Application類
public class WApp extends BaseApp {
@Override
public void onCreate() {
super.onCreate();
initModuleApp(this);
}
@Override
public void initModuleApp(Application application) {
for (String moduleApp : AppConfig.moduleApps) {
try {
Class clazz = Class.forName(moduleApp);
SBBaseApp baseApp = (SBBaseApp) clazz.newInstance();
baseApp.initModuleApp(this);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
}
8诅蝶、組件代碼混淆
方法一:直接在app模塊的proguard-rules.pro中編寫所有組件的混淆規(guī)則
優(yōu)點(diǎn):簡(jiǎn)單無(wú)腦
缺點(diǎn):若app模塊依賴的組件很多退个,則proguard-rules.pro中混淆規(guī)則龐大不利于維護(hù),使用app模塊編寫所有混淆命令是基于業(yè)務(wù)模塊當(dāng)中不再編寫混淆命令為前提调炬,所以在打包將業(yè)務(wù)模塊上傳到私有倉(cāng)庫(kù)時(shí)语盈,業(yè)務(wù)模塊都是不開(kāi)啟混淆功能的!
但是
上述結(jié)論都是建立在以implementation或者api形式依賴的前提下缰泡,開(kāi)發(fā)階段我們是以
implementation project(':live')
這種形式進(jìn)行依賴的刀荒,你會(huì)發(fā)現(xiàn)當(dāng)以這種形式進(jìn)行依賴時(shí),不管業(yè)務(wù)模塊minifyEnabled是true還是false匀谣,只要app模塊寫上了正確的混淆規(guī)則那么程序都能正常運(yùn)行照棋!
方法二:各個(gè)業(yè)務(wù)組件單獨(dú)編寫混淆規(guī)則(推薦)
優(yōu)點(diǎn):各組件自己配置混淆文件,易于維護(hù)
在模塊中的build.gradle中配置
android {
buildTypes {
release {
consumerProguardFiles 'proguard-rules.pro'
}
}
}
使用這種配置最大的一個(gè)好處就是業(yè)務(wù)模塊的是否混淆完全由app模塊來(lái)決定武翎,這種配置有一個(gè)非常重要的關(guān)鍵點(diǎn)就是不能設(shè)置minifyEnabled true烈炭,因?yàn)樵O(shè)置為true之后業(yè)務(wù)模塊是否混淆的控制權(quán)將只能由該模塊自身決定,app模塊將無(wú)法控制業(yè)務(wù)模塊的混淆與否而且設(shè)置為true之后還必須額外配置
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
9宝恶、組件庫(kù)的獨(dú)立發(fā)布和維護(hù)
原有拆分的本地組件徹底分離出去符隙,采取獨(dú)立發(fā)布和維護(hù)的方式迭代更新。