組件化開發(fā)歷程:

為何要組件化開發(fā)

最近也不知道什么原因就突然換了公司兴溜,新的公司新的需求。由于新的項(xiàng)目比較大耻陕,各個(gè)模塊需要解耦拙徽,所以就用到了組件化開發(fā)。而且APP版本不斷的迭代诗宣,新功能的不斷增加膘怕,業(yè)務(wù)也會變的越來越復(fù)雜,APP業(yè)務(wù)模塊的數(shù)量有可能還會繼續(xù)增加召庞,而且每個(gè)模塊的代碼也變的越來越多岛心,這樣發(fā)展下去單一工程下的APP架構(gòu)勢必會影響開發(fā)效率,增加項(xiàng)目的維護(hù)成本篮灼,每個(gè)工程師都要熟悉如此之多的代碼忘古,將很難進(jìn)行多人協(xié)作開發(fā),而且Android項(xiàng)目在編譯代碼的時(shí)候電腦會非匙缬眨卡髓堪,又因?yàn)閱我还こ滔麓a耦合嚴(yán)重,每修改一處代碼后都要重新編譯打包測試娘荡,導(dǎo)致非常耗時(shí)干旁,最重要的是這樣的代碼想要做單元測試根本無從下手,所以必須要有更靈活的架構(gòu)代替過去單一的工程架構(gòu)炮沐。

怎么組建化開發(fā)

image.png

上圖是組件化工程模型疤孕,為了方便理解這張架構(gòu)圖,下面會列舉一些組件化工程中用到的名詞的含義:

1.集成模式 :所有的業(yè)務(wù)組件被“app殼工程”依賴央拖,組成一個(gè)完整的APP;
2.組件模式:可以獨(dú)立開發(fā)業(yè)務(wù)組件,每一個(gè)業(yè)務(wù)組件就是一個(gè)APP鲜戒;
3.app殼工程:負(fù)責(zé)管理各個(gè)業(yè)務(wù)組件专控,和打包apk,沒有具體的業(yè)務(wù)功能遏餐;
4.業(yè)務(wù)組件:根據(jù)公司具體業(yè)務(wù)而獨(dú)立形成一個(gè)的工程伦腐;
5.功能組件 提供開發(fā)APP的某些基礎(chǔ)功能,例如打印日志失都、樹狀圖等柏蘑;
6.Main組件 屬于業(yè)務(wù)組件,指定APP啟動(dòng)頁面粹庞、主界面咳焚;
7.Common組件 屬于功能組件,支撐業(yè)務(wù)組件的基礎(chǔ)庞溜,提供多數(shù)業(yè)務(wù)組件需要的功能革半,例如提供網(wǎng)絡(luò)請求功能;

Android APP組件化架構(gòu)的目標(biāo)是告別結(jié)構(gòu)臃腫流码,讓各個(gè)業(yè)務(wù)變得相對獨(dú)立又官,業(yè)務(wù)組件在組件模式下可以獨(dú)立開發(fā),而在集成模式下又可以變?yōu)閍rr包集成到“app殼工程”中漫试,組成一個(gè)完整功能的APP六敬;
從組件化工程模型中可以看到,業(yè)務(wù)組件之間是獨(dú)立的驾荣,沒有關(guān)聯(lián)的外构,這些業(yè)務(wù)組件在集成模式下是一個(gè)個(gè)library,被app殼工程所依賴秘车,組成一個(gè)具有完整業(yè)務(wù)功能的APP應(yīng)用典勇,但是在組件開發(fā)模式下,業(yè)務(wù)組件又變成了一個(gè)個(gè)application叮趴,它們可以獨(dú)立開發(fā)和調(diào)試割笙,由于在組件開發(fā)模式下,業(yè)務(wù)組件們的代碼量相比于完整的項(xiàng)目差了很遠(yuǎn)眯亦,因此在運(yùn)行時(shí)可以顯著減少編譯時(shí)間伤溉。

組件化實(shí)施流程

1>組件模式和集成模式的轉(zhuǎn)換

Android Studio中的Module主要有兩種屬性,分別為:
1妻率、application屬性乱顾,可以獨(dú)立運(yùn)行的Android程序,也就是我們的APP宫静;
apply plugin: ‘com.android.application’
2.library屬性走净,不可以獨(dú)立運(yùn)行券时,一般是Android程序依賴的庫文件;
apply plugin: ‘com.android.library’

Module的屬性是在每個(gè)組件的 build.gradle 文件中配置的伏伯,當(dāng)我們在組件模式開發(fā)時(shí)橘洞,業(yè)務(wù)組件應(yīng)處于application屬性,這時(shí)的業(yè)務(wù)組件就是一個(gè) Android App说搅,可以獨(dú)立開發(fā)和調(diào)試炸枣;而當(dāng)我們轉(zhuǎn)換到集成模式開發(fā)時(shí),業(yè)務(wù)組件應(yīng)該處于 library 屬性弄唧,這樣才能被我們的“app殼工程”所依賴适肠,組成一個(gè)具有完整功能的APP;

Gradle自動(dòng)構(gòu)建工具有一個(gè)重要屬性候引,可以幫助我們完成這個(gè)事情侯养。每當(dāng)我們用AndroidStudio創(chuàng)建一個(gè)Android項(xiàng)目后,就會在項(xiàng)目的根目錄中生成一個(gè)文件 gradle.properties背伴,我們將使用這個(gè)文件的一個(gè)重要屬性:在Android項(xiàng)目中的任何一個(gè)build.gradle文件中都可以把gradle.properties中的常量讀取出來沸毁;那么我們在上面提到解決辦法就有了實(shí)際行動(dòng)的方法,首先我們在gradle.properties中定義一個(gè)常量值 isModule(是否是組件開發(fā)模式傻寂,true為是息尺,false為否):

每次更改“isModule”的值后,需要點(diǎn)擊 "Sync Project" 按鈕
isModule=false

然后我們在業(yè)務(wù)組件的build.gradle中讀取 isModule疾掰,但是 gradle.properties 還有一個(gè)重要屬性: gradle.properties 中的數(shù)據(jù)類型都是String類型搂誉,使用其他數(shù)據(jù)類型需要自行轉(zhuǎn)換;也就是說我們讀到 isModule 是個(gè)String類型的值静檬,而我們需要的是Boolean值炭懊,代碼如下:

if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}

全局Context的獲取及組件數(shù)據(jù)初始化

1.當(dāng)Android程序啟動(dòng)時(shí),Android系統(tǒng)會為每個(gè)程序創(chuàng)建一個(gè) Application 類的對象拂檩,并且只創(chuàng)建一個(gè)侮腹,application對象的生命周期是整個(gè)程序中最長的,它的生命周期就等于這個(gè)程序的生命周期稻励。在默認(rèn)情況下應(yīng)用系統(tǒng)會自動(dòng)生成 Application 對象父阻,但是如果我們自定義了 Application,那就需要在 AndroidManifest.xml 中聲明告知系統(tǒng)望抽,實(shí)例化的時(shí)候加矛,是實(shí)例化我們自定義的,而非默認(rèn)的煤篙。
但是我們在組件化開發(fā)的時(shí)候斟览,可能為了數(shù)據(jù)的問題每一個(gè)組件都會自定義一個(gè)Application類,如果我們在自己的組件中開發(fā)時(shí)需要獲取 全局的Context辑奈,一般都會直接獲取 application 對象苛茂,但是當(dāng)所有組件要打包合并在一起的時(shí)候就會出現(xiàn)問題已烤,因?yàn)樽詈蟪绦蛑挥幸粋€(gè) Application,我們組件中自己定義的 Application 肯定是沒法使用的味悄,因此我們需要想辦法再任何一個(gè)業(yè)務(wù)組件中都能獲取到全局的 Context草戈,而且這個(gè) Context 不管是在組件開發(fā)模式還是在集成開發(fā)模式都是生效的。
2.在 組件化工程模型圖中侍瑟,功能組件集合中有一個(gè) Common 組件, Common 有公共丙猬、公用涨颜、共同的意思,所以這個(gè)組件中主要封裝了項(xiàng)目中需要的基礎(chǔ)功能茧球,并且每一個(gè)業(yè)務(wù)組件都要依賴Common組件庭瑰,Common 組件就像是萬丈高樓的地基,而業(yè)務(wù)組件就是在 Common 組件這個(gè)地基上搭建起來我們的APP的抢埋,Common 組件會專門在一個(gè)章節(jié)中講解弹灭,這里只講 Common組件中的一個(gè)功能,在Common組件中我們封裝了項(xiàng)目中用到的各種Base類揪垄,這些基類中就有BaseApplication 類穷吮。
BaseApplication 主要用于各個(gè)業(yè)務(wù)組件和app殼工程中聲明的 Application 類繼承用的,只要各個(gè)業(yè)務(wù)組件和app殼工程中聲明的Application類繼承了 BaseApplication饥努,當(dāng)應(yīng)用啟動(dòng)時(shí) BaseApplication 就會被動(dòng)實(shí)例化捡鱼,這樣從 BaseApplication 獲取的 Context 就會生效,也就從根本上解決了我們不能直接從各個(gè)組件獲取全局 Context 的問題酷愧;

組件之間調(diào)用和通信

在組件化開發(fā)的時(shí)候驾诈,組件之間是沒有依賴關(guān)系,我們不能在使用顯示調(diào)用來跳轉(zhuǎn)頁面了溶浴,因?yàn)槲覀兘M件化的目的之一就是解決模塊間的強(qiáng)依賴問題乍迄,假如現(xiàn)在要從A業(yè)務(wù)組件跳轉(zhuǎn)到業(yè)務(wù)B組件,并且要攜帶參數(shù)跳轉(zhuǎn)士败,這時(shí)候怎么辦呢闯两?而且組件這么多怎么管理也是個(gè)問題,這時(shí)候就需要引入“路由”的概念了拱烁,由本文開始的組件化模型下的業(yè)務(wù)關(guān)系圖可知路由就是起到一個(gè)轉(zhuǎn)發(fā)的作用生蚁。通過路由實(shí)現(xiàn)模塊之間的通信。

組件開發(fā)的demo已經(jīng)上傳到github上 :
開發(fā)流程:

1.配置工程

Android 組件開發(fā)指南

1. 簡介

ACMobi跨平臺移動(dòng)開發(fā)框架內(nèi)部采用組件化結(jié)構(gòu)設(shè)計(jì)戏自。組件之間相互獨(dú)立邦投,不存在依賴關(guān)系,通過框架層的路由組件進(jìn)行數(shù)據(jù)通信降低耦合度擅笔。組件化使得不同團(tuán)隊(duì)并行開發(fā)各業(yè)務(wù)志衣,極大提高迭代效率屯援。本文介紹了組件的開發(fā)、調(diào)試念脯、測試狞洋、發(fā)布等一系列流程。

Android插件入口類必須繼承ACComponentBase類绿店。Android組件入口類類名需要配置到component.xml中吉懊。

2. 開發(fā)環(huán)境

已經(jīng)有Android開發(fā)環(huán)境的可以略過本段

Android的開發(fā)環(huán)境搭建主要包括JDK、Android Studio的安裝假勿。

2.1.JDK安裝驗(yàn)證

安裝完成之后借嗽,可以在檢查JDK是否安裝成功。打開終端转培,輸入java –version 查看JDK的版本信息恶导。出現(xiàn)類似下面的畫面表示安裝成功了:

[圖片上傳失敗...(image-7c4f9d-1533362955000)]

2.2.Android Studio

Android Studio 的安裝教程網(wǎng)上有很多,請自行搜索一下浸须,可能需要使用代理

官網(wǎng)地址

2.3.源碼

點(diǎn)擊下載Android組件開發(fā)基礎(chǔ)工程

3. 組件開發(fā)

3.1.配置工程

下載組件開發(fā)基礎(chǔ)工程惨寿, Android Studio點(diǎn)擊File->Open...,選擇剛剛下載下來的基礎(chǔ)工程ACMobiNativeMainDebug打開删窒。

image

3.2.新建TabBar組件

在工程下點(diǎn)擊New->Module裂垦,新建ACComponentTabBar模塊,如下圖:

image
image
image

3.3.定義啟動(dòng)的Activity

啟動(dòng)的activity固定的全類名為:com.appcan.activity.MainActivity

因此需要在java下新建com.appcan.activity包易稠,如下:

image
image

在該包中新建MainAcitivity缸废,繼承Acitivity,如下:

image

該Acitivity中定義一個(gè)簡單的button驶社。

3.4.路由配置

3.4.1.路由引用

在模塊ACComponentTabBar中的build.gradle文件中添加如下引用:

compile 'com.appcan.engine:ACRouter:1.0.0'//注意1.0.0為初始版本企量,開發(fā)時(shí)注意替換為最新版本
image

添加完成之后,點(diǎn)擊Sync Now亡电,等待同步完成届巩。

3.4.2.新建入口類

在ACComponentTabBar模塊java中新建Class,需繼承ACComponentBase類份乒,如下圖:

image

該類必須重寫onCreate方法恕汇,在該方法中做一些初始化的操作。因?yàn)樵谠O(shè)置啟動(dòng)頁時(shí)或辖,不需要對外提供方法瘾英,所以這里Initializer類的onCreate方法里面可以為空,若需要對外提供方法颂暇,則注冊路由必須在這個(gè)方法中實(shí)現(xiàn)(實(shí)現(xiàn)方式后文介紹)缺谴。

3.4.3.配置文件

在ACComponentTabBar模塊的res目錄下,新建xml文件夾耳鸯,并新建component.xml文件湿蛔,如下:

image

內(nèi)容如下:

<?xml version="1.0" encoding="utf-8"?>
<acplugins>
    <!--其中className屬性值即為繼承自ACComponentBase的入口類膀曾,moduleId屬性值為組件唯一標(biāo)示-->
    <plugin className="com.appcan.component.accomponenttabbar.Initializer" moduleId="main"/>
</acplugins>

配置中className屬性值為繼承自ACComponentBase的入口類,moduleId屬性值為組件唯一標(biāo)示

并將該配置加入app模塊的res/xml/component.xml文件中阳啥。如下:

image

3.4.4.導(dǎo)入組件

在app模塊下的build.gradle文件中添谊,新增ACComponentTabBar模塊的工程引用,如下:

compile project(':ACComponentTabBar')

3.4.5.AndroidManifest.xml配置說明

關(guān)于ACMobiNativeMainDebug工程AndroidManifest.xml的配置有如下幾點(diǎn)需要注意

1察迟、工程依賴了NativeMain引擎斩狱,在NativeMain引擎的AndroidManifest.xml中已經(jīng)指定了Application的具體實(shí)現(xiàn)類,其中包含了引擎的初始化等重要操作卷拘,所以在ACMobiNativeMainDebug工程任何模塊的AndroidManifest.xml中<application/>標(biāo)簽下都不允許添加android:name="xxxx"屬性再來指定其它的Application實(shí)現(xiàn)類喊废,如下圖。


image

2栗弟、另外NativeMain引擎中已經(jīng)配置好入口Activity,所以要把入口組件的啟動(dòng)界面命名為com.appcan.activity.MainActivity工闺,如上圖乍赫。

3、ACMobiNativeMainDebug中的app模塊是示例工程的啟動(dòng)模塊陆蟆,其作用僅為創(chuàng)建程序入口雷厂,關(guān)聯(lián)ACComponentTabBar和ACComponentActivity。通過打包服務(wù)器打包時(shí)是不需要上傳app模塊的叠殷,app中的修改并不會在打包生成的apk中生效改鲫,為了避免造成困擾請不要在app模塊中做任何修改。

image

4林束、所有組件不允許配置android:allowBackup屬性像棘,引擎中已指定該屬性值為fasle,當(dāng)組件中配置為true時(shí)會出現(xiàn)沖突導(dǎo)致打包失敗壶冒。

5缕题、為了做到主題可配置,引擎中并未指定android:theme屬性胖腾,但該屬性又是打包必要屬性烟零,缺少該屬性會導(dǎo)致打包失敗,所以必須在入口組件中進(jìn)行配置咸作。


image

3.4.6.組件初始化

上面提到NativeMain引擎封裝了Application的實(shí)現(xiàn)锨阿,那么組件中有些需要在應(yīng)用初始化時(shí)執(zhí)行的操作該如何處理?
引擎的做法是將Application中onCreate记罚、onConfigurationChanged墅诡、onTerminate、onLowMemory毫胜、onTrimMemory等系統(tǒng)方法向組件分發(fā)书斜,并將Application實(shí)例以方法參數(shù)的形式傳遞給各個(gè)組件诬辈。所有組件中必須有一個(gè)繼承于ACComponentBase.java的類來完成組件的初始化工作和其它Application生命周期的響應(yīng)。其中onCreate被定義為抽象方法組件必須實(shí)現(xiàn)荐吉,組件初始化需要執(zhí)行的操作必須放在這里焙糟。其它onConfigurationChanged、onTerminate样屠、onLowMemory穿撮、onTrimMemory等方法可根據(jù)情況重寫。

image
image

以ACComponentTabBar組件為例痪欲,Initializer.java繼承了ACComponentBase.java實(shí)現(xiàn)了OnCreate方法悦穿,在該組件初始化時(shí)將自己注冊到路由中供其它組件調(diào)用。

public class Initializer extends ACComponentBase {
    @Override
    public void onCreate(Application application) {
        ACRouter.getACRouter().regist(new ServiceStub<IntentBridge>() {
            @Override
            public String initModule() {
                return getId(Initializer.class);
            }
        });
    }
}

3.4.7.運(yùn)行app

點(diǎn)擊運(yùn)行app模塊业踢,ACComponentTabBar模塊中定義的MainActivity就會被啟動(dòng)栗柒,該activity中定義了一個(gè)按鈕,啟動(dòng)其他模塊的Activity知举,下文講述啟動(dòng)其他模塊Activity的方法瞬沦。

3.5.模塊之間通過路由調(diào)用

在新模塊中定義activity供ACComponentTabBar模塊啟動(dòng)。新建模塊ACComponentActivity雇锡,根據(jù)步驟3.4.1逛钻,引入ACRoute。

3.5.1.創(chuàng)建入口類

根據(jù)步驟3.4.2中描述的同樣方法創(chuàng)建入口類锰提,在入口類的onCreate方法中添加路由的注冊曙痘。注冊代碼如下:

package com.appcan.component.accomponenttabbar;
import android.app.Application;

import com.appcan.router.ACComponentBase;
import com.appcan.router.ACRouter;
import com.appcan.router.ServiceStub;
public class Initializer extends ACComponentBase {
    @Override
    public void onCreate(Application application) {
        ACRouter.getACRouter().regist(new ServiceStub<IntentBridge>() {
            @Override
            public String initModule() {
            return getId(Initializer.class);//這里通過路由提供的獲取該模塊唯一標(biāo)示的方法getId(Class clazz)返回該模塊注冊路由時(shí)的唯一標(biāo)識,在其他模塊調(diào)用時(shí)需要用到立肘。
            }
        });
    }
}

以上代碼中指定路由方法的接口類為IntentBridge边坤,則需要在該模塊下新建IntentBridge接口類。在ACComponentActivity模塊java中新建Class赛不,修改類型為interface惩嘉,繼承RouterService類,如下圖:

image

在該類中定義對外提供的接口踢故,定義代碼如下:

    @RouterUri(ACUri.Patten.ACTIVITY)//RouterUri注解指定接口類型文黎,必須是Patten常量
    @RouterImp(impClass = SecondActivity.class)//RouterImp注解指定接口實(shí)現(xiàn)類
    void startSecondActivity(RouterCallback callback
            , @RouterParam("context") Context context
            , @RouterParam("param") String param1
            , @RouterParam(value = ActivityProcessor.ACTIVITY_REQUEST_CODE, type = int.class) int requestCode);//startSecondActivity為方法名稱,callback為固定必選項(xiàng)殿较,RouterParam注解指定的參數(shù)為可選項(xiàng)耸峭,注解括號中的內(nèi)容指定在其他模塊調(diào)用時(shí)參數(shù)的關(guān)鍵字,緊跟著注解的是參數(shù)類型淋纲。

然后新建SecondActivity,將SecondActivity注冊到AndroidManifest.xml中劳闹。

下一步,根據(jù)步驟3.4.3和3.4.4配置xml文件,并將配置添加到app中的component.xml文件中本涕,同時(shí)在app的build.gradle文件中添加ACComponentActivity模塊的引用业汰。

3.5.2.接口調(diào)用

接口配置完成之后,可以在模塊ACComponentTabBar中調(diào)用ACComponentActivity模塊中的方法菩颖。調(diào)用方法如下:

String uri = "second://activity/startSecondActivity?param=test&" + ACTIVITY_REQUEST_CODE + "=" + REQUEST_SECOND_CODE;
        ACRouter.getACRouter().getmClient().invoke(MainActivity.this
                , new ACUri(uri)
                , new RouterCallback() {
                    @Override
                    public void callback(RouterCallback.Result result) {
                        Log.i("callback", result + "");
                    }
                });                

其中uri的組合規(guī)則為"模塊在路由中的唯一標(biāo)識符://接口類型/接口名稱?接口參數(shù)1=value1&接口參數(shù)2=value2"

對應(yīng)關(guān)系如下圖:(相同顏色箭頭表示一個(gè)對應(yīng)關(guān)系)

image

注意startSecondActivity方法定義了通過setResult返回給前一個(gè)Activity數(shù)據(jù)样漆,則需要被啟動(dòng)的SecondActivity類中調(diào)用setResult方法,調(diào)用如下:

setResult(Activity.RESULT_OK);
finish();

同時(shí)晦闰,需要在調(diào)用startSecondActivity方法的MainActivity中重寫onActivityResult方法放祟。代碼如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if(requestCode == REQUEST_SECOND_CODE){
        Toast.makeText(this, R.string.activity_back, Toast.LENGTH_SHORT).show();
    }
}

至此,啟動(dòng)其他模塊Activity的調(diào)用方法全部配置完畢呻右, 可以運(yùn)行程序了跪妥。

3.5.3.METHOD類型方法定義

上文講述的是ACTIVITY類型方法定義,即在定義方法時(shí)RouterUri注解指定的ACUri.Patten常量類型声滥,本節(jié)講述METHOD類型方法的定義眉撵。

ACComponentActivity模塊中的SecondActivity中定義一個(gè)按鈕,點(diǎn)擊該按鈕改變ACComponentTabBar模塊中MainActivity界面的背景顏色落塑。

3.5.3.1.ACComponentTabBar模塊定義changeTabBarBackground方法

根據(jù)上文講述的方法执桌,ACComponentTabBar模塊定義繼承自ACComponentBase的入口類Initializer,在Initializer的onCreate方法代碼如下:

@Override
public void onCreate(Application application) {
    ACRouter.getACRouter().regist(new ServiceStub<IntentBridge>() {
        @Override
        public String initModule() {
            return getId(Initializer.class);//在路由中定義的唯一標(biāo)識符
        }
    });
}

以及繼承自RouterService的接口IntentBridge類,在IntentBridge類中定義接口芜赌,代碼如下:

@RouterUri(ACUri.Patten.METHOD)//接口類型為METHOD
@RouterImp(impClass = ImplementMethod.class)//接口的實(shí)現(xiàn)在ImplementMethod類中
void changeTabBarBackground(RouterCallback callback, @RouterParam("color") String bgColor);//接口的名稱changeTabBarBackground,固定參數(shù)callback和RouterParam注解指定color參數(shù)伴逸。

ImplementMethod類中代碼如下:

public class ImplementMethod {
    public void changeTabBarBackground(RouterCallback callback, String color){
        RouterCallback.Result result;
        try {
            MainActivity.getInstance().setBackgroundColor(color);//MainActivity方法需要定義setBackgroundColor方法
            result = new RouterCallback.Result(
                    RouterCallback.Result.SUCCESS,
                    null,
                    null);
        } catch (Exception e) {
            e.printStackTrace();
            result = new RouterCallback.Result(
                    RouterCallback.Result.FAIL,
                    e.getMessage(),
                    null);
        }
        callback.callback(result);
    }
}

對應(yīng)關(guān)系如下圖:

image

緊接著在MainActivity方法中定義setBackgroundColor方法實(shí)現(xiàn)背景顏色的更換設(shè)置缠沈,代碼如下:

public void setBackgroundColor(String color) {
    findViewById(R.id.bgLinearLayout).setBackgroundColor(Color.parseColor(color));
}

至此,ACComponentTabBar模塊METHOD類型接口定義完畢错蝴,下一步在ACComponentActivity接口中調(diào)用洲愤。

3.5.3.2.ACComponentActivity模塊調(diào)用changeTabBarBackground方法

在ACComponentActivity的按鈕點(diǎn)擊事件中加入如下代碼:

String uri = "main://method/changeTabBarBackground?color=#ff0000";
ACRouter.getACRouter().getmClient().invoke(new ACUri(uri), new RouterCallback() {
    @Override
    public void callback(Result result) {
        if(result != null && result.getCode() == Result.SUCCESS){
            Toast.makeText(SecondActivity.this, "設(shè)置成功", Toast.LENGTH_LONG).show();
        }else{
            Toast.makeText(SecondActivity.this, "設(shè)置失敗", Toast.LENGTH_LONG).show();
        }
    }
});

到這里,ACComponentActivity模塊調(diào)用ACComponentTabBar模塊的method類型接口定義及調(diào)用完成顷锰。

以上代碼柬赐,Android組件開發(fā)Demo中,開發(fā)者可自行下載查看源碼及運(yùn)行查看效果官紫。

4.路由SDK

4.1.注解

4.1.1.RouterUri

該注解指定接口的類型肛宋,在繼承自RouterService的接口類中定義方法時(shí)需要使用,目前只支持兩種束世,activity和method

4.1.1.1.activity

activity類型的接口表示啟動(dòng)一個(gè)Activity酝陈,定義方法時(shí)指定RouterUri注解的值為:ACUri.Patten.ACTIVITY,如下:

    @RouterUri(ACUri.Patten.ACTIVITY)
    @RouterImp(impClass = SecondActivity.class)
    void startSecondActivity(RouterCallback callback
            , @RouterParam("context") Context context
            , @RouterParam("param") String param
            , @RouterParam(value = ActivityProcessor.ACTIVITY_REQUEST_CODE, type = int.class) int requestCode);

調(diào)用時(shí)uri的組成為:

String uri = "唯一標(biāo)識符://activity/...";
4.1.1.2.method

method類型的接口表示調(diào)用一個(gè)方法毁涉,定義方法時(shí)指定RouterUri的值為:ACUri.Patten.METHOD沉帮,如下:

    @RouterUri(ACUri.Patten.METHOD)
    @RouterImp(impClass = ImplementMethod.class)
    void changeTabBarBackground(RouterCallback callback, @RouterParam("color") String bgColor);

調(diào)用時(shí)uri的組成為:

String uri = "唯一標(biāo)識符://method/...";

4.1.2.RouterImp

該注解指定實(shí)現(xiàn)類,若RouterUri注解值為activity,則RouterImp即是要啟動(dòng)的Activity類穆壕;若RouterUri注解值為method待牵,則RouterImp是一個(gè)class,class中需包含方法名稱及參數(shù)都相同的方法喇勋。如4.1.1.2中定義的方法就表示需要一個(gè)ImplementMethod.class類缨该,該類中包含方法名稱為changeTabBarBackground,參數(shù)為callback和color的方法茄蚯,如下:

public class ImplementMethod {
    public void changeTabBarBackground(RouterCallback callback, String color){
    }
}

若一個(gè)模塊中包含多個(gè)method類型的方法压彭,這些方法都可以定義在同一個(gè)實(shí)現(xiàn)類中。

4.1.3.RouterParam

若是自定義的參數(shù)渗常,則可以直接寫成:

@RouterParam("參數(shù)名稱") 參數(shù)類型 形參名稱

參數(shù)名稱是指定調(diào)用時(shí)uri中參數(shù)的關(guān)鍵字壮不,兩者必須保持一致。形參名稱可隨意定義皱碘,無影響询一。

若是路由開放的特定的參數(shù),即參數(shù)名稱和參數(shù)類型固定癌椿,則寫法如下:

@RouterParam(value = 參數(shù)名稱, type = 參數(shù)類型) int 形參名稱)

目前特定的參數(shù)有兩種健蕊,

一種是requestCode:

@RouterParam(value = ActivityProcessor.ACTIVITY_REQUEST_CODE, type = int.class) int requestCode)

該參數(shù)表示啟動(dòng)的Activity可以通過setResult回傳給調(diào)用者數(shù)據(jù),上文有詳細(xì)講解踢俄。

另一種是flags:

@RouterParam(value = ActivityProcessor.ACTIVITY_FLAGS, type = int.class) int flags

該參數(shù)表示啟動(dòng)Activity時(shí)的intent的flags參數(shù)缩功。

4.2.callback

callback參數(shù)固定表示通過路由調(diào)用其他模塊接口時(shí)的回調(diào)方法。該方法有一個(gè)Result類型的參數(shù)都办,Result是一個(gè)內(nèi)部JavaBean的類嫡锌,聲明了三個(gè)成員變量,code琳钉,msg和data势木,開發(fā)者可根據(jù)需要定義這三個(gè)參數(shù)的值。

        private int code;
        private String msg;
        private String data;

若RouterUri指定為activity類型歌懒,則callback回調(diào)方法是路由處理的啦桌,不會傳遞給被啟動(dòng)的Activity觸發(fā)。組件開發(fā)時(shí)及皂,只需要在調(diào)用方法時(shí)對回調(diào)的內(nèi)容做處理甫男,如下:

        String uri = "second://activity/startSecondActivity?param=test&" + ACTIVITY_REQUEST_CODE + "=" + REQUEST_SECOND_CODE;
        ACRouter.getACRouter().getmClient().invoke(MainActivity.this
                , new ACUri(uri)
                , new RouterCallback() {
                    @Override
                    public void callback(RouterCallback.Result result) {
                        //只有在啟動(dòng)activity失敗時(shí),該方法被調(diào)用躲庄,result包含code和data參數(shù)查剖。code表示錯(cuò)誤碼,data表示錯(cuò)誤信息噪窘。
                    }
                });

若RouterUri指定為method類型笋庄,則callback回調(diào)方法會傳遞給方法實(shí)現(xiàn)中去觸發(fā)效扫,如下:

   public void changeTabBarBackground(RouterCallback callback, String color){
        RouterCallback.Result result;
        try {
            MainActivity.getInstance().setBackgroundColor(color);
            result = new RouterCallback.Result(
                    RouterCallback.Result.SUCCESS,
                    null,
                    null);
        } catch (Exception e) {
            e.printStackTrace();
            result = new RouterCallback.Result(
                    RouterCallback.Result.FAIL,
                    e.getMessage(),
                    null);
        }
        callback.callback(result);
    }

5.上傳組件打包測試

5.1.腳本引用

在ACComponentTabBar模塊的build.gradle文件末尾加上如下代碼:

apply plugin: 'ACComponent'
apply plugin: 'maven'

def VERSION_NAME = '1.0.0'//組件版本號
def DESCRIPTION = '初始化'//更新日志
def ARTIFACT_ID = 'ACComponentTabBar'//指定組件名稱

ACComponent{
    name = "$ARTIFACT_ID"
    versionName = "$VERSION_NAME"
    description = "$DESCRIPTION"
}


image

加入代碼之后,還需要在工程下的gradle.properties文件中添加MARVEN_PATH,該值為ACMobi的官方maven庫地址直砂,其中包括ACComponent(組件出包腳本)等官方腳本,在工程的build.gradle中需要加入對該腳本的引用.

buildscript {
    repositories {
         jcenter()
         maven {
           url MARVEN_PATH
        }
    }
    ...
}
image

配置完成之后點(diǎn)擊“Sync Now”等待同步完成菌仁。

5.2.新建組件配置文件夾

在ACComponentTabBar模塊的根目錄下,新建跟build.gradle中定義的ARTIFACT_ID變量值相同名稱的文件夾静暂,即ACComponentTabBar济丘,在ACComponentTabBar文件夾下新建文件名為ACComponentTabBar(即ARTIFACT_ID的值)的xml文件以及info.xml文件。如下圖:

image

ACComponentTabBar.xml文件內(nèi)容如下:(該文件內(nèi)容固定洽蛀,無需更改)

<?xml version="1.0" encoding="utf-8"?>
<plugin>
    <manifest_config>

    </manifest_config>
    <dependencies>
    </dependencies>
</plugin>

info.xml文件內(nèi)容如下:

<?xml version="1.0" encoding="utf-8"?>
<acplugins>
    <plugin acName="ACComponentTabBar" version="1.0.0" build="0" desc="xxxx" type="compoment">
        <info>0:測試組件Demo</info>
    </plugin>
</acplugins>

特別注意:

  1. info文件只需要按照上述內(nèi)容做初始化設(shè)置即可摹迷,后面版本升級不需要手動(dòng)修改該文件,該文件中除了acName的屬性值"ACComponentTabBar"和info標(biāo)簽內(nèi)的文字"測試組件Demo"會根據(jù)組件不同而改變郊供,其余內(nèi)容必須保證不變峡碉。
  2. gradle出包腳本會根據(jù)build.gradle的配置,自動(dòng)更新inof.xml文件內(nèi)容驮审。請參考5.5章節(jié)鲫寄。
  3. desc屬性的值為組件功能簡介,必須填寫疯淫。
  4. type="compoment"不可修改地来,表示庫為組件類型。

5.3.本地aar引用配置

如果組件中有用到本地aar文件熙掺,需要在工程根目錄下新建aar文件夾未斑,并在工程的build.gradle文件中加入對aar文件夾的配置。
所有被引用的*.aar文件必須放置于該目錄下 如圖:

image
image
repositories {
    ...
    flatDir {
        dirs '../aar'
    }
}
dependencies {
    ...
    compile(name:'testaar-1.0.0', ext:'aar')
}

5.4.組件混淆配置

在打包服務(wù)器打正式包時(shí)會進(jìn)行代碼混淆,組件需要配置混淆文件告訴打包服務(wù)器該如何混淆。

5.4.1.新建組件混淆文件

在組件module的根目錄下新建proguard.pro文件瘪阁,并將混淆配置寫入該文件臭蚁,注意編寫時(shí)請嚴(yán)格遵循ProGuard語法,如下圖:

image

注意proguard.pro文件只需要按照上述內(nèi)容編寫肌似、放置即可费就,不需要在組件build.gradle中的buildTypes里配置模塊混淆,打包服務(wù)器會自動(dòng)合并

5.5.運(yùn)行腳本

根據(jù)需求先修改build.gradle中以下兩個(gè)變量的值川队。

def VERSION_NAME = '1.0.0'//組件版本號
def DESCRIPTION = '初始化'//更新日志

組件升級只需要更新VERSION_NAMEDESCRIPTION即可力细,gradle出包腳本會自動(dòng)更新變量值到info.xml中。

5.5.1.buildComponent

和步驟5.5.1一樣的方式固额,雙擊ACComponentTabBar模塊下的other->buildComponent執(zhí)行該task眠蚂,如下圖:

image

運(yùn)行完成之后,生成的組件包在ACComponentTabBar根目錄下斗躏,文件名為:ACComponentTabBar-android-1.0.0.zip逝慧,將該組件包上傳到打包服務(wù)器即可以打包在項(xiàng)目中使用。

6.ACComponentMyView

ACComponentMyView是一個(gè)提供視圖組件的demo,該組件中定義了一個(gè)TextView笛臣,并提供了一個(gè)路由方法getTextView(...)供其他組件調(diào)用云稚,最后通過回調(diào)的方式讓其他組件獲得這個(gè)TextView。


image

6.1.創(chuàng)建View

在ACComponentMyView中創(chuàng)建了一個(gè)layout文件myview_textview.xml沈堡,文件中僅包含一個(gè)TextView


image

通過LayoutInflater方法初始化TextView對象


image

6.2.傳遞View

ImplementMethod類中g(shù)etTextView方法是TextView傳遞的具體實(shí)現(xiàn)静陈,具體內(nèi)容就是將TextView解析出來通過RouterCallback返回給其他組件,RouterCallback<T>中的范型T要和View類型保持一致這里是TextView

@RouterUri(ACUri.Patten.METHOD)
    @RouterImp(impClass = ImplementMethod.class)
    void getTextView(@RouterParam("context") Context context, RouterCallback<TextView> callback);
public class ImplementMethod {
    
        public void getTextView(Context context, RouterCallback<TextView> callback){
            RouterCallback.Result<TextView> result;
            try {
                TextView textView = (TextView) MyViewActivity.getInstance().getWindow().getLayoutInflater().inflate(R.layout.myview_textview, null);
                textView.setText("我是ACComponentMyView組件中的TextView");
    
                result = new RouterCallback.Result<>(
                        RouterCallback.Result.SUCCESS,
                        null,
                        textView);
            } catch (Exception e) {
                e.printStackTrace();
                result = new RouterCallback.Result<>(
                        RouterCallback.Result.FAIL,
                        e.getMessage(),
                        null);
            }
            callback.callback(result);
        }
    }

ACComponentActivity組件中拿到TextView后將其添加到一個(gè)LinearLayout中

image

6.3.注意

注意一

如果ACComponentMyView中的View對象已經(jīng)被添加到了具體的布局中诞丽,那么這個(gè)對象在傳遞到其他的組件后是不允許添加到其他父布局的鲸拥,要遵循一個(gè)View只能被添加到一個(gè)父布局的原則。

例子里是通過LayoutInflater初始化了一個(gè)新的TextView對象僧免,沒有被添加到任何布局刑赶,所以在傳遞到ACComponentActivity組件后能夠被添加到ACComponentActivity的布局中。

注意二

對于自定義View由于組件之間類型不能識別猬膨,自定義View在傳遞到其他組件之后只能作為Android系統(tǒng)View使用角撞。如果要用到自定義屬性和方法,需要將自定義View定義到公共包中勃痴,并在兩個(gè)組件中添加引用谒所。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沛申,隨后出現(xiàn)的幾起案子劣领,更是在濱河造成了極大的恐慌,老刑警劉巖铁材,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尖淘,死亡現(xiàn)場離奇詭異,居然都是意外死亡著觉,警方通過查閱死者的電腦和手機(jī)村生,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饼丘,“玉大人趁桃,你說我怎么就攤上這事∫薷耄” “怎么了卫病?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長典徘。 經(jīng)常有香客問我蟀苛,道長,這世上最難降的妖魔是什么逮诲? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任帜平,我火速辦了婚禮幽告,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罕模。我一直安慰自己评腺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布淑掌。 她就那樣靜靜地躺著蒿讥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抛腕。 梳的紋絲不亂的頭發(fā)上芋绸,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機(jī)與錄音担敌,去河邊找鬼摔敛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛全封,可吹牛的內(nèi)容都是我干的马昙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼刹悴,長吁一口氣:“原來是場噩夢啊……” “哼行楞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起土匀,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤子房,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后就轧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體证杭,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年妒御,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了解愤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乎莉,死狀恐怖琢歇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梦鉴,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布揭保,位于F島的核電站肥橙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秸侣。R本人自食惡果不足惜存筏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一宠互、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧椭坚,春花似錦予跌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至垂涯,卻和暖如春烁焙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耕赘。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工骄蝇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人操骡。 一個(gè)月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓九火,卻偏偏與公主長得像,于是被迫代替她去往敵國和親册招。 傳聞我的和親對象是個(gè)殘疾皇子岔激,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

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