最全的Android組件化分析

1.什麼是模塊化、組件化和插件化

隨著業務的積累,產品的迭代,我們寫的工程會越來越大,也越來越臃腫,更加難以維護,那有沒有一種方法,能夠使得每個人專門負責自己的業務模塊,使用的時候把每個人做的模塊直接拼裝組合起來就行,這樣代碼也更加靈活,相互之間的耦合性也更低,重用性也能夠更大。那麼模塊化的概念就來了。

簡單來說, 模塊化就是將一個程序按照其功能做拆分,分成相互獨立的模塊,以便於每個模塊只包含與其功能相關的內容。模塊我們相對熟悉,比如登錄功能可以是一個模塊, 搜索功能可以是一個模塊, 汽車的發送機也可是一個模塊。

當然從個人的理解上,模塊化只是一種思想,就是大化小,分開治理,在實際項目中如何具體實施,目前有兩種方案,一個是組件化,一個是插件化

最全的Android組件化分析

在網上找到了一張很形象的圖

  • 組件化方案就是:由若干獨立的子模塊,組合成一個整體,降低模塊間的耦合,這些子模塊在補足一定的條件下,都可獨立運行。主模塊也不會因為缺少任意子模塊而無法運行。組件之間可以靈活的組建
  • 類似於積木,拼裝組合,易維護
  • 插件化方案就是:一個程序的輔助或者擴展功能模塊,對程序來說插件可有可無,但它能給予程序一定的額外功能。
  • 打個比方,就像現在的應用程序,更多的需要依賴一些第三方庫,比如地圖sdk、分享sdK、支付sdk等等,導致安裝包變得越來越大,單是依賴這些sdk,安裝包可能就會額外的增加10-20M的大小;
  • 當需要新增功能時,不得不重新更新整個安裝包。再熟讀一下上面的定義,就知道它的用途和作用了,那就是有些附加功能,需要時,可靈活的添加,動態的加載。插件化主要是解決的是減少應用程序大小、免安裝擴展功能,當需要使用到相應的功能時再去加載相應的模塊

2.和插件化的區別

區別根據他們使用的用途,就很好理解了:組件化在運行時不具備動態添加或修改組件的功能,但是插件化是可以的

3.組件化的實踐方案

說起組件化的實踐方案,只有一首小詩形容, 走遍了各種論壇,看遍了地老天荒,原來最適合的方案啊,就在身旁

總而言之一句話:各種方案都有,也不缺乏很多寫的不錯的,但是秉持著商用開發為主,接下來介紹一個最合適的,那就是阿里巴巴出的一套ARouter,它簡單易用、它支持多模塊項目、它定製性較強、它支持攔截邏輯等諸多優點,接下來會寫阿里這套框架的使用方便日後開發。如果有興趣的小夥伴,可以等我下一篇博客,介紹它的實踐原理。

最全的Android組件化分析

4.開始擼碼

1.首先,看下工程

最全的Android組件化分析

就是一個電商,有3個組件,一個是首頁,一個是購物車,一個是個人中心,3個獨立的模塊

2.做些準備

因為每一個模塊都是要能夠單獨調試的,所以我們先定義每個模塊的開關,設置這個模塊是否要進行單獨調試運行

  1. 在工程目錄中的build.gradle 中,定義3個變量
buildscript { 
ext.kotlin_version = '1.3.31'
ext {
isRunHome = true // true是Home模塊可單獨運行
isRunPersonalcenter = true
isRunShopingcar = true
}
  1. 在子模塊中,比如Home模塊,設置build.gradle
if(isRunHome.toBoolean()){ // 1.根據之前設定的isRunHome,判斷是否需要獨立運行
apply plugin: 'com.android.application'
}else {
apply plugin: 'com.android.library'
}
android {
android {
compileSdkVersion 29
buildToolsVersion "29.0.0"
defaultConfig {
if(isRunHome.toBoolean()){ // 2.這裡也設置一下,可運行的話,添加applicationId
applicationId "com.bj.home"
}
  1. 在主模塊(app模塊)中設置它的build.gradle
dependencies {
if(!isRunHome.toBoolean()){ // 1.如果要獨立運行,那麼主工程不加載它
implementation project(path: ':home')
}

implementation project(path: ':personalcenter')
implementation project(path: ':shoppingcar')

編譯一下就是這樣

最全的Android組件化分析

4.當然還差一步,設置AndroidManifest.xml文件,因為一般來說,一個APP只有一個啟動頁,在組件單獨調試時也需要一個啟動頁,所以我們需要設置兩個文件。就這樣

最全的Android組件化分析

AndroidManifest文件和ApplicationId 一樣都是可以在 build.gradle 文件中進行配置的,所以我們同樣通過動態配置組件工程類型時定義的 boolean變量的值來動態修改。需要我們修改子模塊(如home)的build.gradle文件。

android {
...

sourceSets {
main {
// 1.單獨調試與集成調試時使用不同的 AndroidManifest.xml 文件
// 我們還可以根據不同工程配置不同的 Java 源代碼、不同的 resource 資源文件等的
if(isRunHome.toBoolean()) {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
} else{
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}

大功告成,使用時只需要修改根目錄build.gradle文件中的那3個變量,就可以一鍵開啟該模塊的單獨運行模式了,親測有效,好了,我們已經完成了,模塊獨立化了,子模塊可單獨運行了,但是,怎麼通訊,傳遞數據呀?組件與組件之間都是不可以直接使用類的相互引用來進行數據傳遞的!

最全的Android組件化分析

3.集成阿里的路由框架ARouter

解決辦法就是集成集成阿里的路由框架ARouter,一個用於幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通信、解耦 來我們集成一下

3.1 添加依賴

1.在各個模塊中添加了對 ARouter 的依賴,當然自己新建一個base模塊,依賴添加到base裡,其他模塊引用它也可以。

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

dependencies {
compile 'com.alibaba:arouter-api:1.2.1.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.2.1'
...
}

好了,配置完成

3.2 初始化SDK

我們在自定義的MyApplication中,初始化它

 @Override
public void onCreate() {
super.onCreate();
// 這兩行必須寫在init之前,否則這些配置在init過程中將無效
if(isDebug()) {
// 打印日誌
ARouter.openLog();
// 開啟調試模式(如果在InstantRun模式下運行,必須開啟調試模式!線上版本需要關閉,否則有安全風險)
ARouter.openDebug();
}
// 初始化ARouter
ARouter.init(this);
}
private boolean isDebug() {
return BuildConfig.DEBUG;
}

3.3 Activity跳轉

最全的Android組件化分析

1.在目標Activity添加註解 Route (home : HomeAty)

/**
* 首頁模塊
*
* 其中 path 是跳轉的路徑,這裡的路徑需要注意的是至少需要有兩級,/xx/xx
* */
@Route(path = "/home/HomeAty")
public class HomeAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_home);
}
}

2 頁面跳轉(app : MainActivity)

 @Override
public void onClick(View view) {
switch (view.getId()){
// 跳轉Activity頁面
case R.id.btn_go_home:
ARouter.getInstance().build("/home/HomeAty").navigation();
break;
}
}

3.4 跳轉ForResult

最全的Android組件化分析

  1. 頁面跳轉及返回(app : MainActivity)
 @Override
public void onClick(View view) {
switch (view.getId()){
...
// 跳轉Activity頁面, 並且返回數據
case R.id.btn_go_aty_forresult:
ARouter.getInstance().build("/home/HomeResultAty").navigation(this, 897);
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 897 && resultCode == 999){
String msg = data.getStringExtra("msg");
tv_msg.setText(msg);
}
}
  1. 目標Activity(home : HomeResultAty)
@Route(path = "/home/HomeResultAty")
public class HomeResultAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_home_result);
findViewById(R.id.btn_goback).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent in = new Intent();
in.putExtra("msg", "從home模塊返回的數據");
setResult(999, in);
finish();
}
});
}
}

3.5 獲取Fragment

最全的Android組件化分析

  1. 獲取fragment(app : MainActivity)
 Fragment mFragment = (Fragment) ARouter.getInstance().build("/home/HomeFragment").navigation();
getSupportFragmentManager().beginTransaction().replace(R.id.fl, mFragment).commit();

2.當然fragment也要加註解(home : HomeFrag)

@Route(path = "/home/HomeFragment")
public class HomeFrag extends Fragment {...}

3.6 攜帶參數的應用內跳轉

最全的Android組件化分析

1. 主工程(app : MainActivity)

 // 攜參數跳轉
case R.id.btn_go_home_byArgs:
ARouter.getInstance().build("/home/arg")
.withString("msg", "5")
.withDouble("msg2", 6.0)
.navigation();
break;

2.目標Activity(home: HomeByArgAty)

@Route(path = "/home/arg")
public class HomeByArgAty extends Activity {
@Autowired(name = "msg")
String arg1;

@Autowired
String arg2;
private TextView tv_msg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_home_arg);
// 如果使用Autowired註解,需要加入底下的代碼
// 當然也可以用 getIntent().getStringExtra("")
ARouter.getInstance().inject(this);
tv_msg = findViewById(R.id.tv_msg);
tv_msg.setText("從主工程傳遞過來的參數:"+arg1);
}
}
最全的Android組件化分析

---------------------------------進階用法------------------------------

3.7 攔截器

ARouter也添加了攔截器模式,攔截器有很多用處,比如路由到目標頁面時,檢查用戶是否登錄,檢查用戶權限是否滿足,如果不滿足,則路由到相應的登錄界面或者相應的路由界面。ARouter的攔截器比較奇葩,只需要實現IInterceptor接口,並使用@Interceptor註解即可,並不需要註冊就能使用。當然這也有了它的壞處,就是每一次路由之後,都會經過攔截器進行攔截,顯然這樣程序的運行效率就會降低。Interceptor可以定義多個,比如定義登錄檢查攔截器,權限檢查攔截器等等,攔截器的優先級使用priority定義,優先級越大,越先執行。攔截器內部使用callback.onContiune()/callback.onInterrupt(),前者表示攔截器任務完成,繼續路由;後者表示終止路由。例子:

最全的Android組件化分析

1.實現IInterceptor接口,自定義攔截器,檢測所有跳轉中,只要uri為空就攔截,也可以在這請求中再加內容

@Interceptor(priority = 4)
public class LoginInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
String uri = postcard.getExtras().getString("uri");
if(TextUtils.isEmpty(uri)){
Log.i("lybj", "uri為空,中斷路由");
callback.onInterrupt(null);
}else {
Log.i("lybj", "攔截器執行,uri不為空,繼續執行吧");
postcard.withString("msg", "可以隨意加內容");
callback.onContinue(postcard);
}
}
@Override
public void init(Context context) {
}
}
  1. 一個網頁正常的跳轉
 // 攔截器測試
case R.id.btn_test_interceptor:
ARouter.getInstance().build("/home/web")
.withString("uri", "file:///android_asset/schame-test.html")
.navigation();
break;

3.目標界面

@Route(path = "/home/web")
public class WebAty extends Activity {
private WebView wv_web;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_web);
Log.i("lybj", getIntent().getStringExtra("msg"));
String uri = getIntent().getStringExtra("uri");
wv_web = findViewById(R.id.wv_web);
wv_web.loadUrl(uri);
}
}

最全的Android組件化分析

4.ARouter踩坑

1. 異常:ARouter::Compiler >>> No module name, for more information, look at gradle log.

這個很坑,翻了翻文檔,說是要在所引用的所有的model的build.gradle裡面都要加上下面的代碼

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

或者

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

AROUTER_MODULE_NAME和moduleName根據不同的版本,選擇不同的名字,上面的代碼要保證所有模塊都要添加,為的是做區分

2.資源名字相同

作者就做了個蠢事,兩個model的layout名字一樣,主工程加載的時候,總是出問題,所以儘可能的保證每個model的資源名加前綴。

鏈接:https://juejin.im/post/5d8732b8f265da03eb1406b6


分享到:


相關文章: