前言
哈哈哈哈,我又回来了,小金子又回来了,好久没写博客了,是时候更新一波了
最近项目在做组件化,然后这次给大家带来组件化的实现和应用
组件化方案地址:https://github.com/xiaojinzi123/Component
组件化带来的好处
- 界面之间通过路由跳转,H5跳转到原生的界面更加的方便了,和请求一个服务器的地址一样一样的,还可以携带参数
- 我们可以在开发的时候就编译打包指定的业务组件,提升打包和运行的速度,这点我是很喜欢喜欢的
- 界面的跳转因为路由的实现我们可以让后台动态的配置了!达到App的千变万化
- 完全的组件化实施之后我们的每一个业务组件的功能高内聚,并且能对外提供功能
下面这张图我们很熟悉,我们今天要做的事情就是实现如下的架构
组件化带来的问题
- Activity 之间的交互
- 业务组件内的服务如何提供
- 业务组件的声明周期怎么解决
我们带着上面的几个问题,我们来实现我们的组件化架构
Activity 路由的实现
最终的跳转方式:
Router.with(this)
.hist("component1")
.path("test")
.requestCode(123)
.navigate();
我画了一张很可爱的图,很清楚的表示了我们的业务组件如果发起一个路由请求的时候,经历了一个怎么样的过程
路由寻找目标的过程
- 路由请求发送到中心路由上
- 中心路由分发路由请求到下面所有业务组件中的子路由
- 如果某个子路由能处理这个路由请求,则返回一个true表示消费了这个事件
- 然后子路由根据对应关系找到对应的Activity,实现跳转
这里面有几点非常重要
- 子路由是由每一个业务模块的注解驱动器生成的
- 子路由中的Path-Activity的对应关系也是注解驱动器生成的
上述的组件化方案地址中的业务组件1中我写了两个 Activity
@Router(host = "component1", value = "main", desc = "业务组件1的主界面")
public class Component1MainAct extends AppCompatActivity {
}
@Router(value = "component1/test",desc = "业务组件1的测试界面")
public class Component1TestAct extends AppCompatActivity {
}
当你使用Android Studio构建项目的时候会生成以下的代码
final class Component1UiRouter extends ModuleRouterImpl {
@Override
public String getHost() {
return "component1";
}
@Override
public void initMap() {
super.initMap();
routerMap.put("component1/component1/test",Component1TestAct.class);
routerMap.put("component1/main",Component1MainAct.class);
}
}
所以我们的路由框架其实和之间的跳转方式没有什么差别
Intent intent = new Intent(context, Component1MainAct.class);
context.startActivity(intent);
这两者的差别就是之前我们访问 Component1MainAct 是直接引用,现在是通过一个别名 “component1/main” 让路由框架帮我们去访问,本子上都是一样的
EHiUiRouter.getInstance().openUri(this, Uri.parse("EHi://component1/main"), null, 123);
为什么还有子路由
上面我解释了子路由其实是由每一个业务module中的注解驱动器生成的,但是为什么每一个 module 都生成一个呢?
原因有两点:
- 方便动态加载和卸载,达到控制的目的
- 每一个module 都生成一个单独的类实现比较方便
注册子路由
ModuleManager.getInstance().registerArr(
"component1",
"component2",
"component3"
)
这个过程是一个反射的过程,因为每一个生成的子路由都有一定的命名规则:
固定的包名.业务组件的名称+"UiRouter"
比如 component1 业务组件生成的路由的全类名就是:
com.ehi.component.impl.Component1UiRouter
比如 component2 业务组件生成的路由的全类名就是:
com.ehi.component.impl.Component2UiRouter
使用注解驱动器生成子路由实现
创建一个路由的Api的Java工程(ComponentApi)
新建了两个注解和两个工具类
/**
* 这是一个路由的 Api
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Router {
/**
* 定义host
*
* @return
*/
String host() default "";
/**
* 路径
*
* @return
*/
String value();
/**
* 描述信息
*
* @return
*/
String desc() default "";
}
工具类就是写了一些注解驱动器项目和Android项目都要使用的信息和方法
创建一个路由的ApiCompiler的Java工程(ComponentApiCompiler)
路由注解驱动器核心实现(RouterProcessor)
- 初始化的方法中获取到模块中定义的模块的名称
- 读取到所有的用注解 @Router 标记的Activity的信息
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (CollectionUtils.isNotEmpty(set)) {
Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith(Router.class);
mMessager.printMessage(Diagnostic.Kind.NOTE, " >>> size = " + (routeElements == null ? 0 : routeElements.size()));
parseRouterAnno(routeElements);
createRouterImpl();
return true;
}
return false;
}
private Map<String, com.xiaojinzi.component.bean.RouterBean> routerMap = new HashMap<>();
/**
* 解析注解
*
* @param routeElements
*/
private void parseRouterAnno(Set<? extends Element> routeElements) {
TypeMirror typeActivity = mElements.getTypeElement(EHiConstants.ACTIVITY).asType();
for (Element element : routeElements) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "element == " + element.toString());
TypeMirror tm = element.asType();
if (!(element instanceof TypeElement)) {
mMessager.printMessage(Diagnostic.Kind.ERROR, element + " is not a 'TypeElement' ");
continue;
}
if (!mTypes.isSubtype(tm, typeActivity)) {
mMessager.printMessage(Diagnostic.Kind.ERROR, element + " can't use 'Router' annotation");
continue;
}
// 如果是一个Activity
Router router = element.getAnnotation(Router.class);
if (router == null) {
continue;
}
if (router.value() == null || "".equals(router.value())) {
mMessager.printMessage(Diagnostic.Kind.ERROR, element + ":Router'value can;t be null or empty string");
continue;
}
// 如果有host那就必须满足规范
if (router.host() == null || "".equals(router.host())) {
} else {
if (router.host().contains("/")) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "the host value '" + router.host() + "' can contains '/'");
}
}
`
if (routerMap.containsKey(getHostAndPath(router.host(), router.value()))) {
mMessager.printMessage(Diagnostic.Kind.ERROR, element + ":Router'value is alreay exist");
continue;
}
com.xiaojinzi.component.bean.RouterBean routerBean = new com.xiaojinzi.component.bean.RouterBean();
routerBean.setHost(router.host());
routerBean.setPath(router.value());
routerBean.setDesc(router.desc());
//routerBean.setPriority(router.priority());
routerBean.setRawType(element);
routerMap.put(getHostAndPath(router.host(), router.value()), routerBean);
mMessager.printMessage(Diagnostic.Kind.NOTE, "router.value() = " + router.value() + ",Activity = " + element);
}
}
每一个Activity的注解信息和Activity的信息都被读取到了集合 routerMap 中
- 关键生成的代码,这里使用的是 squareup 团队的 javapoet 来生成代码
javapoet 是一个很好用的代码生成的库,这里我们用来生成子路由,由于我们的子路由中是最后实现跳转的类,而我们生成的只需要是 Path 和 Activity 的对应关系,所以我写了一个子路由的父类来实现不用生成的代码
/**
* 生成路由
*/
private void createRouterImpl() {
String claName = ComponentUtil.genHostUIRouterClassName(componentHost);
//pkg
String pkg = claName.substring(0, claName.lastIndexOf("."));
//simpleName
String cn = claName.substring(claName.lastIndexOf(".") + 1);
// superClassName
ClassName superClass = ClassName.get(mElements.getTypeElement(ComponentUtil.UIROUTER_IMPL_CLASS_NAME));
MethodSpec initHostMethod = generateInitHostMethod();
MethodSpec initMapMethod = generateInitMapMethod();
try {
JavaFile.builder(pkg, TypeSpec.classBuilder(cn)
//.addModifiers(Modifier.PUBLIC)
.addModifiers(Modifier.FINAL)
.superclass(superClass)
.addMethod(initHostMethod)
.addMethod(initMapMethod)
.build()
).build().writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
private MethodSpec generateInitMapMethod() {
TypeName returnType = TypeName.VOID;
final MethodSpec.Builder openUriMethodSpecBuilder = MethodSpec.methodBuilder("initMap")
.returns(returnType)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC);
openUriMethodSpecBuilder.addStatement("super.initMap()");
routerMap.forEach(new BiConsumer<String, com.xiaojinzi.component.bean.RouterBean>() {
@Override
public void accept(String key, RouterBean routerBean) {
openUriMethodSpecBuilder.addStatement(
"routerMap" + ".put($S,$T.class)", key,
ClassName.get((TypeElement) routerBean.getRawType()));
}
});
return openUriMethodSpecBuilder.build();
}
private MethodSpec generateInitHostMethod() {
TypeName returnType = TypeName.get(typeString);
MethodSpec.Builder openUriMethodSpecBuilder = MethodSpec.methodBuilder("getHost")
.returns(returnType)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC);
openUriMethodSpecBuilder.addStatement("return $S", componentHost);
return openUriMethodSpecBuilder.build();
}
private String getHostAndPath(String host, String path) {
if (host == null || "".equals(host)) {
host = componentHost;
}
if (path != null && path.length() > 0 && path.charAt(0) != '/') {
path = "/" + path;
}
return host + path;
}
以上是实现路由的注解驱动器的代码,其实还有很多的小细节,这里就不一一展示了,有空请大大们去看源代码比较好
以上两个都是Java工程的项目,下面是Android Base库需要依赖的一个路由的实现类库
类结构示意图如下:
详细的代码就不贴了,请有兴趣的看源代码去,有完整的实现!
业务组件内的服务的对外提供
这个功能非常简单,每一个业务组件库都依赖Base库,在Base库中设计一个服务容器
/**
* 服务的容器
*/
public class Service {
private static Map<Class,Object> map = new HashMap();
public static<T> void register(@NonNull Class<T> tClass, T t) {
map.put(tClass, t);
}
@Nullable
public static<T> T unregister(@NonNull Class<T> tClass) {
return (T)map.remove(tClass);
}
@Nullable
public static <T> T get(@NonNull Class<T> tClass) {
return (T) map.get(tClass);
}
}
而要提供出来的服务的接口只要写在Base库中即可,然后当某一个业务组件被加载的时候就可以注册这个服务的实现类来提供服务,这里会牵扯出另一个问题,那就是子路由的注册和服务的注册的代码写在哪里?
我们的业务组件被加载或者卸载的时候并没有一个生命周期的概念,所以这里又要实现另一个很重要的功能,为业务组件提供一个声明周期
业务组件的生命周期实现
这个实现和路由的实现是几乎一模一样的,所以这里就不多说了,大家直接看代码去吧,实现了之后我们就可以这样子做了
在源代码中的Component1业务组件中,我们定义了一个Component1Applicaiton
使用 @ModuleApp() 注解了之后这个类在整个业务组件 Component1 被加载的时候就会走 onCreate 生命周期的方法,反之就会走 onDestory
而里面我们就可以注册我们的路由和服务啦,完工!
@ModuleApp()
public class Component1Application implements IComponentApplication {
@Override
public void onCreate(@NonNull Context app) {
// 注册路由和服务
Router.register(ComponentEnum.Component1.getModuleName());
ServiceContainer.register(Component1Service.class,new Component1ServiceImpl());
}
@Override
public void onDestory() {
// 反注册路由和服务
UiRouter.unregister(ComponentEnum.Component1.getModuleName());
ServiceContainer.unregister(Component1Service.class);
}
}
壳工程加载所有的业务组件
EHiModuleManager.getInstance().register(ComponentEnum.App.getModuleName());
EHiModuleManager.getInstance().register(ComponentEnum.Component1.getModuleName());
EHiModuleManager.getInstance().register(ComponentEnum.Component2.getModuleName());
这样子组件化就全部完工了!
最后放一张图,我可以在壳工程中直接不依赖 Component1 业务模块
也能运行成功,只不过缺失了 Component1 业务模块的功能罢了