Android 组件化的使用

当前博客新地址

http://xiaojinzi.tpddns.cn:18888/post/Android组件化的使用.html

前言

其实组件化方案很多很多,让人选择也是很多很多,但是为什么每一家有技术积累的公司都会参考别人的组件化方案自己去修修改改或者自己实现一套呢?往下看吧

技术的选型

使用开源的库的缺点和优点

  • 缺点

    • 缺乏一些自定义的操作,总是会受制于人的
    • 出现问题不能及时解决
    • 有些开源库比较复杂,在出现问题的时候,自己公司的技术人员一脸懵逼
    • 我们使用到的功能可能是几个,而引入一个库可能引入了很多我们不需要的功能
    • 每次升级的时候都要仔细的看升级的日志,要小心
    • 不像 OkHttp 之类的开源库专注于功能,组件化的库可能比较贴近项目,可能要全部的技术人员改变以往的习惯,所以一般为了更贴合项目和公司的开发,一般都会对原有项目改造或者借鉴自己写
  • 优点

    • 不用内部的技术人员去维护,省去了成本
    • 选择大公司出品的开源库,基本稳定,问题少(但是不代表不会出问题,如果该库使用人数巨多,那么库可能会更加的完善和健壮)

综上所述,一般公司的项目中,在有能力去维护组件化这种对项目整个架构或者整体代码要调整的情况下,自己借鉴或者改造开源项目去贴合项目会更加的好
而且组件中的路由要特别关心,因为当整个项目都可以用路由跳转的时候,那么我们也要兼顾一下 iOS,争取做到 后台下发一些路由 url 来达到跳转一致的效果

组件化几个比较重要的点

组件化的方式很多,但是每一个组件化都必须有这三点,我们做组件化只要保证做到这三点其实就是做了组件化了

  • 业务组件之间的隔离
项目拆分成多个 module
  • 业务组件之间的服务的提供
某个业务组件中的服务如何让别人使用
  • 业务组件之间的跳转
路由
  • 基础组件的下沉
比如:(实体对象,网络请求模块,数据库,本地存储.....)
  • 业务组件加载和卸载的生命周期
业务组件的生命周期(为每一个业务模块带去类似于Application的概念)
业务组件的动态的加载和卸载,比如像大公司的 App,后台能下发指令,下架某一个模块,比如 滴滴 在顺风车出现问题的情况下,下架手机上的顺风车的功能
这就是 '业务组件加载和卸载的生命周期' 可以做到的事情

示例代码

公司内网:组件化示例项目

github地址:组件化示例项目

这个项目里面实现了组件化的方案,并且使用组件化方案写了一个 Demo,整个项目架构如下:

Android 组件化的使用_第1张图片

  • app
壳工程
  • BaseModule
基础库工程
  • Component1
业务组件1
  • Component2
业务组件2
  • UserComponent
用户业务模块
  • ComponentApi
组件化的注解Api
  • ComponentApiCompiler
组件化的注解Api注解驱动器
  • ComponentApiImpl
组件化的基础实现
  • ComponentRxImpl
组件化结合Rxjava2的实现,这个ComponentRxImpl实现中会有一个很好用的功能,基于ComponentApiImpl扩展

组件化的使用

依赖

对外的发布版本请看 Component,里面有所有的依赖

在主工程的 build.gradle 中添加 maven 地址:

maven { url 'http://192.168.9.230:8081/repository/app-releases/' }

在基础工程 BaseModule 中添加依赖:(版本号你可以先写+,拉下来之后再写死)

api "com.ehi:component-impl:${version}"

或者 RxJava2的实现

api "com.ehi:component-impl-rx:${version}"

各个业务组件会依赖 BaseModule,所以自动会有上述的依赖

然后在各个业务组件中添加注解驱动器

annotationProcessor "com.ehi:component-api-compiler:${version}"

这个会生成各个业务组件的 Application 管理类和路由表

在壳工程初始化

ComponentConfig.init(this,true);

如果依赖了 Rx版本的实现,请调用

EHiRxRouter.tryErrorCatch(); // 这个可以帮助你在路由拿目标界面数据的时候或者整个路由过程中出现的异常可以被忽略,Rxjava2 的错误默认不处理会崩溃哒!!!

在壳工程注册业务模块

EHiModuleManager.getInstance().register("component1");
EHiModuleManager.getInstance().register("component2");
EHiModuleManager.getInstance().register("user");

同样你也可以反注册业务模块,让某一个业务模块下架,比如下架 业务组件1

EHiModuleManager.getInstance().unregister("component1");

每一个业务组件的配置

  • 配置 业务组件的 Host 名称

Android 组件化的使用_第2张图片

  • 编写业务组件的 Application,继承 IComponentApplication 接口实现创建和销毁的逻辑
    • 创建一个Application 实现 IComponentApplication接口,并且使用 @EHiModuleApp() 标记
    • 在创建和销毁方法中注册(反注册)自己的路由表,暴露(不暴露)自己的功能给其他的业务组件
@EHiModuleAppAnno()
public class Component1Application implements IComponentApplication {
     
    @Override
    public void onCreate(@NonNull final Application app) {
     
        // 你可以做一些当前业务模块的一些初始化
    }

    @Override
    public void onDestory() {
     
        // 你可以销毁有关当前业务模块的东西
    }
}

Activity 的配置

这里展示了一个业务组件1的一个 Activity 的使用

  • 使用 @EHiRouterAnno 注解, ** host** 表示模块,value表示path, interceptors表示进入这个页面需要执行的拦截器
  • 使用 QueryParameterSupport 帮助类获取 Uri 中传过来的 query 参数
  • 其他参数的获取和原生一样,原先的代码或者习惯无需更改
@EHiRouterAnno(
        host = "component1",
        value = "test",
        interceptors = {
     Component1Interceptor1.class, Component1Interceptor2.class},
        desc = "业务组件1的测试界面"
)
public class Component1TestAct extends AppCompatActivity {
     

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

        setContentView(R.layout.component1_test_act);

        TextView tv_data = findViewById(R.id.tv_data);
        tv_data.setText(QueryParameterSupport.getString(getIntent(), "data"));

        String data = getIntent().getStringExtra("data");

        if (data != null) {
     
            tv_data.setText(tv_data.getText() + "\n" + data);
        }

    }

    public void returnData(View view) {
     
        Intent intent = new Intent();
        intent.putExtra("data", "this is the return data");
        setResult(RESULT_OK, intent);
        finish();
    }

}

路由到其他界面

普通跳转

这段就跳转到上面我们写的那个例子的界面中了,并且我们还传了两个 query 值过去哦

EHiRouter
                .with(this)
                .host("component1")
                .path("main")
                .query("name", "cxj")
                .query("pass", "123")
                .navigate();

普通跳转Bundle带值

这段就跳转到上面我们写的那个例子的界面中了,我们传了两个 query 值,并且额外存了两个值到 Bundle 中携带过去,和我们普通的 传值效果是一样的,唯一的区别是 路由的 putXXX 方式利用方法名区别传不同的值, Intent 靠重载方法来传不同的值,但是我更倾向于利用方法区分,因为一旦你入参的参数类型有改动的时候,方法就会报错,而 Intent 不会
Intent intent = new Intent(Context,XXX.class);
intent.putExtra(key,value);

EHiRouter
                .with(this)
                .host("component1")
                .path("main")
                .query("name", "cxj")
                .query("pass", "123")
				.putString("name", "cxj1")
                .putInt("age", 25)
                .navigate();

普通跳转判断是否跳转成功

EHiRouter.with(this)
               .host("component1")
               .path("main")
               .query("name", "cxj")
               .query("pass", "123")
               .navigate(new EHiCallbackAdapter() {
     
                        @Override
                        public void onSuccess(@NonNull EHiRouterResult result) {
     
                                // 跳转成功,参数是 request 对象
                        }

                        @Override
                        public void onError(@NonNull Exception error) {
     
                                // 跳转失败,参数是 Exception 对象
                        }
                });

你就能从 routerResult 中的 isSuccess 判断是否成功啦!如果失败,routerResult 中的 error 字段也能给你错误的信息

普通跳转拿数据

EHiRouter
                .with(this)
                .host("component1")
                .path("main")
                .query("name", "cxj")
                .query("pass", "123")
                .requestCode(456) // requestCode
                .navigate();

你就可以在 onActivityResult 方法中拿到数据啦,这种方式和原生的写法没什么差别,骚操作且看下面

Rx方式跳转拿数据

EHiRxRouter
                .with(this)
                .host("component1")
                .path("main")
                .query("name", "cxj")
                .query("pass", "123")
                .requestCode(456)
                .newIntentCall()
                .subscribe(new Consumer<Intent>() {
     
                    @Override
                    public void accept(Intent intent) throws Exception {
     
                        // intent 参数中拿数据
                    }
                }, new Consumer<Throwable>() {
     
                    @Override
                    public void accept(Throwable throwable) throws Exception {
     
                        // 你可以自己处理错误,如果不传入 错误的回调接口,默认忽略
                    }
                });

这种方式可以说很明显减少了平常写的代码的量,并且让你摆脱了 onActivityResult,所以这可以在** Adapter 或者 任何一个有 Context(其实是一个Activity)的地方去使用**
上述的写法是要处理错误,如果你不想处理错误,可以这样写

EHiRxRouter
                .with(this)
                .host("component1")
                .path("main")
                .query("name", "cxj")
                .query("pass", "123")
                .requestCode(456)
                .newIntentCall()
                .subscribe(new Consumer<Intent>() {
     
                    @Override
                    public void accept(Intent intent) throws Exception {
     
                        // intent 参数中拿数据
                    }
                });

Rx 方式和其他操作组合

SingleTransformer<String, Intent> transformer = EHiRxRouter
                .with(this)
                .host("component1")
                .path("main")
                .query("name", "cxj")
                .query("pass", "123")
                .requestCode(456)
                .intentSingleTransformer();

你可以拿到一个 SingleTransformer,然后把它结合到你任何一个地方,完美的嵌入到一个流程中,不会被 onActivityResult 等方式打断你的流程

服务的提供和使用

在依赖库中有一个设计好的服务容器类

EHiService.class

定义服务接口

当你想提供业务组件1的功能出去的时候,你在**基础库(BaseModule)**的 service 包下面新建一个接口文件,这个接口会被所有的业务模块引用

Android 组件化的使用_第3张图片

public interface Component1Service {
     
    Fragment getFragment();
	void xxx();
	int count();
	......
}

注册服务到服务容器

非单例服务
@EHiServiceAnno(value = {
     Component1Service.class},singleTon = false)
public class Component1ServiceImpl implements Component1Service {
     

    private Context context;

    public Component1ServiceImpl(@NonNull Application app) {
     
        context = app;
        Toast.makeText(app, "创建了 Component1Service 服务", Toast.LENGTH_SHORT).show();
    }

    @Override
    public Fragment getFragment() {
     
        return new Component1Fragment();
    }


}
单例服务
@EHiServiceAnno(value = {
     Component1Service.class},singleTon = true)
public class Component1ServiceImpl implements Component1Service {
     

    private Context context;

    public Component1ServiceImpl(@NonNull Application app) {
     
        context = app;
        Toast.makeText(app, "创建了 Component1Service 服务", Toast.LENGTH_SHORT).show();
    }

    @Override
    public Fragment getFragment() {
     
        return new Component1Fragment();
    }


}

服务的使用

然后其他任何一个地方就可以这样子使用啦

Android 组件化的使用_第4张图片

总结

到此组件化的方案介绍完毕,里面有很多基于得到的经验,也有很多的调整改进和优化
如果有兴趣想更清楚内部的实现,请详细的去琢磨以下三个 module 中的代码吧,不明白的可以来和我交流
image.png

你可能感兴趣的:(Android)