Android App模塊化篇

還是要從頭說起:

最近由于項(xiàng)目進(jìn)行到了一定的階段尤莺,項(xiàng)目中的代碼到達(dá)了一定的體積與當(dāng)量(主要是業(yè)務(wù)越來越多)九串,這個(gè)時(shí)間分層使用MVC,MVVM等來分層已經(jīng)力不從心癣亚,不是說MVC不行而是我認(rèn)為MVC分層是一種粒度相對(duì)于較小的分層丑掺,相對(duì)于模塊化要站在一個(gè)更大更宏觀的角度來進(jìn)行項(xiàng)目架構(gòu)的調(diào)整所以這次一定要從項(xiàng)目全局宏觀的角度出發(fā)。

上一篇《IOS App模塊化篇》已經(jīng)介紹了 模塊化述雾,組件化街州,插件化 的概念這里我們繼續(xù)來學(xué)習(xí)下這三個(gè)概念:

插件化:
插件化開發(fā)是將一個(gè)項(xiàng)目app拆分成多個(gè)模塊,這些模塊包括宿主和插件玻孟。每個(gè)模塊相當(dāng)于一個(gè)apk唆缴,最終發(fā)布的時(shí)候?qū)⑺拗鱝pk和插件apk單獨(dú)打包或者聯(lián)合打包。
作用:每個(gè)組負(fù)責(zé)一個(gè)插件黍翎,彼此之間沒有過多的依賴面徽,可以單獨(dú)調(diào)試打包,有時(shí)發(fā)版其實(shí)就相當(dāng)于發(fā)插件匣掸,最重要的是可以動(dòng)態(tài)下載插件更新插件趟紊。

模塊化:
組件化開發(fā)是將一個(gè)項(xiàng)目app拆分成多個(gè)模塊,模塊化開發(fā)過程中相互依賴或單獨(dú)調(diào)試碰酝,最終發(fā)布的時(shí)候是將這些模塊合并統(tǒng)一成一個(gè)apk霎匈,另外模塊化也是插件化的前提

組件化:是一種細(xì)粒度更小的結(jié)構(gòu)方式多見于項(xiàng)目中一個(gè)組件化控件例如:自定義Button按鈕,這種組件更多是為了在項(xiàng)目中的復(fù)用以及方便開發(fā)管理送爸。

好了铛嘱,概念是比較通俗淺顯易懂的,如果有疑問可以留言討論碱璃,我們繼續(xù)討論Android App的模塊化路上會(huì)遇到的問題弄痹,以及解決的方法饭入,問題還是那幾個(gè)通用的問題:

 1. 多個(gè)模塊的跳轉(zhuǎn)怎么解決 
 2. 模塊化里面的傳值問題怎么解決
 3. 模塊化里面的方法互相怎么調(diào)用
 4. 模塊化里面的響應(yīng)式事件怎么處理

問題雖多但是不得不說在Android模塊化框架的選型上面確沒有那么多煩惱嵌器,因?yàn)榘⒗镩_源的Arouter框架確實(shí)是受眾不少,而且我們這4個(gè)問題都可以解決谐丢,確切的說是解決了前3個(gè)最后一個(gè)可以使用EventBus來實(shí)現(xiàn)爽航。(插一句我找了一番沒有發(fā)現(xiàn)IOS里面有EventBus這種知名度高而且好用的注冊監(jiān)聽框架

那么我們來看看Arouter的功能介紹:

支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)蚓让,并自動(dòng)注入?yún)?shù)到目標(biāo)頁面中
支持多模塊工程使用
支持添加多個(gè)攔截器,自定義攔截順序
支持依賴注入讥珍,可單獨(dú)作為依賴注入框架使用
支持InstantRun
支持MultiDex(Google方案)
映射關(guān)系按組分類历极、多級(jí)管理,按需初始化
支持用戶指定全局降級(jí)與局部降級(jí)策略
頁面衷佃、攔截器趟卸、服務(wù)等組件均自動(dòng)注冊到框架
支持多種方式配置轉(zhuǎn)場動(dòng)畫
支持獲取Fragment
完全支持Kotlin以及混編
支持第三方 App 加固(使用 arouter-register 實(shí)現(xiàn)自動(dòng)注冊)
支持生成路由文檔
提供 IDE 插件便捷的關(guān)聯(lián)路徑和目標(biāo)類

功能非常多,那么我們來看一看基本使用氏义,看完使用我們再說實(shí)現(xiàn)原理

@Route(path = "/home/main")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
    }
}

注意下第一句話 @Route(path = "/home/main") 這個(gè)注解意思就是指定了這個(gè) MainActivity 的URL標(biāo)識(shí)锄列,如果要跳轉(zhuǎn)的話一句話搞定:

ARouter.getInstance().build("/home/main").navigation();

是不是很簡單,那跳轉(zhuǎn)的參數(shù)怎么傳呢惯悠,也很簡單:

ARouter.getInstance().build("/chat/main").withString("key", "888").navigation();

這樣傳參那怎么取呢邻邮,取的模塊也很簡單也是通過注解來承接參數(shù):

@Route(path = "/chat/main")
public class MainActivity extends AppCompatActivity {

    private TextView text;

    @Autowired
    public String key;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        ARouter.getInstance().inject(this);
        Toast.makeText(this, "收到傳送過來的數(shù)據(jù):" + key, Toast.LENGTH_LONG).show();
        text = findViewById(R.id.text);
        text.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });
    }
}

第1,2問題得到解決了克婶,那么第3個(gè)問題怎么解決呢筒严,Arouter里面提供一種叫做IProvider的對(duì)象你去繼承這個(gè)對(duì)象,

public interface HomeExportService extends IProvider {
    String sayHello(String s);
    BaseHomeModel getHomeModelData();
}

實(shí)際的業(yè)務(wù)模塊實(shí)現(xiàn)了這個(gè)接口情萤,首先通過注解來標(biāo)識(shí)這個(gè)服務(wù)類:

@Route(path = "/home/HomeService",name = "測試服務(wù)"):

@Route(path = "/home/HomeService",name = "測試服務(wù)")
public class HomeService implements HomeExportService {
    private String name;
    @Override
    public String sayHello(String s) {
        return "HomeService say hello to" + s;
    }

    @Override
    public void init(Context context) {
       initData();
    }

    private void initData() {
        name="haozi";
    }

    public BaseHomeModel getHomeModelData(){

        BaseHomeModel hm = new HomeModel("home",100);
        return  hm;
    }
}

調(diào)用的方式為首先用注解來找到這個(gè)服務(wù)類鸭蛙,然后進(jìn)行調(diào)用

@Autowired(name = "/home/HomeService")

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView chat_tv;
    private TextView contract_tv;
    private TextView find_tv;
    private TextView mine_tv;
    private TextView say_hello_tv;

    @Autowired(name = "/home/HomeService")
    public HomeExportService baseService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ARouter.getInstance().inject(this);
        initView();
        BaseHomeModel bm =  baseService.getHomeModelData();
    }
}

這里面其實(shí)也有分層的概念在里面服務(wù)模塊與調(diào)用模塊分開互不影響,然后中間有一個(gè)中間層暴露服務(wù)的申明筋岛,然后服務(wù)模塊與調(diào)用模塊分別引用了這個(gè)中間層模塊

OK规惰,目前到此為止Arouter+EventBus能解決我們4個(gè)問題,看起來很美好泉蝌,那么它是怎么做到的呢歇万?···

原理:

其實(shí)Arouter的原理的文章網(wǎng)絡(luò)上還是比較多的,但是我用一種比較通俗易懂的方式來給大家做一個(gè)講解勋陪,主要分兩個(gè)部分:

1.首先APT是什么贪磺?

APT:APT(Annotation Processing Tool)是一種處理注釋的工具,它對(duì)源代碼文件進(jìn)行檢測找出其中的Annotation,根據(jù)注解自動(dòng)生成代碼诅愚。 Annotation處理器在處理Annotation時(shí)可以根據(jù)源文件中的Annotation生成額外的源文件和其它的文件(文件具體內(nèi)容由Annotation處理器的編寫者決定),APT還會(huì)編譯生成的源文件和原來的源文件寒锚,將它們一起生成class文件。

簡單的來講通過APT技術(shù)违孝,即注解處理器在編譯時(shí)掃描并處理注解刹前,注解處理器
可以在編譯時(shí)生成額外的.java文件,在程序運(yùn)行的時(shí)候調(diào)用相關(guān)方法雌桑,可以達(dá)到減少重復(fù)代碼的效果喇喉。它的好處:提高開發(fā)效率,使得項(xiàng)目更容易維護(hù)和擴(kuò)展校坑,同時(shí)幾乎不影響性能拣技。

2.Arouter基于APT是怎么做的千诬?

那么ARouter背后是怎么樣實(shí)現(xiàn)跳轉(zhuǎn)的呢?我們在代碼里加入的@Route注解膏斤,會(huì)在編譯時(shí)期通過apt生成一些存儲(chǔ)path和activity.class映射關(guān)系的類文件徐绑,例如app模塊編譯動(dòng)態(tài)生成的文件如下:

image.png

其中 Aroute-Root-app 文件內(nèi)容是看你這個(gè)模塊定義的URL 例如有兩個(gè):/test/main/aaa/main 那么 Aroute-Root-app 里面就有兩條數(shù)據(jù)如下:

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("aaa", ARouter$$Group$$aaa.class);
    routes.put("test", ARouter$$Group$$test.class);
  }
}

分別保存的是他們 Group 類的名字莫辨,Group 就會(huì)有兩個(gè)文件:

image.png

每個(gè) Group 里面的內(nèi)容保存你想跳轉(zhuǎn)的Activity的類:

atlas.put("/aaa/main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/aaa/main", "aaa", null, -1, -2147483648));
atlas.put("/test/target", RouteMeta.build(RouteType.ACTIVITY, TargetActivity.class, "/test/target", "test", new java.util.HashMap<String, Integer>(){{put("key3", 8); }}, -1, -2147483648));

如果你的模塊兩個(gè)URL是:
/test/main傲茄,/test/main 那么你的 Aroute-Root-app 文件里面的內(nèi)容只有一條數(shù)據(jù)

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
  }
}

然后你的 Group 里面會(huì)有兩條數(shù)據(jù),保存你想跳轉(zhuǎn)的Activity的類的名字:

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/test/main", "test", null, -1, -2147483648));
    atlas.put("/test/target", RouteMeta.build(RouteType.ACTIVITY, TargetActivity.class, "/test/target", "test", new java.util.HashMap<String, Integer>(){{put("key3", 8); }}, -1, -2147483648));
  }
}

如果你沒有特定指定組的話沮榜,它其實(shí)是用URL的前 /test/ 部分來作為一個(gè) Group 的烫幕,如果你這個(gè)模塊沒有使用 IProvider 提供對(duì)外模塊的服務(wù)則 Atoute-Provider-app 文件夾里面的內(nèi)容為空

然后app進(jìn)程啟動(dòng)的時(shí)候會(huì)加載這些類文件,把保存這些映射關(guān)系的數(shù)據(jù)讀到內(nèi)存里(保存在map里)敞映,然后在進(jìn)行路由跳轉(zhuǎn)的時(shí)候较曼,通過build()方法傳入要到達(dá)頁面的路由地址,ARouter會(huì)通過它自己存儲(chǔ)的路由表找到路由地址對(duì)應(yīng)的Activity.class(activity.class = map.get(path))振愿,然后new Intent(context, activity.Class)捷犹,當(dāng)調(diào)用ARouter的withString()方法它的內(nèi)部會(huì)調(diào)用intent.putExtra(String name, String value),調(diào)用navigation()方法冕末,它的內(nèi)部會(huì)調(diào)用startActivity(intent)進(jìn)行跳轉(zhuǎn)萍歉,這樣便可以實(shí)現(xiàn)兩個(gè)相互沒有依賴的module順利的啟動(dòng)對(duì)方的Activity了。

gradle里面這一行說明了使用了注解解釋器

annotationProcessor com.alibaba:arouter-compiler:1.1.4

這一行說明了自定義的注解以及一些注解相關(guān)的工具類服務(wù)類在這里面

implementation com.alibaba:arouter-api:1.3.1

這幾行是為了給注解處理器提供模塊名字档桃,注解處理器會(huì)根據(jù)模塊名字來動(dòng)態(tài)生成對(duì)應(yīng)的JAVA文件名例如下圖:結(jié)尾home就是模塊名字

javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
image.png

PS:順便插一句很多博客沒有講清楚下面這句話的作用就在代碼加上了枪孩,其實(shí)這句話的意思是如果你的類里面需要用到@Autowired 來進(jìn)行注入?yún)?shù)以及服務(wù)的話就需要手動(dòng)調(diào)用這句話,沒有的話則不需要也是可以的藻肄。

ARouter.getInstance().inject(this);

其實(shí)網(wǎng)絡(luò)上有很多已經(jīng)手動(dòng)實(shí)現(xiàn)了APT的功能一可以給大家推薦幾個(gè)鏈接:
http://www.reibang.com/p/b5be6b896a1a
http://www.reibang.com/p/857aea5b54a8

已經(jīng)寫得比較好了蔑舞,有疑問可以給我留言大家一起交流

我已經(jīng)在一個(gè)Demo的基礎(chǔ)上做了一層小小的改進(jìn),改進(jìn)主要是HomeExportService部分嘹屯,Service部分是在Commlib模塊里面用作暴露給外面調(diào)用的攻询,如果需要返回模型對(duì)象的話應(yīng)該怎么處理呢?我在Commlib公共模塊里面定義了一個(gè)model文件夾和service文件夾平級(jí)州弟,里面包含了service服務(wù)要返回給調(diào)用模塊的model例如BaseHomeModel钧栖,然后真正實(shí)現(xiàn)服務(wù)的模塊里面的model再繼承這個(gè)model:

image.png
public class BaseHomeModel {

    public String add;
    public  int count;

    public BaseHomeModel(String add, int count) {
        this.add = add;
        this.count = count;
    }

    public String getAdd() {
        return add;
    }

    public void setAdd(String add) {
        this.add = add;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

public class HomeModel extends BaseHomeModel{

    private String add;
    private  int count;

    public HomeModel(String add, int count) {
        super(add, count);
        this.add = add;
        this.count = count;
    }

    public String getAdd() {
        return add;
    }

    public int getCount() {
        return count;
    }

    public void setAdd(String add) {
        this.add = add;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

然后需要調(diào)用的模塊只需要依賴Commlib模塊,首先先用注解獲得service的注入

@Autowired(name = "/home/HomeService")
public HomeExportService baseService;

這里面會(huì)注入真正提供的service服務(wù)對(duì)象在里面婆翔,上面介紹了這個(gè)對(duì)象使用了 @Route 注解早在編譯期的時(shí)候已經(jīng)加入到了map里面了

@Route(path = "/home/HomeService",name = "測試服務(wù)")
public class HomeService implements HomeExportService {
    private String name;
    @Override
    public String sayHello(String s) {
        return "HomeService say hello to" + s;
    }

    @Override
    public void init(Context context) {
       initData();
    }

    private void initData() {
        name="Hao zi";
    }

    public BaseHomeModel getHomeModelData(){

        BaseHomeModel hm = new HomeModel("home",100);
        return  hm;
    }
}

然后進(jìn)行調(diào)用即可:

BaseHomeModel bm =  baseService.getHomeModelData();
Toast.makeText(this, bm.getAdd() + "|" + bm.getCount(), Toast.LENGTH_SHORT).show();

好了拯杠,我們Android App模塊化篇講解得差不多了,最后我會(huì)把Demo上傳給大家啃奴,如果大家喜歡請(qǐng)留言或者點(diǎn)贊···

AndroidRouterDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末潭陪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子纺腊,更是在濱河造成了極大的恐慌畔咧,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揖膜,死亡現(xiàn)場離奇詭異誓沸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)壹粟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門拜隧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人趁仙,你說我怎么就攤上這事洪添。” “怎么了雀费?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵干奢,是天一觀的道長。 經(jīng)常有香客問我盏袄,道長忿峻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任辕羽,我火速辦了婚禮逛尚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刁愿。我一直安慰自己绰寞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布铣口。 她就那樣靜靜地躺著滤钱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脑题。 梳的紋絲不亂的頭發(fā)上菩暗,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音旭蠕,去河邊找鬼停团。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掏熬,可吹牛的內(nèi)容都是我干的佑稠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼旗芬,長吁一口氣:“原來是場噩夢啊……” “哼舌胶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疮丛,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤幔嫂,失蹤者是張志新(化名)和其女友劉穎辆它,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體履恩,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锰茉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了切心。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片飒筑。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绽昏,靈堂內(nèi)的尸體忽然破棺而出协屡,到底是詐尸還是另有隱情,我是刑警寧澤全谤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布肤晓,位于F島的核電站,受9級(jí)特大地震影響认然,放射性物質(zhì)發(fā)生泄漏材原。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一季眷、第九天 我趴在偏房一處隱蔽的房頂上張望余蟹。 院中可真熱鬧,春花似錦子刮、人聲如沸威酒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葵孤。三九已至,卻和暖如春橱赠,著一層夾襖步出監(jiān)牢的瞬間尤仍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工狭姨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宰啦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓饼拍,卻偏偏與公主長得像赡模,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子师抄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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