還是要從頭說起:
最近由于項(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)生成的文件如下:
其中 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è)文件:
每個(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()]
}
}
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:
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)贊···