Android组件化初探

组件化的优势:

  • 代码架构更加清晰,降低项目的维护难度;
  • 组件模式下可以加快编译速度,提高开发效率;
  • 项目比较大的情况下,多团队独立开发不同模块,互不影响;
  • 利于向插件化变更。

架构:

  • App壳工程与具体的业务无关,作为门面封装入口。
  • Module实现具体的模块功能,Module之间解耦合,组件可独立于App壳工程单独运行。
  • Common用于存放公用资源,例如res、工具类,公用第三方library等,各Module依赖于Common。

Demo

https://github.com/huangyu/ComponentDemo

组件模式和集成模式

gradle.properties中配置isModule=false,通过if(isModule.toBoolean())判断是否是组件模式。

  • 注意:每次更改“isModule”的值后,需要点击 “Sync Project” 按钮。

在组件的build.gradle中,区分当前Module是组件模式还是集成模式。

1
2
3
4
5
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}

AndroidManifest和Application文件问题

1
2
3
4
5
6
7
8
9
10
11
12
13
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
// 集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'library/**'
}
}
}
}

每个使用两份AndroidManifest.xml:
一个用于集成模式,即定义内部组件、权限等信息,不包含Application信息和启动类信息。
一个用于组件模式,包含Application信息和启动类信息,可用于组件单独运行。

组件的Application放置于java/library目录,基于Common库的BaseApplication,集成模式打包不加入。
业务组件自己的 Application 可以在组件开发模式下初始化一些数据,Common 组件初始化公用数据,例如全局Context等。

资源冲突

资源重名,比如App壳工程有资源和Module重名,则会覆盖Module同名资源,造成显示不准确。
利用resourcePrefix “prefix_”在资源名加前缀可避免重名问题。
设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。

library 重复依赖

不同Module都依赖了Common,会不会导致 library 重复依赖呢?
实际上在 release 构建 APP 的过程中 Gradle 会自动将重复的 aar 包排除,因此不存在此问题。

混淆问题

一般对于开源的第三方库,没有加入混淆的必要;对于部分闭源的项目,如果需要可用consumerProguardFiles对核心代码和算法进行混淆。
我们自己的项目使用,Module间没有混淆代码的必要(不同开发小组没有必要相互隐藏代码实现吧),统一在App里混淆即可。

组件通信

组件间通信包括三个场景:(1)UI 跳转;(2)调用组件某个类的某个方法; (3)事件通知
前两个场景建议使用强大的路由库:https://github.com/alibaba/ARouter

UI跳转

使用方式:

  1. 通过@Route定义Activity或者Fragment的路径
  2. 使用ARouter.getInstance().build(“url”).navigation()进行跳转

如果模块没有集成进来,想要跳转到这个页面的时候,不会崩溃,设置debug模式的时候会出现找不到的提示,而点击属于这个模块的功能的时候则不会有反应。

在没有ARouter或者其他类似的路由库的时候,我们想从组件间进行Activity跳转,怎么处理?
答案就是:反射!只要知道对应要跳转的Activity包名全路径,反射即可获取对应的Activity类,即可进行跳转。
那么我们可以构造一个全局的Map,Map的key是路径,value是具体的类信息,即可实现全局的管理。
另外,需要了解APT:APT技术可以让我们通过自定义注解动态生成编译后的class代码,ARoute里使用了APT去生成注解处理文件。

ARouter背后的原理是怎么样的呢?实际上它的核心思想跟上面讲解的是一样的。
1.在Activity定义@Route注解,会在编译时期通过APT生成一些存储path和activityClass映射关系的类文件。
2.ARoute.init()在app进程启动的时候会拿到这些类文件,把保存这些映射关系的数据保存在map里。
3.通过build()方法传入要到达页面的路由地址,ARouter会通过它自己存储的路由表找到路由地址对应的Activity.class(activity.class = map.get(path)),然后new Intent()
4.当调用ARouter的withString()等方法它的内部会调用intent.putExtra(String name, String value)存放传递参数。
5.调用navigation()方法,它的内部会调用startActivity(intent)进行跳转,这样便可以实现两个相互没有依赖的module顺利的启动对方的Activity了。
ARoute的基本思想是这样,当然了ARoute还做了其他很多处理,例如@Interceptor的AOP,@Autowired的依赖注入等,不详细赘述。

调用组件某个类某个方法

这里参考ARoute的README文档:
通过依赖注入解耦:服务管理(一) 暴露服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 声明接口,其他组件通过接口来调用服务
public interface HelloService extends IProvider {
String sayHello(String name);
}

// 实现接口
@Route(path = "/service/hello", name = "测试服务")
public class HelloServiceImpl implements HelloService {

@Override
public String sayHello(String name) {
return "hello, " + name;
}

@Override
public void init(Context context) {

}
}

通过依赖注入解耦:服务管理(二) 发现服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Test {
@Autowired
HelloService helloService;

@Autowired(name = "/service/hello")
HelloService helloService2;

HelloService helloService3;

HelloService helloService4;

public Test() {
ARouter.getInstance().inject(this);
}

public void testService() {
// 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取
// Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,不设置name属性,会默认使用byType的方式发现服务(当同一接口有多个实现的时候,必须使用byName的方式发现服务)
helloService.sayHello("Vergil");
helloService2.sayHello("Vergil");

// 2. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byType
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
helloService3.sayHello("Vergil");
helloService4.sayHello("Vergil");
}
}

事件通知:

第三个场景可使用Android的本地广播或者EventBus:https://github.com/greenrobot/EventBus

定义事件:

1
public static class MessageEvent { /* Additional fields if needed */ }

事件响应:

1
2
@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};

注册EventBus:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}

@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}

发送事件:

1
EventBus.getDefault().post(new MessageEvent());

0%