组件化开发是当一个项目进入到非常复杂的阶段,需要负责各个模块之间的小组或者个人频繁的沟通与协同开发的时候,原有的单项目开发模式就是非常的掣肘,人员之间的沟通和时间成本都非常的高,为了等待对方的一个功能模块的开发,往往消耗了不少时间。另外,从个人负责的模块角度来讲,自己的开发的模块是可控的,自己只要负责完成自己的模块,并且测试完成,基本就完成了,需要他人的接口的时候,对方暴露出来即可,错误与否不是很重要,此时流程是重要的,当各个模块完成之后,合并成一个App统一进行测试,一是加速了开发进度,二是快速暴露问题,三是确定了开发人员责任,只要自己很自信自己的模块没有问题,那么模块之间的问题最多就是数据问题,很好解决。基于这个思想,很容易想到将应用分成多个模块,各个模块是可以独立进行开发测试的,即分成独立的Application,各个模块之间的数据交互是通过Common模块的中的接口来实现。
如上所讲,组件化的思想就是独立开发,发布时合并成一个项目进行发布,独立开发意味着开发人员之间彼此并不是很熟悉对方的模块代码,但是对对方的某些功能又有很强烈的需求,那么是否可以只知道一些简单的信息就可以了呢?比如调用对方模块的某个方法,只需要类名和方法签名就可以了,或者对方直接暴露出一个统一的接口,自己需要做的就是拿到这个接口,然后调用接口方法就可以了,恩,看起来很不错,这是一个想法,另外,不同模块页面之间的跳转也是比较常见的操作,比如只需要知道目标Activity的路径就可以启动指定页面,也是不错的想法。现实是现有的组件开发也是这种思路,给个路径就可以启动或者获取到目标对象。
启动不属于同一模块下的目标Activity,需要知道Activity的类全名,这样才能启动
Class clazz = Class.forName("com.luckyboy.module.personal.Personal_MainActivity");
Intent intent = new Intent(this, clazz);
startActivity(intent);
这是一种写法,但是不够简练,不一定非要这么长的类名,可以省略些,将这些目标类Class进行管理,如下:
//Application.java
// 管理维护比较麻烦 如果有200 500多个需要管理的 是要每个都要手写加上?
// 利用注解的方式 将这些信息自动的添加到RouterManager中
RouterManager.getInstance().addPath("app", "com.luckyboy.componentarchitect.MainActivity",
MainActivity.class);
RouterManager.getInstance().addPath("product", "com.luckyboy.module.product.Product_MainActivity", Product_MainActivity.class);
RouterManager.getInstance().addPath("personal", "com.luckyboy.module.personal.Personal_MainActivity", Personal_MainActivity.class);
//启动
Class clazz = RouterManager.getInstance().loadClass("personal",
"com.luckyboy.module.personal.Personal_MainActivity");
Intent intent = new Intent(this, clazz);
startActivity(intent);
这种写法也很啰嗦,而且一旦需要启动的Activity比较多的情况下,注册比较麻烦。有没有一种可以自动进行注册并进行管理的机制呢?
只需要类似RouterManager.getInstance().load("/order/Order_MainActivity").navigate();
, 这种就非常简单。
很明显,这种是需要一个Manager对路由进行管理,从而获取到对应的目标Class类对象,进而启动。这些都是自动化的,完成自动化过程有两个点需要考虑,一个是注解,一个是注解处理器。注解是用于标明一个Activity的路径,注解处理器利用注解自动生成代码。
先可以不去考虑这些注解问题,先考虑路由和路由管理。
/**
* 路由组Group对外提供加载数据接口
* // 一对一 "app" "ARouter$$Group$app"
* */
public interface ARouterLoadGroup {
Map<String, Class<? extends ARouterLoadPath>> loadGroup();
}
/**
*
* 路由组Group 对应的详细Path加载数据接口
* 比如: "app"分组有这些信息
*
* key: "/app/MainActivity" , value MainActivity 信息封装到Router对象中
*
* "/app/MainActivity"
* "/app/MainDetailActivity"
* "/app/SecondActivity"
* */
public interface ARouterLoadPath {
Map<String, RouterBean> loadPath();
}
//RouterBean.java
public class RouterBean {
public enum Type {
ACTIVITY,
// 跨模块的业务接口
CALL
}
// 枚举类型
private Type type;
// 类节点
private Element element;
// 被@ARouter注解的类对象
private Class<?> clazz;
// 路由的组名
private String group;
// 路由地址
private String path;
private RouterBean(Builder builder) {
this.group = builder.group;
this.path = builder.path;
this.element = builder.element;
}
private RouterBean(Type type, Class<?> clazz, String path, String group) {
this.type = type;
this.clazz = clazz;
this.path = path;
this.group = group;
}
public void setType(Type type) {
this.type = type;
}
public void setGroup(String group) {
this.group = group;
}
public String getGroup() {
return group;
}
public void setPath(String path) {
this.path = path;
}
public String getPath() {
return path;
}
public void setElement(Element element) {
this.element = element;
}
public Element getElement() {
return element;
}
public Type getType() {
return type;
}
public void setClazz(Class<?> clazz) {
this.clazz = clazz;
}
public Class<?> getClazz() {
return clazz;
}
public static RouterBean create(Type type, Class<?> clazz, String path, String group) {
return new RouterBean(type, clazz, path, group);
}
public final static class Builder {
// 类节点
private Element element;
// 路由的组名
private String group;
// 路由地址
private String path;
public Builder setElement(Element element) {
this.element = element;
return this;
}
public Builder setGroup(String group) {
this.group = group;
return this;
}
public Builder setPath(String path) {
this.path = path;
return this;
}
public RouterBean build() {
if (path == null || path.length() == 0) {
throw new IllegalArgumentException("path必填项为空 如:/app/MainActivity");
}
return new RouterBean(this);
}
}
@Override
public String toString() {
return "path " + path;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (!(o instanceof RouterBean)) {
return false;
}
RouterBean routerBean = (RouterBean) o;
return this.type == routerBean.type && this.path.equalsIgnoreCase(routerBean.path)
&& this.group.equalsIgnoreCase(routerBean.path);
}
}
从上面的定义可以看出一些情况,ARouterLoadGroup是用于管理模块,例如order,ARouterLoadPath是用于管理路径,例如/order/Order_MainActivity,路径包含目标类Class对象。如果从"/order/Order_MainActivity"中截取order,那么如果存在一个类ARouter$Group$order,并且实现这个ARouterLoadGroup接口,那么就可以获取到该模块下的ARouter#Path$order的Class对象, 利用该对象通过path又可以获取到RouterBean,那么就可以获取到目标类了,当然这些类都是自动生成的。整体的两个类如下:
public class ARouter$$Group$$order implements ARouterLoadGroup {
@Override
public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
groupMap.put("order", ARouter$$Path$$order.class);
return groupMap;
}
}
public class ARouter$$Path$$order implements ARouterLoadPath {
@Override
public Map<String, RouterBean> loadPath() {
Map<String, RouterBean> pathMap = new HashMap<>();
pathMap.put("/order/Order_MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY,Order_MainActivity.class, "/order/Order_MainActivity", "order"));
return pathMap;
}
}
有了上面的两个类的存在,是可以通过路由管理器来查找的目标类的。
//RouterManager.java
// 加载APT路由组Group类文件 ARouter$$Group$$order
String packageName = BuildConfig.packageNameForAPT;
// packageName + GROUP_FILE_PREFIX_NAME 是固定的 com.luckyboy.module.apt.ARouter$$Group$$
// 所以加上group正好是Group的类全名
String groupClassName = packageName + GROUP_FILE_PREFIX_NAME + group;
// 获取Group的Class对象
Class<?> clazz = Class.forName(groupClassName);
// 初始化类文件
aRouterLoadGroup = (ARouterLoadGroup) clazz.newInstance();
//
// 通过组Group 加载接口 获取Path加载接口
Class<? extends ARouterLoadPath> clazz = aRouterLoadGroup.loadGroup().get(group);
// 初始化类文件
if (clazz != null) {
aRouterLoadPath = clazz.newInstance();
}
...
RouterBean routerBean = aRouterLoadPath.loadPath().get(path);
Intent intent = new Intent(context, routerBean.getClazz());
((Activity) context).startActivity(intent, bundleManager.getBundle());
...
以上就是关于跳转方面的实现,另外还有一个问题需要注意,就是页面Activity的成员变量的初始化,比如用一个注解 注解一个成员变量,该变量就可以默认初始化。
//MainActivity.java
@Parameter(name = "name")
String name;
@Parameter(name = "age")
int age;
@Parameter(name = "/order/getOrder")
OrderService orderService;
这些变量的初始化需要一个自动生成类来专门处理,类似如下
public class MainActivity$$Parameter implements ParameterLoad {
@Override
public void loadParameter(Object target) {
MainActivity t = (MainActivity)target;
t.name = t.getIntent().getStringExtra("name");
t.age = t.getIntent().getIntExtra("age",t.age);
// 利用路由导航获取OrderService
t.orderService = (OrderService) RouterManager.getInstance().build("/order/getOrder").navigation(t);
}
}
public class ARouter$$Path$$order implements ARouterLoadPath {
@Override
public Map<String, RouterBean> loadPath() {
Map<String, RouterBean> pathMap = new HashMap<>();
//@ARouter作用与哪种类是没有区别的的,最终都是获取到指定的Class, 区别在于Type.Call类型的用于生成普通的类对象,
//Type.ACTIVITY类型的用于Activity
pathMap.put("/order/getOrder", RouterBean.create(RouterBean.Type.CALL,OrderServiceImpl.class, "/order/getOrder", "order"));
pathMap.put("/order/Order_MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY,Order_MainActivity.class, "/order/Order_MainActivity", "order"));
return pathMap;
}
}
/**
* 参数Parameter 加载管理器
*/
public class ParameterManager {
private static ParameterManager instance;
// LRU 缓存 key:类名 value:参数Parameter加载接口
private LruCache<String, ParameterLoad> cache;
// APT 生成的获取参数类文件 后缀名
private static final String FILE_SUFFIX_NAME = "$$Parameter";
public static ParameterManager getInstance() {
if (instance == null) {
synchronized (ParameterManager.class) {
if (instance == null) {
instance = new ParameterManager();
}
}
}
return instance;
}
private ParameterManager() {
// 初始化 cache 并赋条目最大值
cache = new LruCache<>(200);
}
public void loadParameter(@NonNull Activity activity) {
String className = activity.getClass().getName();
ParameterLoad iParameter = cache.get(className);
try {
if (iParameter == null) {
Class<?> clazz = Class.forName(className + FILE_SUFFIX_NAME);
iParameter = (ParameterLoad) clazz.newInstance();
cache.put(className, iParameter);
}
iParameter.loadParameter(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在MainActivity中使用
//MainActivity.java
@ARouter(group = "app", path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ParameterManager.getInstance().loadParameter(this);
}
....
}