在上一篇文章中Android 模块化探索和实践(2):Dagger2实现模块化(组件化)实现了模块间的Dagger2注入,但是细心的读者应该会发现,那个模块化方案其实是不彻底,因为没有做到模块之间的彻底隔离。比如在主APP中,需要手动在build.gradle中引入module,这样就无法做到代码和资源隔离,这是不彻底的模块化方案。本篇文章主要解决这个问题
别人的方案
参考了很多大牛的模块化方案,找到了一个可行度高、风险可控、后期好维护的方案。这个就是目前“得到”采用的组件化方案,该方案详细说明可以参考得到:彻底组件化方案。该组件化方案的核心有两点:
- 通过Router实现各个组件的动态注册和动态卸载,同时,各个模块间通过接口实现数据交互,接口暴露出来,注册模块的同时接口也被注册到Router中;
注册模块方式如下:
Router.registerComponent("com.xud.modulea.BusinessAAppLike");
注册Service和使用Service方式如下:
Router.getInstance().addService(BusinessAService.class, new BusinessAServiceImpl());
Fragment fragment = Router.getInstance().getService(BusinessAService.class).getMainFragment();
- 实现了一个自定义的Gradle脚本,编译时根据模块下gradle.properties文件中配置的依赖组件名,往build.gradle文件中注入“api project(':component')”,实现了编译时组件依赖,从而达到了代码和资源的隔离。
// gradle.properties中的依赖配置示例
debugComponent=modulea,moduleb,modulekotlin
compileComponent=modulea,moduleb,modulekotlin
本文要解决的问题
本文就是在这个方案的基础上,对之前的方案做进一步的改进,主要解决的问题有三个:
-
明确模块之间的架构;
-
优化模块中Dagger2的注入;
-
支持Databinding
源码已经提交到Github,地址为 https://github.com/xudjx/DaggerModules
模块分层架构
能用图说明,就不废话了,见下图:
Android模块化分层架构.pngDagger2注入的优化
各模块需要共享的实例注入写在BaseModule中。详细的原理我在文章中Android 模块化探索和实践(2):Dagger2实现模块化(组件化)有详细的介绍,这里只就优化点说明一下。
1、 简化业务模块的ModuleKit, 仅提供AppComponent的实例获取。以BusinessAModuleKit为例,其实现如下:
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、 单独调试业务模块或者注册业务模块时,不要忘记初始化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
}
为了更方便各个业务模块之间共享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就变得很简单了。首先,先创建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>
然后再创建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
网友评论