文章目录
  1. 1. ToyBricks背景
  2. 2. ToyBricks简介
    1. 2.1. ToyBricks简介
  3. 3. ToyBricks原理分析
    1. 3.1. Android Library(最终可能打包成jar,aar,并发布到maven库)
    2. 3.2. Android Application
  4. 4. 总结
  5. 5. 参考资料:
  6. 6. 联系方式

ToyBricks背景

我始终认为,在高内聚,低耦合的原则下,进行组件化,模块化,插件化都是移动应用开发的趋势。

为什么这么说呢?下面我们举个栗子:
大家都知道,以前Android应用开发中,可以使用HttpClient或者HttpUrlConnection来进行http访问。这里假设有一个耦合严重,但代码量巨大的项目,使用了基于HttpClient封装的loopj/android-async-http来进行http访问。但是,后来,Google明确支持使用HttpUrlConnection。此时,经过调研,你们觉得square/okhttp基于HttpUrlConnection,符合你们的要求。

现在,不管是否将HttpClient替换成okhttp,你们都可能面临以下困境:

  1. 需求都做不完,根本没有排期做这个替换。于是,你们面临离google的支持越来越远,离风险越来越近的困境;
  2. 辛辛苦苦耗费人力将HttpClient替换成okhttp。但由于两者变化很大,需要投入很多测试资源,来重新确认这些接口是否正常访问。一旦出现问题,还需要安排研发资源,去一一排查。
  3. 替换的工作量太大。替换一部分之后,发现没有足够人力去继续完成。于是,替换终止。整个工程又变得混乱和臃肿,同时包含了两种http的封装库和调用。

模块化可以有效解决这些问题。通用的做法,是按照业务,功能等将整个项目分成不同的模块,由不同的研发测试小组负责。
每个模块又分为接口和实现两个部分。接口部分提供给模块外部调用,而实现部分则禁止来自外部的调用。

[apt]

那么,如何将模块的接口和实现部分关联起来呢?通过APT工具,可以轻松地将接口部分和实现部分关联起来。

APT,即Annotation Processing Tool,可以理解为“编译时注解处理器工具”。

官方说明:
“The apt tool is a command-line utility for annotation processing. It includes a set of reflective APIs and supporting infrastructure to process program annotations (JSR 175). These reflective APIs provide a build-time, source-based, read-only view of program structure. They are designed to cleanly model the Java programming language’s type system after the addition of generics (JSR 14).”

简单理解如下:
apt工具是javac工具的一部分。在编译时,apt工具首先会扫描工程下Java源码中的编译时注解,再根据预先定义的编译时注解处理工具,生成指定的Java源码文件。紧接着,生成的Java源码文件和之前项目下的Java源码一起,由javac工具来编译成class。

但是,APT有一个局限性,就是只会扫描Java源码,不会扫描jar ,aar 和class 。也就是说,所有模块需要以源码形式存在。而现在通用的做法是,将模块打包成jar或者aar,发布到Maven库,再由其他模块自行引用。

有没有办法将APT的这种功能和特性延伸到jar和aar呢?
于是,ToyBricks应运而生。

ToyBricks简介

ToyBricks简介

ToyBricks是一个Android项目模块化的解决方案,主要包括四个部分,APT注解,APT注解处理器,ToyBricks插件(Gradle Plugin),ToyBricks库。

[ToyBricks]

其中:

  1. APT注解,主要定义了两个注解:Interface(接口,例如:IText),Implementation (实现,例如:TextImpl)
  2. APT注解处理器,在javac编译java源码之前。APT注解处理器会扫描Java源码中带有上面两个注解的接口和类,并且生成一个json文件, ToyBricks.json.
  3. ToyBricks插件(Gradle Plugin),负责ToyBricks.json的打包,合并,生成Java源文件等工作
  4. ToyBricks,提供对外调用方法。通过参数传入接口,返回相应的实现。

ToyBricks原理分析

下面以接口IText和实现TextImpl为例,简单介绍下ToyBricks原理。

主要分为两个部分:

Android Library(最终可能打包成jar,aar,并发布到maven库)

如果工程是Android库模块,则主要流程如下:
1.在javac编译java源码之前,由APT注解处理器扫描Java源码中带有上面两个注解的接口和类,生成ToyBricks.json。

1
2
3
4
5
6
7
8
9
10
{
"interfaceList" : [ "com.github.snowdream.toybricks.app.IText" ],
"globalImplementation" : {
"com.github.snowdream.toybricks.app.IText" : "com.github.snowdream.toybricks.app.impl.NewTextGobalImpl"
},
"defaultImplementation" : {
"com.github.snowdream.toybricks.app.IText" : "com.github.snowdream.toybricks.app.impl.TextImpl"
},
"singletonImplementation" : [ "com.github.snowdream.toybricks.app.impl.NewTextGobalImpl" ]
}

2.在打包jar,aar的时候,由ToyBricks插件(Gradle Plugin)提前处理,保证ToyBricks.json能被拷贝进去jar包或者aar包的根目录下,并随同一起分布到maven仓库。

Android Application

如果工程是Android应用模块,则主要流程如下:

  1. 第一步,和Android Library第一步一致。
  2. Javac编译Java源代码
  3. 扫描所有依赖的库文件,过滤出所有包含ToyBricks.json文件的jar包或者aar包,并且提取出来。提取完毕后,合并所有的ToyBricks.json文件,成为一个ToyBricks.json。
  4. 将最终的ToyBricks.json按照预定规则生成一个Java源码文件.文件名为: InterfaceLoaderImpl.java
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.github.snowdream.toybricks.annotation.impl;
import com.github.snowdream.toybricks.annotation.InterfaceLoader;
import java.lang.Class;
import java.lang.Override;
import java.util.HashMap;
/**
*
* Created by snowdream
*
* This file is automatically generated by apt(Annotation Processing Tool)
* Do not modify this file -- YOUR CHANGES WILL BE ERASED!
*
* This file should *NOT* be checked into Version Control Systems,
* as it contains information specific to your local configuration.
*/
final class InterfaceLoaderImpl implements InterfaceLoader {
private static HashMap<Class, Object> sSingletonMap = new HashMap<Class, Object>();
private static HashMap<Class, Class> sGlobalMap = new HashMap<Class, Class>();
private static HashMap<Class, Class> sDefaultMap = new HashMap<Class, Class>();
public InterfaceLoaderImpl() {
addGlobalMap();
addDefaultMap();
addSingletonMap();
}
private void addGlobalMap() {
sGlobalMap.put(com.github.snowdream.toybricks.app.IText.class,com.github.snowdream.toybricks.app.impl.NewTextGobalImpl.class);
}
private void addDefaultMap() {
sDefaultMap.put(com.github.snowdream.toybricks.app.IText.class,com.github.snowdream.toybricks.app.impl.TextImpl.class);
}
private void addSingletonMap() {
sSingletonMap.put(com.github.snowdream.toybricks.app.impl.NewTextGobalImpl.class,null);
}
@Override
public <T> T getImplementation(Class<T> clazz) {
T implementation = null;
boolean isSingleton = false;
Class implClass;
implClass = sGlobalMap.get(clazz);
if (implClass == null) {
implClass = sDefaultMap.get(clazz);
}
if (implClass != null) {
isSingleton = sSingletonMap.containsKey(implClass);
if (isSingleton) {
implementation = (T) sSingletonMap.get(implClass);
if (implementation != null) {
return implementation;
}
}
try {
implementation = (T) implClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (isSingleton && implementation != null) {
sSingletonMap.put(implClass, implementation);
}
}
return implementation;
}
}

5.再次使用Javac工具编译InterfaceLoaderImpl.java文件。
6.这个文件就类似字典索引,通过这个文件,就可以通过传入接口,来查找对应的实现类。

总结

与APT工具相比,ToyBricks能够将接口和实现之间的关系进行持久化,存储在jar和aar中,并随之发布到Maven仓库,实现接口和实现的彻底分离。

如果您对ToyBricks有什么问题或者建议,欢迎通过后面的联系方式联系我。

参考资料:

  1. SnowdreamFramework/ToyBricks
  2. Annotation Processing Tool (apt)
  3. ANNOTATION PROCESSING 101
  4. Annotation-Processing-Tool详解
  5. Java注解处理器
  6. 什么是高内聚、低耦合?

联系方式

sn0wdr1am

Fork me on GitHub