Activity 組件化分發(fā)結(jié)構(gòu)

背景

在 Android 應(yīng)用開發(fā)中驶拱,我們經(jīng)常能看到下面的頁面:

簡(jiǎn)書
人人視頻

這些頁面有以下幾個(gè)共同特征:

1.一個(gè) Activity 中包含多個(gè)子業(yè)務(wù)(以下稱為子模塊)霜浴。如上面簡(jiǎn)書 App 消息頁,包含了消息蓝纲、簡(jiǎn)信倆個(gè)模塊阴孟;
2.多個(gè)子模塊之間不存在依賴關(guān)系,相互獨(dú)立即可運(yùn)行税迷;
3.子模塊的生命周期與 Activity 相關(guān)聯(lián)永丝,且不能超過 Activity 的生命周期。

本文探討的就是如何將上述類型的 Activity 代碼組件化箭养、模塊化慕嚷,使其易于閱讀、開發(fā)毕泌、維護(hù)喝检。

探索

實(shí)際開發(fā)中,如果將多個(gè)獨(dú)立的功能代碼全部嵌入同一個(gè) Activity 類中撼泛,該 Activity 必定會(huì)越來越復(fù)雜冗余蛇耀,維護(hù)性、可讀性也會(huì)越來越差坎弯。

于是為了解決這個(gè)問題,我們可以將一個(gè)個(gè)獨(dú)立的功能抽成一個(gè)個(gè)子模塊,在子模塊中完成各自的功能抠忘;由于子模塊之間有其相同特性:比如都需要 context 上下文撩炊,都跟隨 Activity 生命周期。于是我們將這些相同特性抽出崎脉,創(chuàng)建模塊抽象類拧咳;一個(gè) Activity 的子模塊數(shù)量是不能保證的,將多個(gè)模塊交給 Activity 管理顯然不合適囚灼,為了項(xiàng)目擴(kuò)展性和可復(fù)用性骆膝,我們需要一個(gè)模塊管理類,來有序的創(chuàng)建灶体、初始化阅签、向子模塊分發(fā)生命周期等等。

這時(shí)我們心中大概有如下的組織結(jié)構(gòu):

初步結(jié)構(gòu)

下面就以簡(jiǎn)書 App 消息頁面為例蝎抽,編寫組件化代碼政钟。

編碼

在簡(jiǎn)書 App 消息頁面,我們將會(huì)按如下方式重構(gòu)樟结,其中除實(shí)體 module 與業(yè)務(wù)相關(guān)聯(lián)外养交,其它都是無關(guān) Activity 可復(fù)用的。建議看完后面代碼再回顧下面結(jié)構(gòu)瓢宦。

結(jié)構(gòu)分析

對(duì)于子模塊碎连,有至少三個(gè)依賴需要外部注入:

1.Activity:上下文對(duì)象;
2.ViewGroup:布局對(duì)象驮履,決定了子模塊在哪里布局鱼辙;
3.SaveInstanceState:保存狀態(tài)的對(duì)象。

所以首先創(chuàng)建 Bean 類:ModuleContext 來表示一個(gè)模塊需要的依賴參數(shù)合集疲吸。

public class ModuleContext {
    private Activity context; //上下文對(duì)象
    private Bundle saveInstance; //保存狀態(tài)的對(duì)象
    private SparseArrayCompat<ViewGroup> viewGroups = new SparseArrayCompat<>();
    
    public Activity getContext() {
        return context;
    }

    public void setContext(Activity context) {
        this.context = context;
    }

    public Bundle getSaveInstance() {
        return saveInstance;
    }

    public void setSaveInstance(Bundle saveInstance) {
        this.saveInstance = saveInstance;
    }

    public SparseArrayCompat<ViewGroup> getViewGroups() {
        return viewGroups;
    }

    public void setViewGroups(SparseArrayCompat<ViewGroup> viewGroups) {
        this.viewGroups = viewGroups;
    }
}

ModuleContext 表示一個(gè)子模塊需要注入的參數(shù)座每,其中 viewGroups 表示該子模塊所擁有的所有布局 ViewGroup(一個(gè)模塊可以有多處布局)。

有了參數(shù)摘悴,就可以開始創(chuàng)建模塊抽象類:AbsModule峭梳。

AbsModule 作為基類至少要有以下功能:

1.初始化模塊功能(上面定義的 ModuleContext 在這里注入)。
2.生命周期觸發(fā)蹂喻;
3.保存狀態(tài)觸發(fā)葱椭;

public abstract class AbsModule {

    //初始化將AbsModule的參數(shù)傳入
    public abstract void init(ModuleContext moduleContext);

    public abstract void onSaveInstanceState(Bundle outState);

    public abstract void onResume();

    public abstract void onPause();

    public abstract void onStop();

    public abstract void onOrientationChanges(boolean isLandscape);

    public abstract void onDestroy();
}

模塊管理類將會(huì)對(duì)其下所有的模塊 AbsModule 執(zhí)行初始化、生命周期分發(fā)等口四。這里為了擴(kuò)展性孵运,模塊管理類將派生出以下倆個(gè)類:

ModuleManager:抽象管理類,僅做模塊的相關(guān)方法分發(fā)蔓彩;

public class ModuleManager {
    private List<String> modules = new ArrayList<>(); //模塊類全限定名
    protected HashMap<String, AbsModule> allModules = new HashMap<>(); //模塊實(shí)體

    public List<String> getModuleNames() {
        return modules;
    }

    public void moduleConfig(List<String> modules) {
        this.modules = modules;
    }

    public AbsModule getModuleByName(String name) {
        if (!allModules.isEmpty()) {
            return allModules.get(name);
        }
        return null;
    }

    public void onResume() {
        for (AbsModule module : allModules.values()) {
            if (module != null) {
                module.onResume();
            }
        }
    }

    public void onPause() {
        for (AbsModule module : allModules.values()) {
            if (module != null) {
                module.onPause();
            }
        }
    }

    public void onStop() {
        for (AbsModule module : allModules.values()) {
            if (module != null) {
                module.onStop();
            }
        }
    }

    public void onDestroy() {
        for (AbsModule module : allModules.values()) {
            if (module != null) {
                module.onDestroy();
            }
        }
    }

    public void onConfigurationChanged(Configuration newConfig) {
        for (AbsModule module : allModules.values()) {
            if (module != null) {
                module.onOrientationChanges(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
            }
        }
    }
}

ActivityModuleManager:Activity 模塊管理類治笨,負(fù)責(zé)模塊的初始化驳概,然后將初始化成功的模塊交給父類 ModuleManager 進(jìn)行相關(guān)方法分發(fā)。

ActivityModuleManager 一共完成了如下功能:

1.創(chuàng)建指定模塊旷赖;
2.指定模塊依賴注入顺又;
3.初始化指定模塊;
4.納入方法分發(fā)管理等孵。

簡(jiǎn)單來說一句話稚照,模塊依次初始化。

public class ActivityModuleManager extends ModuleManager {

    public void initModules(Bundle saveInstance, Activity activity, HashMap<String, ArrayList<Integer>> modules) {
        if (activity == null || modules == null) {
            return;
        }
        //配置Activity下的所有module全限定名 后續(xù)可以根據(jù)名稱還原module實(shí)體(實(shí)體才包含ViewGroup俯萌、Activity等參數(shù))
        moduleConfig(new ArrayList<>(modules.keySet()));
        //依次給所有module初始化:1.創(chuàng)建實(shí)體 2.傳遞參數(shù) 3.調(diào)用初始化 4.納入生命周期管理
        for (String moduleName : modules.keySet()) {
            //創(chuàng)建對(duì)應(yīng)module
            AbsModule module = ModuleFactory.newModuleInstance(moduleName);
            if (module != null) {
                //創(chuàng)建參數(shù)
                ModuleContext moduleContext = new ModuleContext();
                moduleContext.setContext(activity);
                moduleContext.setSaveInstance(saveInstance);
                SparseArrayCompat<ViewGroup> viewGroups = new SparseArrayCompat<>();
                ArrayList<Integer> mViewIds = modules.get(moduleName);
                if (mViewIds != null && mViewIds.size() > 0) {
                    for (int i = 0; i < mViewIds.size(); i++) {
                        viewGroups.put(i, (ViewGroup) activity.findViewById(mViewIds.get(i)));
                    }
                }
                moduleContext.setViewGroups(viewGroups);
                //調(diào)用初始化(參數(shù)傳遞)
                module.init(moduleContext);
                //納入管理
                allModules.put(moduleName, module);
            }
        }
    }
}

上面類使用了 ModuleFactory 創(chuàng)建實(shí)體模塊果录,其實(shí)現(xiàn)如下:

public class ModuleFactory {

    //反射初始化對(duì)應(yīng)module
    public static AbsModule newModuleInstance(String moduleName) {
        if (TextUtils.isEmpty(moduleName)) {
            return null;
        }
        try {
            Class<? extends AbsModule> moduleClzz = (Class<? extends AbsModule>) Class.forName(moduleName);
            return moduleClzz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }
}

使用反射獲取實(shí)體對(duì)象的意義主要在于 ModuleFactory 可以不依賴具體模塊,在組件化編程中咐熙,ModuleFactory 作為 Base 模塊的一員不依賴任何功能模塊的類非常必要弱恒。

現(xiàn)在,模塊管理類和模塊已經(jīng)有了關(guān)聯(lián)關(guān)系糖声,下面只需要通過 Activity 分發(fā)事件到 ActivityModuleManager斤彼,進(jìn)而就可以分發(fā)到子模塊中。這樣就可以專注子模塊的功能開發(fā)蘸泻,事件分發(fā)全權(quán)交給模塊管理類琉苇。

創(chuàng)建 Activity 的抽象類 ModuleManagerActivity,負(fù)責(zé)與模塊管理類通信:

public abstract class ModuleManagerActivity extends Activity {

    private ActivityModuleManager moduleManager;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //布局 onLayout 時(shí)初始化
        ViewTreeObserver viewTreeObserver = getWindow().getDecorView().getRootView().getViewTreeObserver();
        if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
            viewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
                @Override
                public void onWindowAttached() {
                    if (moduleManager == null) {
                        initModuleManager(savedInstanceState);
                    }
                }

                @Override
                public void onWindowDetached() {

                }
            });
        } else {
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (moduleManager == null) {
                        initModuleManager(savedInstanceState);
                    }
                }
            });
        }
    }

    private void initModuleManager(Bundle saveInstance) {
        moduleManager = new ActivityModuleManager();
        moduleManager.initModules(saveInstance, this, moduleConfig());
    }

    public abstract HashMap<String, ArrayList<Integer>> moduleConfig();

    @Override
    protected void onResume() {
        super.onResume();
        if (moduleManager != null){
            moduleManager.onResume();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (moduleManager != null){
            moduleManager.onStop();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (moduleManager != null){
            moduleManager.onDestroy();
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (moduleManager != null){
            moduleManager.onConfigurationChanged(newConfig);
        }
    }
}

至此組件化分發(fā)結(jié)構(gòu)代碼開發(fā)完畢悦施,下面開始測(cè)試實(shí)踐并扇。

測(cè)試

創(chuàng)建一個(gè)簡(jiǎn)單的模塊類 BodyAModule(可以當(dāng)作模擬上面簡(jiǎn)書 App 簡(jiǎn)信模塊,在這里編寫業(yè)務(wù)代碼)

public class BodyAModule extends AbsModule {

    private Activity activity;
    private ViewGroup parentViewGroup;

    @Override
    public void init(ModuleContext moduleContext) {
        activity = moduleContext.getContext();
        parentViewGroup = moduleContext.getViewGroups().get(0);
        initView();
    }

    private void initView() {
        LayoutInflater.from(activity).inflate(R.layout.content_body_a, parentViewGroup, true);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onStop() {

    }

    @Override
    public void onOrientationChanges(boolean isLandscape) {

    }

    @Override
    public void onDestroy() {

    }
}

布局也很簡(jiǎn)單:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="bodyA" />
</merge>

同理創(chuàng)建 BodyBModule抡诞。

MainActivity 繼承自 ModuleManagerActivity穷蛹,并引入 BodyAModuleBodyBModule昼汗。

public class MainActivity extends ModuleManagerActivity {

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

    @Override
    public HashMap<String, ArrayList<Integer>> moduleConfig() {
        HashMap<String, ArrayList<Integer>> map = new HashMap<>();
        map.put(PageConfig.BODY_A, new ArrayList<Integer>() {{
            add(R.id.al_fl_bodyA);
        }});
        map.put(PageConfig.BODY_B, new ArrayList<Integer>() {{
            add(R.id.al_fl_bodyB);
        }});
        return map;
    }
}

其中 PageConfig 僅為方便管理模塊類肴熏,代碼如下:

public class PageConfig {

public class PageConfig {

    public static final String BODY_A = "com.example.activitysend.BodyAModule";
    public static final String BODY_B = "com.example.activitysend.BodyBModule";
}

MainActivity 布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/al_fl_bodyA"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <FrameLayout
        android:id="@+id/al_fl_bodyB"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

這樣就將布局與實(shí)體 module 類關(guān)聯(lián)了起來,實(shí)際運(yùn)行效果如下圖:

效果

總結(jié)

上面示例中的模塊劃分清晰易懂顷窒,Activity蛙吏、module 簡(jiǎn)潔明了、功能專一鞋吉,在閱讀鸦做、開發(fā)、擴(kuò)展上都大有益處谓着。

組件化分發(fā)結(jié)構(gòu)的思想是將功能模塊化分割泼诱,分割后的多模塊負(fù)責(zé)業(yè)務(wù),由專門的管理類維護(hù)生命周期赊锚。而 Activity 或其他類則負(fù)責(zé)與管理類進(jìn)行生命周期的分發(fā)交互治筒,完全隔離業(yè)務(wù)處理屉栓。

上面的代碼參考自 <Android 組件化架構(gòu)> 一書,我做了修正和部分修改耸袜。實(shí)際開發(fā)中系瓢,我確實(shí)也有將功能模塊化、然后創(chuàng)建 Manager 進(jìn)行管理句灌、Activity 與 Manager 交互的編碼習(xí)慣,但是我有個(gè)嚴(yán)重的編碼誤區(qū):Activity 直接通過控制 Manager 執(zhí)行相關(guān)模塊的一些業(yè)務(wù)方法欠拾,而不是通過生命周期控制胰锌、讓模塊自身獨(dú)立完成業(yè)務(wù)功能,這樣是不對(duì)的藐窄,需要反思资昧。

舉個(gè)栗子,電商 App荆忍,在支付完成之后格带,刷新購物車頁面數(shù)據(jù)。錯(cuò)誤編碼可能是刹枉,EventBus 觸發(fā)購物車 Activity 調(diào)用該頁的 Manager 執(zhí)行刷新對(duì)應(yīng)模塊數(shù)據(jù)的方法叽唱。但是在組件化分發(fā)結(jié)構(gòu)上,Activity 只負(fù)責(zé)模塊的生命控制微宝,如初始化棺亭、生命周期分發(fā),不負(fù)責(zé)任何相關(guān)模塊的業(yè)務(wù)功能蟋软,所以正確的做法是镶摘,Activity 什么都不做,EventBus 直接在相關(guān)模塊中注冊(cè)岳守,由相關(guān)模塊直接接收 EventBus 事件并處理凄敢。

區(qū)別在于,誤區(qū) “Activity 通過控制 Manager 執(zhí)行相關(guān)模塊的業(yè)務(wù)方法” 本質(zhì)只是對(duì)功能的封裝湿痢,不是分割涝缝。封裝的結(jié)果會(huì)保留調(diào)用入口供其它類使用,分割在于盡可能的完全解耦蒙袍,將邏輯從 Activity 或其他地方剝離俊卤,業(yè)務(wù)功能上完完全全自我掌控、自我維護(hù)害幅,Activity 不調(diào)用也不參與删窒。

可以理解為將一個(gè) Activity 分解成了無數(shù)小 activity咽弦,這些小 activity 只接收生命周期管理,而業(yè)務(wù)上完全自我維護(hù)蝉仇,不受原本 Activity 的任何管制。

上述組件化分發(fā)架構(gòu)就是分割的良好體現(xiàn)饵逐,通過生命周期分發(fā)控制,實(shí)現(xiàn)業(yè)務(wù)代碼的完全剝離。

以上僅為個(gè)人理解恰矩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市憎蛤,隨后出現(xiàn)的幾起案子外傅,更是在濱河造成了極大的恐慌,老刑警劉巖俩檬,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萎胰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡棚辽,警方通過查閱死者的電腦和手機(jī)技竟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屈藐,“玉大人榔组,你說我怎么就攤上這事×撸” “怎么了搓扯?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)遣妥。 經(jīng)常有香客問我擅编,道長(zhǎng),這世上最難降的妖魔是什么箫踩? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任爱态,我火速辦了婚禮,結(jié)果婚禮上境钟,老公的妹妹穿的比我還像新娘锦担。我一直安慰自己,他們只是感情好慨削,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布洞渔。 她就那樣靜靜地躺著,像睡著了一般缚态。 火紅的嫁衣襯著肌膚如雪磁椒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天玫芦,我揣著相機(jī)與錄音浆熔,去河邊找鬼。 笑死桥帆,一個(gè)胖子當(dāng)著我的面吹牛医增,可吹牛的內(nèi)容都是我干的慎皱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼叶骨,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼茫多!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忽刽,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤天揖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后跪帝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宝剖,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年歉甚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扑眉。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纸泄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腰素,到底是詐尸還是另有隱情聘裁,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布弓千,位于F島的核電站衡便,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏洋访。R本人自食惡果不足惜镣陕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姻政。 院中可真熱鬧呆抑,春花似錦、人聲如沸汁展。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽食绿。三九已至侈咕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間器紧,已是汗流浹背耀销。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留品洛,地道東北人树姨。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓摩桶,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親帽揪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子硝清,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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