美文网首页Android收藏集android开发专题组件化之路
Android模块化探索和实践(3):模块间彻底隔离

Android模块化探索和实践(3):模块间彻底隔离

作者: 浪淘沙xud | 来源:发表于2018-04-10 16:04 被阅读252次

    在上一篇文章中Android 模块化探索和实践(2):Dagger2实现模块化(组件化)实现了模块间的Dagger2注入,但是细心的读者应该会发现,那个模块化方案其实是不彻底,因为没有做到模块之间的彻底隔离。比如在主APP中,需要手动在build.gradle中引入module,这样就无法做到代码和资源隔离,这是不彻底的模块化方案。本篇文章主要解决这个问题

    别人的方案

    参考了很多大牛的模块化方案,找到了一个可行度高、风险可控、后期好维护的方案。这个就是目前“得到”采用的组件化方案,该方案详细说明可以参考得到:彻底组件化方案。该组件化方案的核心有两点:

    1. 通过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();
    
    
    1. 实现了一个自定义的Gradle脚本,编译时根据模块下gradle.properties文件中配置的依赖组件名,往build.gradle文件中注入“api project(':component')”,实现了编译时组件依赖,从而达到了代码和资源的隔离。
    
    // gradle.properties中的依赖配置示例
    
    debugComponent=modulea,moduleb,modulekotlin
    
    compileComponent=modulea,moduleb,modulekotlin
    
    

    本文要解决的问题

    本文就是在这个方案的基础上,对之前的方案做进一步的改进,主要解决的问题有三个:

    1. 明确模块之间的架构;

    2. 优化模块中Dagger2的注入;

    3. 支持Databinding

    源码已经提交到Github,地址为 https://github.com/xudjx/DaggerModules

    模块分层架构

    能用图说明,就不废话了,见下图:

    Android模块化分层架构.png

    Dagger2注入的优化

    各模块需要共享的实例注入写在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

    相关文章

      网友评论

      • JustFutureRight:根据你的帖子已经实现到加入Dagger2依赖注入框架了,感谢分享:clap:,DataBinding框架个人感觉不太适合调试,所以不打算用,ComponentService那一层好像Arouter也提供了,现在有一个问题就是 比如modulea在debug模式时需要在Application里初始化需要的组件,是不是在它作为library提供时就需要在MainApplication里初始化
        浪淘沙xud:挺好的!需要初始化的,具体你可以比较一下BusinessAApplication(Debug模式) 和 BusinessAAppLike (Release模式),作为Library引入时会在MainApplication中回调BusinessAAppLike的初始化方法

      本文标题:Android模块化探索和实践(3):模块间彻底隔离

      本文链接:https://www.haomeiwen.com/subject/cirnhftx.html