1. 程式人生 > >Android元件化方案實踐與思考

Android元件化方案實踐與思考

Demo地址:https://github.com/751496032/ComponentDemo

效果圖:
在這裡插入圖片描述

背景

Android從誕生到現在,不知不覺的走過十多個年頭了,也產生了很多App,隨著專案的推進不斷的迭代,而App也從最初的單一功能演變成多工功能,各種業務的錯綜複雜,開發人員也不斷的增加,如果架構不做調整優化,會給開發帶來很大的困難:

  • 各種業務程式碼耦合性及高,程式碼臃腫會越來越高,不利於團隊間協同開發,維護成本也高;
  • 降低開發效率,工程的編譯執行時間及長,在單一工程下,每修改一小處都要執行整個專案,導致非常耗時。
  • ……

基於程式碼耦合性問題,App的設計架構也不斷的演變,從最初的MVC,到現在主流的MVP、MVVM,這些模式也確實起到程式碼解耦的效果,但還是很大侷限性的;各業務間的耦合、執行效率問題都還是存在的;於是元件化思想就誕生了,元件化有如下優勢:

  • 業務間程式碼互不干擾,解耦性好,程式碼複用性也高;
  • 各個元件能單獨生成apk,可以單獨除錯,降低了編譯執行時長。

元件化思想

元件化就是把單一工程的app分成多個Module,每個Module就相當於一個元件,而這些元件是不需要相互依賴的,可根據開發需求,自由將各個元件進行ApplicationLibrary模式切換進行除錯開發。

元件化基礎架構圖
上面是元件化基礎結構圖,從上向下分為三部分,分別是app空殼、功能元件(業務元件)、基礎元件;

  • App空殼只有一個元件就是App元件,需要依賴於各個業務元件,最終上線的就是App統籌所有的業務元件打包生成的;
  • 功能元件又稱之業務元件,各個元件間並沒有依賴關係,除了Login元件外,把Login元件單獨分開的原因是,我認為基本上所有的業務元件都需要登入行為才可以操作,不排除少數業務元件是不需要的,於是乾脆把所有的業務元件全部依賴於Login元件,在這裡Login元件其實是一個共享元件;
  • 基礎元件,這個很好理解就是我們封裝的基礎庫,比如網路、路由、推送、圖片等等

元件化需解決的問題

  • 模式切換,如何使每個Module在ApplicationLibrary間自由切換;
  • 依賴關係,如何處理每個Module間、工具類庫的依賴關係,這個沒有唯一模型,可根據專案需求也定,但一點可以肯定的是,同一層次的元件模組不能存在相互依賴的關係,不然就失去了元件化的意義了;
  • 資源衝突,如何處理App空殼中所依賴的Module間資源重名衝突;
  • 元件通訊,如何處理業務元件間通訊問題。
  • ……

上面這幾個問題是元件化實現過程中的主要問題,解決了上述問題,元件化方案實施基本沒有多大的問題,其他的一些問題可根據自身需求而定。

實現步驟

1、在專案根目錄下的gradle.properties配置全域性引數,方便管理各個Module的常用全域性引數,比如版本號、常量等等

isModuleRun=false
compile_sdk_version=26
min_sdk_version=17
target_sdk_version=26
version_code=1
version_name=1.0

constraint_layout_version=1.1.3
support_version=26.1.0
leakcanary_version=1.6.1
arouter_version=1.3.1
arouter_annotation_version=1.2.0
eventbus_version=3.1.1

……

2、模式切換

在專案根目錄下的gradle.properties設定一個boolean的變數isModuleRun,這個變數的作用就是控制業務元件ApplicationLibrary模式切換,當isModuleRun=true,元件處於Application可單獨編譯執行,反之則為Library是一個依賴庫,在模式切換過程同時還需處理每個Module的AndroidMainfest檔案的衝突,如下:
在這裡插入圖片描述

在每個業務Module下的build.gradle下編寫切換判斷的程式碼處理模式切換
在這裡插入圖片描述
isModuleRun的值不同,Module的AndroidMainfest檔案內容也會有所不同,當Module處於Application下,此時是獨立應用,需要配置applicationId,以及應用的啟動頁設定,而在Library下則不需要這些,因為我們針對不同模式下引用不同AndroidMainfest

首頁在Module的main目錄下建立一個module_run目錄單獨存放Application所需的AndroidMainfest檔案,接著在Module下的build.gradle引入:
在這裡插入圖片描述

Lib下的AndroidMainfest檔案內容
在這裡插入圖片描述

Application下的AndroidMainfest檔案內容
在這裡插入圖片描述

到這裡基本解決了模式切換的問題

3、資源衝突

從App空殼到基礎元件,中間依賴很多其他元件,難免會有資源衝突的問題,在這情況下,建議在定義一個資源命名規範,大家統一遵守這個規範,能很好的避免資源衝突的問題,比如可以以Module名稱作為字首進行規範:
在這裡插入圖片描述

4、元件通訊

元件間通訊我們使用開源元件通訊框架,比如阿里的ARouter,能很好的處理各元件間的跳轉,並且同層次的元件間不會任何的依賴關係,實現瞭解耦的效果。使用如下:

在各元件下的build.gradle新增依賴和配置

android {
    defaultConfig {
        ...
        //注意:這裡每個業務元件都需要配置
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
   	//這裡在Base基礎元件新增依賴即可,其他元件無需新增
    api "com.alibaba:arouter-api:${arouter_version}"
    //註解依賴需要在各個元件中新增依賴
    annotationProcessor "com.alibaba:arouter-compiler:${arouter_annotation_version}"
    ...
}

在BaseAppliction下初始化

    private void initARouter() {
        if (BuildConfig.DEBUG) {   // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
            ARouter.openLog();     // 列印日誌
            ARouter.openDebug();   // 開啟除錯模式(如果在InstantRun模式下執行,
            必須開啟除錯模式!線上版本需要關閉,否則有安全風險)
        }
        ARouter.init(this); // 儘可能早,推薦在Application中初始化
    }

簡單的使用,比如獲取Fragment例項、啟動Activity、攔截跳轉頁面

/**
 * 路由管理類
 * 命名規則:/模組名/特殊描述/目標頁面名稱(特殊描述可選)
 * 需要登入的頁面操作,帶login_after欄位
 */

public final class ARouterManager {

    public static final String LOGIN_AFTER="login_after";

    public static final String HomeFragment = "/home/HomeFragment";

    public static final String CartFragment="/cart/CartFragment";

    public static final String MeFragment="/me/CartFragment";

    public static final String LoginActivity="/login/LoginActivity";

    public static final String GoodsDetailActivity="/home/GoodsDetailActivity";

    public static final String ShareActivity="/login/login_after/ShareActivity";

}
-------------------------------------------------------------------------------------
//Fragment路由路徑定義
@Route(path = ARouterManager.HomeFragment) //定義路由路徑
public class HomeFragment extends BaseFragment implements View.OnClickListener {

}
// Fragment例項獲取
Fragment fragmet = (Fragment) ARouter.getInstance().build(ARouterManager.HomeFragment).navigation()

-------------------------------------------------------------------------------------
//Activty
@Route(path = ARouterManager.GoodsDetailActivity)
@SuppressWarnings("all")
public class GoodsDetailActivity extends BaseActivity {

}
//啟動
  ARouter.getInstance().build(ARouterManager.GoodsDetailActivity).navigation();

頁面跳轉攔截器,比如某些頁面操作必須登入,我們可以先獲取當前是否有登入,然後根據頁面路由路徑進行判斷攔截頁面跳轉

/**
 * 頁面跳轉攔截器
 * 應用場景:如某些頁面需要登入才可操作,可通過攔截器來統一處理跳轉頁面
 */
@Interceptor(priority = 7)
public class ARouterInterceptor implements IInterceptor {
    Context mContext;

    /**
     * The operation of this interceptor.
     *
     * @param postcard meta
     * @param callback cb
     */
    @Override
    public void process(final Postcard postcard, final InterceptorCallback callback) {
        boolean isLogin = SpUtils.getBoolean(mContext, SpUtils.LOGIN_KEY);
        String path = postcard.getPath();
        if (!isLogin&&path.contains(ARouterManager.LOGIN_AFTER)){
            //未登入
            ARouter.getInstance().build(ARouterManager.LoginActivity).navigation();
            callback.onInterrupt(null);
        }else {
            callback.onContinue(postcard);
        }
    }

    /**
     * Do your init work in this method, it well be call when processor has been load.
     * 在路由初始化時會載入攔截器
     * @param context ctx
     */
    @Override
    public void init(Context context) {
        mContext = context;
        Log.e("testService", ARouterInterceptor.class.getName() + " has init.");
    }
}

上面是ARouter的一些簡單用法,詳細可以檢視官方文件。

總結

元件化並沒有一個放之四海皆準的通用方案,在我認為,只要實現各個業務模組、基礎模組間解耦就是一個好方案,最起碼相對之前單一工程來說,已經改善了很多了,效率肯定會有提升,只有根據自己專案實際情況,進行不斷改造找到適合自己專案的設計方案。如果在現有的專案中進行元件化拆分,建議先把基礎元件庫進行剝離,緊接著再抽離一些共享資料元件(比如登入、分享元件等等),最後才對核心業務元件下刀拆分,在拆分的過程中千萬別指望一口氣全部拆分完,否則一不小心就會出現專案滿堂紅的情況,要做到邊拆分邊備份,避免程式碼丟失的危險。