Android模塊化探索和實踐(3):模塊間徹底隔離

在上一篇文章中Android 模塊化探索和實踐(2):Dagger2實現(xiàn)模塊化(組件化)實現(xiàn)了模塊間的Dagger2注入蔑匣,但是細心的讀者應該會發(fā)現(xiàn),那個模塊化方案其實是不徹底绘面,因為沒有做到模塊之間的徹底隔離腻惠。比如在主APP中环肘,需要手動在build.gradle中引入module,這樣就無法做到代碼和資源隔離集灌,這是不徹底的模塊化方案悔雹。本篇文章主要解決這個問題

別人的方案

參考了很多大牛的模塊化方案,找到了一個可行度高欣喧、風險可控腌零、后期好維護的方案。這個就是目前“得到”采用的組件化方案唆阿,該方案詳細說明可以參考得到:徹底組件化方案益涧。該組件化方案的核心有兩點:

  1. 通過Router實現(xiàn)各個組件的動態(tài)注冊和動態(tài)卸載,同時驯鳖,各個模塊間通過接口實現(xiàn)數(shù)據(jù)交互闲询,接口暴露出來,注冊模塊的同時接口也被注冊到Router中浅辙;

注冊模塊方式如下:


Router.registerComponent("com.xud.modulea.BusinessAAppLike");

注冊Service和使用Service方式如下:


Router.getInstance().addService(BusinessAService.class, new BusinessAServiceImpl());

Fragment fragment = Router.getInstance().getService(BusinessAService.class).getMainFragment();

  1. 實現(xiàn)了一個自定義的Gradle腳本扭弧,編譯時根據(jù)模塊下gradle.properties文件中配置的依賴組件名,往build.gradle文件中注入“api project(':component')”记舆,實現(xiàn)了編譯時組件依賴鸽捻,從而達到了代碼和資源的隔離。

// gradle.properties中的依賴配置示例

debugComponent=modulea,moduleb,modulekotlin

compileComponent=modulea,moduleb,modulekotlin

本文要解決的問題

本文就是在這個方案的基礎上泽腮,對之前的方案做進一步的改進御蒲,主要解決的問題有三個:

  1. 明確模塊之間的架構;

  2. 優(yōu)化模塊中Dagger2的注入诊赊;

  3. 支持Databinding

源碼已經(jīng)提交到Github厚满,地址為 https://github.com/xudjx/DaggerModules

模塊分層架構

能用圖說明,就不廢話了豪筝,見下圖:

Android模塊化分層架構.png

Dagger2注入的優(yōu)化

各模塊需要共享的實例注入寫在BaseModule中。詳細的原理我在文章中Android 模塊化探索和實踐(2):Dagger2實現(xiàn)模塊化(組件化)有詳細的介紹摘能,這里只就優(yōu)化點說明一下续崖。

1、 簡化業(yè)務模塊的ModuleKit, 僅提供AppComponent的實例獲取团搞。以BusinessAModuleKit為例严望,其實現(xiàn)如下:

public class BusinessAModuleKit {

    private static BusinessAModuleKit instance;
    private AppComponent component;

    public static BusinessAModuleKit getInstance() {
        if (instance == null) {
            synchronized (BusinessAModuleKit.class) {
                if (instance == null) {
                    instance = new BusinessAModuleKit();
                }
            }
        }
        return instance;
    }

    public BusinessAModuleKit init(AppModuleComponentDelegate appModuleComponentDelegate) {
        this.component = appModuleComponentDelegate.initAppComponent();
        return this;
    }

    public AppComponent getComponent() {
        return component;
    }
}

2、 單獨調試業(yè)務模塊或者注冊業(yè)務模塊時逻恐,不要忘記初始化ModuleKit, 以BusinessAModuleKit的初始化為例

加載組件時的初始化過程

public class BusinessAAppLike implements IApplicationLike {

    private AppModuleComponentDelegate componentDelegate = new AppModuleComponentDelegate() {
        @Override
        public AppComponent initAppComponent() {
            BusinessAAppComponent appComponent = DaggerBusinessAAppComponent.builder()
                    .baseAppComponent(BaseModuleKit.getInstance().getComponent())
                    .build();
            return appComponent;
        }
    };

    @Override
    public void onCreate() {
        Router.getInstance().addService(BusinessAService.class, new BusinessAServiceImpl()); 

        // 初始化BusinessAModuleKit
        BusinessAModuleKit.getInstance().init(componentDelegate);
        ModuleAUIInterCeptor.isRegister = true;
    }

    @Override
    public void onStop() {
        Router.getInstance().removeService(BusinessAService.class);
        ModuleAUIInterCeptor.isRegister = false;
    }
}

單獨調試時的初始化過程

public class BusinessAApplication extends BaseApplication {

    private AppModuleComponentDelegate componentDelegate = new AppModuleComponentDelegate() {
        @Override
        public AppComponent initAppComponent() {
            BusinessAAppComponent appComponent = DaggerBusinessAAppComponent.builder()
                    .baseAppComponent(BaseModuleKit.getInstance().getComponent())
                    .build();
            return appComponent;
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void initComponentDi() {
        BusinessAModuleKit.getInstance().init(componentDelegate);
    }

    @Override
    public void registerRouter() {
        RouterManager.initRouter(instance);
        Router.getInstance().addService(BusinessAService.class, new BusinessAServiceImpl());
    }
}

Databinding支持

在模塊化方案中也是可以使用Databinding的像吻,你只需要在各個模塊的build.gradle添加


dataBinding {

 enabled = true

}

為了更方便各個業(yè)務模塊之間共享Databinding基礎組件峻黍,我將通用的Databinding Adapter注冊在BaseModule,同時抽象出通用的BaseDataBindingActivity 和 BaseDataBindingFragment等拨匆。

以PicassoBindingAdapters為例:

public class PicassoBindingAdapters {

    @BindingAdapter(value = {"imageUrl"})
    public static void loadImage(ImageView view, String url) {
        PicassoHelperUtils.displayImage(url, view);
    }

    @BindingAdapter(value = {"imageUrl", "imageError"})
    public static void loadImage(ImageView view, String url, Drawable error) {
        PicassoHelperUtils.displayImage(url, view, error);
    }

    @BindingAdapter(value = {"imageUrl", "imageError", "imageWidth", "imageHeight", "imageCenterCrop"}, requireAll = false)
    public static void loadImage(ImageView view, String url, Drawable error, int width, int height, boolean centerCrop) {
        PicassoHelperUtils.displayImage(view, url, error, width, height, centerCrop);
    }
}

BaseDataBindingActivity的設計如下:

public abstract class BaseDataBindingActivity<T extends ViewDataBinding> extends BaseActivity {

    protected T mBinding;

    @Override
    protected final void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, getLayoutRes());
        onCreated(savedInstanceState);
    }

    @Override
    protected void onDestroy() {
        mBinding.unbind();
        super.onDestroy();
    }

    @LayoutRes
    protected abstract int getLayoutRes();

    protected void onCreated(Bundle savedInstanceState) {
    }
}

此外姆涩,需要提到的一點是,在Databinding頁面中使用Dagger2有一點不一樣的地方惭每,即該頁面注入的Component必須繼承android.databinding.DataBindingComponent骨饿,否則會注入失敗


@PerView

@Component(dependencies = BusinessAAppComponent.class, modules = BaseViewModule.class)

public interface DJDataBandingComponent extends android.databinding.DataBindingComponent {

 void inject(ModuleADatabandingActivity activity);

}

有了以上這些基礎構件,在模塊中使用Databinding就變得很簡單了台腥。首先宏赘,先創(chuàng)建module_a_fragment_databand.xml;

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.xud.modulea.ui.ModuleADatabandingActivity.ViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical">

        <ImageView android:id="@+id/img"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="centerCrop"
            app:imageUrl='@{"http://7xopuh.dl1.z0.glb.clouddn.com/pic06.jpg"}' />


        <TextView
            android:id="@+id/detail_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{viewModel.detail}"
            android:textSize="15dp"/>

    </LinearLayout>

</layout>

然后再創(chuàng)建ModuleADatabandingActivity黎侈,繼承BaseDataBindingActivity察署。

public class ModuleADatabandingActivity extends BaseDataBindingActivity<ModuleAFragmentDatabandBinding> {

    private DJDataBandingComponent mDJDataBandingComponent;

    public DJDataBandingComponent dbComponent() {
        if(mDJDataBandingComponent == null) {
            mDJDataBandingComponent = DaggerDJDataBandingComponent.builder()
                    .businessAAppComponent((BusinessAAppComponent) BusinessAModuleKit.getInstance().getComponent())
                    .baseViewModule(new BaseViewModule(this))
                    .build();
        }
        return mDJDataBandingComponent;
    }

    @Inject
    BusinessAApi businessAApi;

    ViewModel viewModel;

    @Override
    protected void onCreated(Bundle savedInstanceState) {
        super.onCreated(savedInstanceState);
        dbComponent().inject(this);
        viewModel = new ViewModel();
        mBinding.setViewModel(viewModel);
        initData();
    }

    @Override
    protected int getLayoutRes() {
        return R.layout.module_a_fragment_databand;
    }

    private void initData() {12·
        // todo
    }

    public class ViewModel {
        public ObservableField<String> detail = new ObservableField<>();
    }
}

帖的代碼比較多,讀者有興趣的話峻汉,還是移步 https://github.com/xudjx/DaggerModules

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末贴汪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俱济,更是在濱河造成了極大的恐慌嘶是,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛛碌,死亡現(xiàn)場離奇詭異聂喇,居然都是意外死亡,警方通過查閱死者的電腦和手機蔚携,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門希太,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酝蜒,你說我怎么就攤上這事誊辉。” “怎么了亡脑?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵堕澄,是天一觀的道長。 經(jīng)常有香客問我霉咨,道長蛙紫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任途戒,我火速辦了婚禮坑傅,結果婚禮上,老公的妹妹穿的比我還像新娘喷斋。我一直安慰自己唁毒,他們只是感情好蒜茴,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浆西,像睡著了一般粉私。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上室谚,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天毡鉴,我揣著相機與錄音,去河邊找鬼秒赤。 笑死猪瞬,一個胖子當著我的面吹牛,可吹牛的內容都是我干的入篮。 我是一名探鬼主播陈瘦,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼潮售!你這毒婦竟也來了痊项?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤酥诽,失蹤者是張志新(化名)和其女友劉穎鞍泉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肮帐,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡咖驮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了训枢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片托修。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖恒界,靈堂內的尸體忽然破棺而出睦刃,到底是詐尸還是另有隱情,我是刑警寧澤十酣,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布涩拙,位于F島的核電站,受9級特大地震影響耸采,放射性物質發(fā)生泄漏兴泥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一洋幻、第九天 我趴在偏房一處隱蔽的房頂上張望郁轻。 院中可真熱鬧翅娶,春花似錦文留、人聲如沸好唯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骑篙。三九已至,卻和暖如春森书,著一層夾襖步出監(jiān)牢的瞬間靶端,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工凛膏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杨名,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓猖毫,卻偏偏與公主長得像台谍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吁断,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容