Android組件化開發(fā)

最近公司在做一款新的車機(jī) Launcher琼富,需要將一個(gè)類似QQ音樂(lè)凛虽、喜馬拉雅的音頻模塊放入其中,整體作為一個(gè) Launcher偿短,雖然產(chǎn)品一再確定欣孤,后面不會(huì)進(jìn)行拆分,但是小心為上昔逗,將 Launcher 和 音頻軟件分為兩個(gè) App 開發(fā)降传,兩個(gè)團(tuán)隊(duì)開發(fā)互不影響,最后通過(guò)組件化勾怒,作為 module 引入到空殼App中婆排。

整體思路

這里寫個(gè)組件化二維碼掃描的 Demo 声旺,①空殼 App,②公共 Library段只,③第一個(gè) App 類似于上面說(shuō)的 Launcher腮猖,④第二個(gè) App 類似于上面說(shuō)的音頻 App。



其中③赞枕、④均是可單獨(dú)運(yùn)行的 module澈缺,都依賴于②,當(dāng)運(yùn)行①時(shí)炕婶,需要將③姐赡、④轉(zhuǎn)換為 library 去依賴。

那么需要解決的第一個(gè)問(wèn)題就是古话,如何在 app 和 library 直接切換雏吭。

切換 application 和 library 屬性

切換 module 的 application 和 library,需要在 gradle.properties 里面進(jìn)行配置陪踩,因?yàn)檫@里面的變量都是全局的杖们,全部 gradle 都可以取到。


gradle.properties

在最后一行增加一個(gè)標(biāo)記變量 isModule=true肩狂,我這里用 true 表示 application摘完,false 表示 library。

# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
isModule=false

下面就可以修改 module 的 build.gradle 判斷操作了傻谁。

if (isModule.toBoolean()) {// ① 切換
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
apply plugin: 'com.jakewharton.butterknife'

def config = rootProject.ext// 定義變量
android {
    compileSdkVersion config.android.compileSdkVersion
    defaultConfig {
        if (isModule.toBoolean()) {// ② library沒(méi)有applicationId
            applicationId "com.ff.modulea"
        }
        minSdkVersion config.android.minSdkVersion
        targetSdkVersion config.android.targetSdkVersion
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            // ③ 加載不同位置的AndroidManifest
            if (isModule.toBoolean()) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }

    compileOptions {
        // ButterKnife 需要Java 8
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    lintOptions {
        // 禁用Google Search
        disable 'GoogleAppIndexingWarning'
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    annotationProcessor "com.jakewharton:butterknife-compiler:$config.dependencies.butterknife"
    api project(':baselib')
}

主要看上面①孝治、②、③ 處代碼审磁,主要說(shuō)下③谈飒,可以通過(guò)修改 SourceSets 中的屬性,修改 AndroidManifest 默認(rèn)的加載路徑态蒂,更多SourceSets介紹與使用杭措。

先看下作為 library 時(shí)的 AndroidManifest,上圖①處钾恢。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ff.moduleb">

    <application>
        <activity
            android:name="com.ff.moduleb.CaptureActivity"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:screenOrientation="portrait" />
    </application>

</manifest>

再對(duì)比下作為 application 的 AndroidManifest手素,上圖②處。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ff.moduleb">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".CaptureActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

為什么需要加載不同的 AndroidManifest 瘩蚪?
一是泉懦,因?yàn)?library 清單文件不需要指明 application 內(nèi)容;二是疹瘦,并不是每個(gè) Activity 都是 App 第一個(gè)啟動(dòng)的 Activity崩哩。

還需要注意一點(diǎn),由于組件化拱礁,可能會(huì)導(dǎo)致每個(gè) module 之間依賴的遠(yuǎn)程倉(cāng)庫(kù)版本不一致琢锋,出現(xiàn)異常情況辕漂,所以這里使用 config.gradle 統(tǒng)一配置版本呢灶。

ext {

    android = [
            compileSdkVersion: 28,
            minSdkVersion    : 19,
            targetSdkVersion : 28
    ]

    dependencies = [
            arouter_api     : "1.4.1",
            arouter_compiler: "1.2.2",
            butterknife     : "9.0.0",
            zxing           : "3.3.3"
    ]

    supportVersion = "28.0.0"
}

只需要在項(xiàng)目的 build.gradle 中引入即可使用吴超,更多詳細(xì)介紹

// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle"http:// 引入config.gradle
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.2'
        classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

空殼 App

里面沒(méi)有任何 java 代碼鸯乃。



只有一個(gè) AndroidManifest鲸阻,指明 application,我們會(huì)把 BaseApplication 放在 baselib 中缨睡。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ff.module">

    <application
        android:name="com.ff.baselib.base.BaseApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" />

</manifest>

唯一依賴是 baselib 庫(kù)中的 BaseApplication鸟悴。

apply plugin: 'com.android.application'

def config = rootProject.ext
android {
    compileSdkVersion config.android.compileSdkVersion
    defaultConfig {
        applicationId "com.ff.ui"
        minSdkVersion config.android.minSdkVersion
        targetSdkVersion config.android.targetSdkVersion
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        // ButterKnife 需要Java 8
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    lintOptions {
        // 禁用Google Search
        disable 'GoogleAppIndexingWarning'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    if (isModule.toBoolean()) {
        // 依賴baselib中的BaseApplication
        implementation project(':baselib')
    } else {
        // modulea和moduleb中依賴了baselib
        implementation project(':modulea')
        implementation project(':moduleb')
    }
}

公用的 Library

這里面可以放基類、工具類奖年、常量细诸、權(quán)限聲明、圖片網(wǎng)絡(luò)框架等等陋守。

我這里放入了一些常量震贵,BaseApplication,BaseActivity水评,二維碼掃描的 jar 包猩系。
至于 ButterKnife 需要如何引入,可以看下 ButterKnife最新版本使用的深坑中燥。

apply plugin: 'com.android.library'
// 雖然在library中使用butterknife寇甸,但僅在BaseActivity中bind,
// 不需要尋找控件疗涉,也就不需要生成R2拿霉,所以無(wú)需添加plugin
// apply plugin: 'com.jakewharton.butterknife'

def config = rootProject.ext// 定義變量
android {
    compileSdkVersion config.android.compileSdkVersion
    defaultConfig {
        minSdkVersion config.android.minSdkVersion
        targetSdkVersion config.android.targetSdkVersion
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    api "com.android.support:appcompat-v7:$config.supportVersion"
    api "com.jakewharton:butterknife:$config.dependencies.butterknife"
    // 僅在BaseActivity中bind,不需要尋找控件咱扣,也就不需要生成java文件绽淘,無(wú)需使用annotationProcessor
    // annotationProcessor "com.alibaba:arouter-compiler:$config.dependencies.arouter_compiler"
    api files('libs/zxing.jar')
}

權(quán)限聲明,可以都放在這個(gè) AndroidManifest 中偏窝。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ff.baselib">

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.FLASHLIGHT" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

組件 module

上面已經(jīng)設(shè)置好收恢,切換 module 的 application 和 library 屬性,這里就沒(méi)有什么其他工作了祭往。

注意不同 module 間的 layout 文件不要重名伦意,不然會(huì)出現(xiàn)找不到的現(xiàn)象,類名可以重復(fù)硼补,因?yàn)槊總€(gè) module 的包名是不一樣的驮肉,要是每個(gè) module 的包名都一樣我就無(wú)語(yǔ)了。

組件間跳轉(zhuǎn)

比如我們這里需要 modulea 中的 MainActivity 需要跳轉(zhuǎn)到 moduleb 中到 CaptureActivity 這個(gè)就需要使用路由框架了已骇,這里推薦阿里開源的路由框架 ARouter离钝,使用很便捷票编。

添加依賴和配置

官方示例代碼:

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {// ①
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
    // 替換成最新版本, 需要注意的是api
    // 要與compiler匹配使用,均使用最新版可以保證兼容
    compile 'com.alibaba:arouter-api:x.x.x'// ②
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'// ②
    ...
}
  1. 首先卵渴,要注意的是①慧域、③處代碼,需要同時(shí)出現(xiàn)浪读,不然會(huì)報(bào)錯(cuò):
ARouter::Compiler >>> No module name, for more information, look at gradle log.
  1. 在新版本中需要使用 api 代替 compiler:
dependencies {
    api 'com.alibaba:arouter-api:x.x.x'
    ...
}
  1. com.alibaba:arouter-api 中的 v4 包是 25 的昔榴,與我引入的 v7 包沖突(一般 v7 都包含 v4),所以需要使用 exclude 移除 com.alibaba:arouter-api 里面的 v4 包:
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    api "com.android.support:appcompat-v7:$config.supportVersion"
    // arouter-api中包含了v4包碘橘,與上面v7包中的v4沖突了
    api("com.alibaba:arouter-api:$config.dependencies.arouter_api") {
        // 默認(rèn)情況下v7中是包含V4包的互订,exclude的意思是去除v4包,這樣就可以解決沖突了
        exclude module: 'support-v4'// 根據(jù)組件名排除
        exclude group: 'com.android.support'// 根據(jù)包名排除
    }
}

關(guān)于 exclude 可以看 com.android.support版本沖突的解決辦法痘拆。

添加注解

官方示例代碼:

// 在支持路由的頁(yè)面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級(jí)仰禽,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}

初始化SDK

官方示例代碼:

if (isDebug()) {           // 這兩行必須寫在init之前,否則這些配置在init過(guò)程中將無(wú)效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 開啟調(diào)試模式(如果在InstantRun模式下運(yùn)行纺蛆,必須開啟調(diào)試模式吐葵!線上版本需要關(guān)閉,否則有安全風(fēng)險(xiǎn))
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化

發(fā)起路由操作

官方示例代碼:

// 1. 應(yīng)用內(nèi)簡(jiǎn)單的跳轉(zhuǎn)(通過(guò)URL跳轉(zhuǎn)在'進(jìn)階用法'中)
ARouter.getInstance().build("/test/activity").navigation();

// 2. 跳轉(zhuǎn)并攜帶參數(shù)
ARouter.getInstance().build("/test/1")
            .withLong("key1", 666L)
            .withString("key3", "888")
            .withObject("key4", new Test("Jack", "Rose"))
            .navigation();

具體的使用犹撒,包括 startActivityForResult 的實(shí)現(xiàn)折联,就不在這里粘出來(lái)了,可以下載demo看下识颊。
github項(xiàng)目地址

其他注意點(diǎn)

組件化開發(fā)中诚镰,資源文件不要重名,建議使用組建名作為前綴祥款。

參考資料

Android組件化方案
Android組件化初探
解決v4,v7包沖突問(wèn)題
探索Android路由框架-ARouter之基本使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末清笨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子刃跛,更是在濱河造成了極大的恐慌抠艾,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,835評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桨昙,死亡現(xiàn)場(chǎng)離奇詭異检号,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蛙酪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門齐苛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人桂塞,你說(shuō)我怎么就攤上這事凹蜂。” “怎么了?”我有些...
    開封第一講書人閱讀 156,481評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵玛痊,是天一觀的道長(zhǎng)汰瘫。 經(jīng)常有香客問(wèn)我,道長(zhǎng)擂煞,這世上最難降的妖魔是什么混弥? 我笑而不...
    開封第一講書人閱讀 56,303評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮颈娜,結(jié)果婚禮上剑逃,老公的妹妹穿的比我還像新娘浙宜。我一直安慰自己官辽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,375評(píng)論 5 384
  • 文/花漫 我一把揭開白布粟瞬。 她就那樣靜靜地躺著同仆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪裙品。 梳的紋絲不亂的頭發(fā)上俗批,一...
    開封第一講書人閱讀 49,729評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音市怎,去河邊找鬼岁忘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛区匠,可吹牛的內(nèi)容都是我干的干像。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼驰弄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼麻汰!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起戚篙,我...
    開封第一講書人閱讀 37,633評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤五鲫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后岔擂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體位喂,經(jīng)...
    沈念sama閱讀 44,088評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,443評(píng)論 2 326
  • 正文 我和宋清朗相戀三年乱灵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了塑崖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,563評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阔蛉,死狀恐怖弃舒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤聋呢,帶...
    沈念sama閱讀 34,251評(píng)論 4 328
  • 正文 年R本政府宣布苗踪,位于F島的核電站,受9級(jí)特大地震影響削锰,放射性物質(zhì)發(fā)生泄漏通铲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,827評(píng)論 3 312
  • 文/蒙蒙 一器贩、第九天 我趴在偏房一處隱蔽的房頂上張望颅夺。 院中可真熱鬧,春花似錦蛹稍、人聲如沸吧黄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拗慨。三九已至,卻和暖如春奉芦,著一層夾襖步出監(jiān)牢的瞬間赵抢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工声功, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烦却,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,240評(píng)論 2 360
  • 正文 我出身青樓先巴,卻偏偏與公主長(zhǎng)得像其爵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子筹裕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,435評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 我在盛蕭的風(fēng)中起舞 不高調(diào)醋闭,為低吟 亦在狂亂的雨中咆哮 恰悠悠,頌赳赳 誰(shuí)借的烈日一朵 誰(shuí)又在祈福禱告 尋不了 夏...
    煜朔閱讀 164評(píng)論 0 0
  • 我欺騙了你朝卒,因?yàn)槲也幌胱屇闶軅?我不想因?yàn)檫@個(gè)事麻煩你证逻。 這是善意謊言常用的理由。 聽起來(lái)很合理抗斤,但背后的動(dòng)機(jī)往...
    團(tuán)子君堅(jiān)持碎碎念閱讀 448評(píng)論 0 0
  • 視圖的寬高可以靈活變化 android對(duì)于的視圖布局的定義提供了一種很靈活的實(shí)現(xiàn)囚企,就是當(dāng)給視圖的寬高屬性設(shè)置wra...
    cxlin007閱讀 1,354評(píng)論 0 2
  • 在《廣東互聯(lián)網(wǎng)大會(huì)分享(一):怎么做好產(chǎn)品》一文中,彭晉杰曾言:豆瓣瑞眼,它是一個(gè)成功的產(chǎn)品龙宏,但也是一個(gè)不成功的產(chǎn)品。...
    彭晉杰閱讀 561評(píng)論 0 7