在上一篇文章中Android 模塊化探索和實踐(2):Dagger2實現(xiàn)模塊化(組件化)實現(xiàn)了模塊間的Dagger2注入蔑匣,但是細心的讀者應該會發(fā)現(xiàn),那個模塊化方案其實是不徹底绘面,因為沒有做到模塊之間的徹底隔離腻惠。比如在主APP中环肘,需要手動在build.gradle中引入module,這樣就無法做到代碼和資源隔離集灌,這是不徹底的模塊化方案悔雹。本篇文章主要解決這個問題
別人的方案
參考了很多大牛的模塊化方案,找到了一個可行度高欣喧、風險可控腌零、后期好維護的方案。這個就是目前“得到”采用的組件化方案唆阿,該方案詳細說明可以參考得到:徹底組件化方案益涧。該組件化方案的核心有兩點:
- 通過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();
- 實現(xiàn)了一個自定義的Gradle腳本扭弧,編譯時根據(jù)模塊下gradle.properties文件中配置的依賴組件名,往build.gradle文件中注入“api project(':component')”记舆,實現(xiàn)了編譯時組件依賴鸽捻,從而達到了代碼和資源的隔離。
// gradle.properties中的依賴配置示例
debugComponent=modulea,moduleb,modulekotlin
compileComponent=modulea,moduleb,modulekotlin
本文要解決的問題
本文就是在這個方案的基礎上泽腮,對之前的方案做進一步的改進御蒲,主要解決的問題有三個:
明確模塊之間的架構;
優(yōu)化模塊中Dagger2的注入诊赊;
支持Databinding
源碼已經(jīng)提交到Github厚满,地址為 https://github.com/xudjx/DaggerModules
模塊分層架構
能用圖說明,就不廢話了豪筝,見下圖:
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