前提
之前在一直單獨(dú)干位仁,自己隨便搭個(gè)框架就開(kāi)始開(kāi)發(fā)揣钦,such as mvc mvp mvvm clean 一些mv*架構(gòu)吧祭芦,可以隨便弄隨便改移层,方便自己的開(kāi)發(fā)同時(shí)也可以鍛煉自己的架構(gòu)方面的知識(shí)吧仍翰,確實(shí)學(xué)到很多,比如MVP + RxJava + Retrofit + Dagger2 + GreenDAO + Glide 這些結(jié)合起來(lái)用真的讓開(kāi)發(fā)速度提升了很多有想學(xué)習(xí)的同學(xué)可以看看這個(gè)app喜歡的可以關(guān)注下 Life APP
但是目前由于工作原因嗎幽钢,需要和幾個(gè)小伙伴一起開(kāi)發(fā)歉备,合作開(kāi)發(fā),可能不能這樣隨便玩玩了匪燕,就需要考慮到合作開(kāi)發(fā)需要注意的問(wèn)題蕾羊,由于項(xiàng)目是剛剛開(kāi)始喧笔,必定要考慮到之后開(kāi)發(fā)一些坑嗎,打包這個(gè)問(wèn)題龟再,做android的同學(xué)书闸,每次遇到都是很無(wú)語(yǔ),項(xiàng)目很大的話可能打包一個(gè)需要五六分鐘利凑,太痛苦了浆劲,這是后大家應(yīng)該會(huì)想到的是插件化開(kāi)發(fā),隨時(shí)隨地的更新app內(nèi)容而不需要打包上線這些流程什么的哀澈,但是這個(gè)大部分是用于動(dòng)態(tài)修復(fù)bug和更新模塊牌借,可能會(huì)有些偏離我們要做的事情,我們要做到是 代碼耦合度降低割按,每個(gè)模塊完全達(dá)到 解耦膨报,不互相牽連,這樣保證了每個(gè)人的開(kāi)發(fā)效率适荣,同時(shí)每個(gè)module之間也可以打包成相應(yīng)的apk 進(jìn)行測(cè)試
原理
正常我們開(kāi)發(fā)app的時(shí)候在gradle里面配置的主module都是Application现柠,其他的都是Library,那么組件化開(kāi)發(fā)會(huì)有什么區(qū)別的弛矛,其實(shí)也就是讓每個(gè)module運(yùn)行起來(lái)够吩,就是就是把pludgin改成 Application 發(fā)布的時(shí)候合并即可
架構(gòu)
不知道有些同學(xué)開(kāi)過(guò)餓了嗎和滴滴打車發(fā)布的他們的app開(kāi)發(fā)框架,畢竟是大公司丈氓,維護(hù)成本和開(kāi)發(fā)成本都很大周循,他們之前在某it論壇上發(fā)布了一篇文章就是說(shuō)組件化開(kāi)發(fā)架構(gòu),把所有的基礎(chǔ) 所有公共的東西提取出成一個(gè)BaseSDK
然后每個(gè)module依賴這個(gè)Library
簡(jiǎn)要
先說(shuō)說(shuō)組件化開(kāi)發(fā)會(huì)遇到的一些問(wèn)題吧
1.module與Application之間調(diào)用的問(wèn)題
2.跨module的Activity 或 Fragment 之間的跳轉(zhuǎn)問(wèn)題
3.AAR 或Library project 重復(fù)依賴
4.資源名沖突
下面我會(huì)一一的說(shuō)明如何解決這些問(wèn)題扒寄。
project 配置
組件化的基本就是通過(guò) gradle 腳本來(lái)做的鱼鼓。
這時(shí)候需要組件化的業(yè)務(wù)module中需要配置
if (isDebug.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
就是說(shuō)當(dāng)我們?cè)跊](méi)發(fā)布版本之前,我們的每個(gè)module之間是相互沒(méi)有任何依賴的都可以單獨(dú)運(yùn)行
isDebug這個(gè)字段可以在最外層的gradle里面配置该编,也可以在業(yè)務(wù) module 中放一個(gè) gradle.properties來(lái)配置迄本,
但是我個(gè)人感覺(jué)嗎,最好在外出gradle中配置课竣,這樣每個(gè)module 可以用一個(gè)總開(kāi)關(guān)來(lái)控制嘉赎。
下面放置一個(gè)完整的module 供參考
def Dependencies = rootProject.ext
if (isDebug.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'android-apt'
android {
compileSdkVersion Dependencies.androidCompileSdkVersion
buildToolsVersion Dependencies.androidBuildToolsVersion
resourcePrefix "preview_"
defaultConfig {
if (isDebug.toBoolean()) {
applicationId "com.cuieney.preview"
}
minSdkVersion Dependencies.androidMinSdkVersion
targetSdkVersion Dependencies.androidTargetSdkVersion
versionCode Dependencies.versionCode
versionName Dependencies.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
sourceSets {
main {
if (isDebug.toBoolean()) {
manifest.srcFile 'src/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
}
}
packagingOptions {
exclude 'META-INF/rxjava.properties'
}
lintOptions {
abortOnError Dependencies.abortOnLintError
checkReleaseBuilds Dependencies.checkReleaseBuilds
ignoreWarnings Dependencies.ignoreWarnings
}
compileOptions {
sourceCompatibility Dependencies.javaVersion
targetCompatibility Dependencies.javaVersion
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
repositories {
flatDir {
dirs 'libs'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
compile (name: 'StreamingLib', ext: 'aar')
compile project(':meetvrsdk')
apt Dependencies.dataDependencies.arouter_compiler
}
可以根據(jù)自己的需求進(jìn)行修改
Manifest
當(dāng)module單獨(dú)運(yùn)行的時(shí)候和合并運(yùn)行的時(shí)候每個(gè)需要用的manifest也是有些許不同的,一些細(xì)微的差別的于樟,但是這個(gè)我們也是需要注意的公条,簡(jiǎn)單的一句代碼在gradle重配置即可
sourceSets {
main {
if (isDebug.toBoolean()) {
manifest.srcFile 'src/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
}
}
根據(jù)我們之前全局設(shè)置的isDebug來(lái)進(jìn)行切換manifest即可。main 下的 manifest 寫通用的東西迂曲,另外 2 個(gè)分別寫各自獨(dú)立的靶橱,通常 release 的 manifest 只是一個(gè)空的 application 標(biāo)簽,而 debug 的會(huì)有 application 和調(diào)試用的 activity(你總得要有個(gè)啟動(dòng) activity 吧)及權(quán)限。
這里有一個(gè)小 tip关霸,就是在 release 的 manifest 中传黄,application 標(biāo)簽下盡量不要放任何東西,只是占個(gè)位队寇,讓上面去 merge膘掰,否則比如一個(gè) module supportsRtl 設(shè)置為了 true,另一個(gè) module 設(shè)置為了 false佳遣,就不得不去做 override 了识埋。
module與Application之間調(diào)用的問(wèn)題
這個(gè)問(wèn)題可能每個(gè)人會(huì)有不同的寫法和解決方法,這里我提供一個(gè)簡(jiǎn)單的解決方案零渐。
由于我們每個(gè)module都會(huì)依賴我們的BaseSDK這個(gè)library窒舟,其實(shí)在我們的 BaseSDK中直接定義個(gè)BaseApplication即可,然而每個(gè)module都可以通過(guò)BaseApplication來(lái)調(diào)用诵盼,這樣就可以解決module與Application之間調(diào)用的問(wèn)題辜纲。代碼如下,可根據(jù)自己的需求不同進(jìn)行修改
public abstract class BaseApplication extends Application {
public static BaseApplication app;
public static BaseApplication getInstance() {
return app;
}
protected static boolean isDebug = true;
@Override
public void onCreate() {
super.onCreate();
app = this;
initSDK();
}
public abstract void initSDK();
}
在我們的主application中可以繼承這個(gè)類然后寫一些自己需要初始化的東西
代碼如下:
public class App extends BaseApplication {
@Override
public void initSDK() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
只要把公共需求的東西定義在Base中拦耐,然而調(diào)用的時(shí)候就可以解決這些問(wèn)題
跨module的Activity 或 Fragment 之間的跳轉(zhuǎn)問(wèn)題
這個(gè)問(wèn)題呢,解決方案有很多種 可以自己寫個(gè)router來(lái)解決跳轉(zhuǎn)之間的問(wèn)題见剩,也可以借助三方工具來(lái)完成這個(gè)操作杀糯。
自己寫router呢,只不過(guò)感覺(jué)很有些麻煩苍苞,直接上圖吧
ActivityRouter
public class ActivityRouter {
public static void startActivity(Context context, String action) {
context.startActivity(new Intent(action));
}
public static void startActivity(Context context, Class clazz) {
context.startActivity(getIntent(context, clazz));
}
public static Intent getIntent(Context context, Class clazz) {
return new Intent(context, clazz);
}
public static void startActivityForName(Context context, String name) {
try {
Class clazz = Class.forName(name);
startActivity(context, clazz);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
FragmentRouter
public class FragmentRouter {
public static Fragment getFragment(String name) {
Fragment fragment;
try {
Class fragmentClass = Class.forName(name);
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
return fragment;
}
}
RouterList
public class RouterList {
public static final String PREVIEW_MAIN = "com.cuieney.preview.PreviewActivity";
}
就這些自己可以這樣使用固翰。但是我還是推薦使用三方,因?yàn)閍ct跳轉(zhuǎn)傳值問(wèn)題羹呵,act請(qǐng)求fragment問(wèn)題骂际,還有許多未知的坑,所以推薦兩個(gè)三方router ARouter,
ActivityRouter冈欢,這兩個(gè)可以根據(jù)自己需求進(jìn)行選擇歉铝,我用的是ActivityRouter。感覺(jué)配置起來(lái)會(huì)方便許多
ActivityRouter一些配置細(xì)節(jié)
1.ActivityRouter提供的compile可以配置在BaseSDK中凑耻,然后apt配置在需要組件化的module中
2.AndroidManifest配置呢太示,也是如此這個(gè)需要配置在需要組件化的module中。而不是主module中香浩,但是如果說(shuō)是release可以配置在主module中
3.其他的一些配置可以參考ActivityRouter readme
AAR 或Library project 重復(fù)依賴
解決方案各有不同类缤,可以在dependency中根據(jù)isDebug 來(lái)判斷依賴包問(wèn)題等,可以是 將 compile 改為 provided邻吭,只在最終的項(xiàng)目中 compile 對(duì)應(yīng)的代碼餐弱,但是這種辦法只能用于沒(méi)有資源的純代碼工程或者jar包;目前我了解的是這兩種方法 ,大家可以看看還有什么好的辦法提供解決思路
資源名沖突
這個(gè)問(wèn)題解決最簡(jiǎn)單膏蚓,可以自己命名的時(shí)候相互注意一下瓢谢,也可以在對(duì)于的module中的gradle配置
resourcePrefix "preview_"
設(shè)置了這個(gè)值后,你所有的資源名必須以指定的字符串做前綴降允,否則會(huì)報(bào)錯(cuò)恩闻。
但是 resourcePrefix 這個(gè)值只能限定 xml 里面的資源,并不能限定圖片資源剧董,所有圖片資源仍然需要你手動(dòng)去修改資源名幢尚。
ending
可能前期不會(huì)考慮到后面項(xiàng)目逐漸增大了之后 模塊之間的耦合度,需求復(fù)雜度上升等問(wèn)題翅楼,但是組件化開(kāi)發(fā)的形式可以解耦尉剩,降低開(kāi)發(fā)成本,提高編譯速度毅臊,為什么不用呢理茎。何樂(lè)而不為。
開(kāi)開(kāi)心心上班管嬉,安安心心睡覺(jué)