ARouter
MoMo Lv5

路由系统 ——— 给 无依赖的双方 提供 通信和路由的能力

ARouter

ARouter原理

使用ARouter在进行Activity跳转非常简单:初始化ARouter、添加注解@Route、发起路由。

1
2
3
// 在module app中
//1.初始化SDK
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
1
2
3
4
5
6
7
// moduleA
// 2.在支持路由的页面上添加注解(必选)
// 路径注意至少需有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
1
2
3
// moduleB(没有依赖 moduleA)
// 3.发起路由跳转
ARouter.getInstance().build("/test/activity").navigation();

这样就使得没有依赖moduleAmoduleB能跳转到moduleAActivity

构建PostCard

想要跳转Activity最终必定是走到了 startActivity(intent)方法,而intent是一般需要目标ActivityClass

ARouter.getInstance():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static ARouter getInstance() {
if (!hasInit) { // 未初始化则报异常
throw new InitException("ARouter::Init::Invoke init(context) first!");
} else {
if (instance == null) {
synchronized (ARouter.class) {
if (instance == null) {
instance = new ARouter();
}
}
}
return instance;
}
}

获取ARouter单实例,没有初始化则报异常。

build(string):

1
2
3
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}

这里是调用了 _ARouter 的同名方法,返回了 Postcard

ARouter实际是使用了外观模式,其所有方法都是调用了_ARouter的同名方法。

_ARouter:

1
2
3
4
5
6
7
8
9
10
11
12
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) { //path不能为空
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
//path替换,这是预处理
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path), true);
}
}

这里对path做了空校验和预处理替换。如果想重写path,可以写一个PathReplaceService实现类。接着又走到重载方法:

1
2
3
4
5
6
7
8
protected Postcard build(String path, String group, Boolean afterReplace) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
...
return new Postcard(path, group);
}
}

其中参数group是通过extractGroup(path)获取,也就是path的第一级,即”/test/activity”中的”test”。group的作用是作为路由的默认分组。

路由中的分组概念:

  • SDK中针对所有的路径(/test/1、/test/2)进行分组,分组只有在分组中的某一个路径第一次被访问的时候,该分组才会被初始化
  • 可以通过 @Route 注解主动指定分组,否则使用路径中第一段字符串(/*/)作为分组
  • 一旦主动指定分组之后,应用内路由需要使用 ARouter.getInstance().build(path, group) 进行跳转,手动指定分组,否则无法找到@Route(path = "/test/1", group = "app")

最后返回创建的Postcard实例。承载了一次跳转/路由 需要的所有信息,它继承自路由元信息 RouteMeta:

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
public final class Postcard extends RouteMeta {

// Base
private Uri uri; //使用Uri方式发起的路由
private Object tag; // A tag prepare for some thing wrong. inner params, DO NOT USE!
private Bundle mBundle; //启动activity的传入的Bundle
private int flags = 0; // 启动activity的Flags
private int timeout = 300; // 路由超时时间
private IProvider provider; // 如果是获取provider,就有值
private boolean greenChannel; //是绿色通道
private SerializationService serializationService;
private Context context; //context
private String action; //启动activity的action

// activity转场动画相关
private Bundle optionsCompat;
private int enterAnim = -1;
private int exitAnim = -1;

public Postcard(String path, String group) {
this(path, group, null, null);
}

public Postcard(String path, String group, Uri uri, Bundle bundle) {
setPath(path);
setGroup(group);
setUri(uri);
this.mBundle = (null == bundle ? new Bundle() : bundle);
}
...
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RouteMeta {
private RouteType type; // 路由类型;activity、service、fragment、IProvider等,编译时会根据被@Route注解的类的类型来设置
private Element rawType; // 路由原始类型,在编译时用来判断
private Class<?> destination; // 目的地:具体的 XxxActivity.class等
private String path; // Path
private String group; // Group
private int priority = -1; // 优先级,越小优先级越高
private int extra; // Extra
private Map<String, Integer> paramsType; // 参数类型,例如activity中使用@Autowired的参数类型
private String name; //路由名字,用于生成javadoc
private Map<String, Autowired> injectConfig; // 参数配置(对应paramsType).
}

Postcard: 路由的信息。继承自RouteMeta。

RouteMeta: 路由元信息,即基础信息。

路由过程

整体步骤

通过path构建了PostCard后调用了其navigation()方法,也就是开始了路由过程:

1
2
3
4
5
6
7
8
9
public Object navigation() {
return navigation(null);
}
public Object navigation(Context context) {
return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}

连续走了两个重载方法,最后走到ARouter的navigation方法,并且把自己传了进去。ARouter的navigation方法同样会走到_ARouter的同名方法:

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
 // @param context     Activity or null.
// @param postcard Route metas
// @param requestCode RequestCode,startActivity的requestCode
// @param callback cb,路由回调:找到目的地、未找到、中断、到达
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
//若有PretreatmentService的实现,就进行预处理。可以在真正路由前进行一些判断然后中断路由。
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
// 预处理失返回false,路由取消.
return null;
}

// 给路由设置context,例如启动activity需要。如果没有传就使用mContext,即初始化ARouter时传入的Application
postcard.setContext(null == context ? mContext : context);

try {
// 1. 完善postcard信息(目前只有path、group,还需要知道具体目的地,例如要跳转到的activity信息)
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
//这里就是debug包中,没找到路由目的地时 经常出现的提示
if (debuggable()) {
runInMainThread(new Runnable() {
public void run() {
Toast.makeText(mContext, "There's no route matched!\n" +" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
}
});
}
//没找到的回调
if (null != callback) {
callback.onLost(postcard);
} else {
// 没有callback的话, (如果有)就回调到降低服务
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
//找到的回调
if (null != callback) {
callback.onFound(postcard);
}
// 2. 不是绿色通道的话,要先走拦截器
if (!postcard.isGreenChannel()) {
interceptorService.doInterceptions(postcard, new InterceptorCallback() {

//拦截器处理结果:继续路由
@Override
public void onContinue(Postcard postcard) {
// 3. 获取路由结果
_navigation(postcard, requestCode, callback);
}

//拦截器处理结果:中断路由,回调中断
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
}
});
} else {
//绿色通道,不走拦截器,就获取路由结果
return _navigation(postcard, requestCode, callback);
}

return null;
}

使用前面构建好的PastCard经过整体3个步骤,就完成了路由:

  1. 完善postcard信息:只有path、group还不行,还需要知道具体目的地,例如要跳转到的Activity信息。
  2. 拦截器处理:不是绿色通道的话,要先经过路由器的处理,结果是中断或者继续。
  3. 获取路由结果:例如进行目标Activity的跳转、获取IProvider实现对象、获取Fragment等。

获取路由结果

路由结果获取过程,也就是**_navigation()**方法:

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
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext();
//根据路由类型来走对应逻辑
switch (postcard.getType()) {
case ACTIVITY:
// Activity, 使用postcard.getDestination()来构建intent、传入Extras、设置 flags、action
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
int flags = postcard.getFlags();
if (0 != flags) {
intent.setFlags(flags);
}
// 当前的context不是activity,需要添加flag:FLAG_ACTIVITY_NEW_TASK
//(启动startActivity需有任务栈的,application/service启动activity没有任务栈,所以必须要new_task_flag新建一个任务栈)
if (!(currentContext instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}

String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}

// 最后在主线程执行 熟悉的startActivity,
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
break;
case PROVIDER:
//provider,指的是想要获取的服务,即IProvider的实现类。直接从postCard中获取。
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
//Broadcast、ContentProvider、Fragment,都是使用postcard.getDestination()反射创建实例
Class<?> fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
return instance;
} ...
}
return null;
}

从上面可见,postcard 经过完善后,路由类型type、目的地destination等都已经被赋了值。 destination就是目标类的class对象。

方法内容就是根据 路由类型来走对应逻辑:

  • Activity, 使用postcard.getDestination()来构建intent、传入Extras、设置 flags、action,最后在主线程执行 熟悉的startActivity
  • provider,指的是想要获取的服务,即IProvider的实现类。是直接从postCard中获取的,因为服务类是单例,只会在首次获取时创建
  • Broadcast、ContentProvider、Fragment,都是使用postcard.getDestination()反射创建实例

拦截器

拦截器模式是开发中常用设计模式之一,路由中也可以设置拦截器,对路径进行判断决定是否需要中断。

未设置绿色通道的路由需要经过拦截器处理,也就是interceptorServicedoInterceptions() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final class ARouter {
...

private static InterceptorService interceptorService;
...

public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
}
}
...
}
1
2
3
4
5
6
static void afterInit() {
interceptorService = (InterceptorService) ARouter
.getInstance()
.build("/arouter/service/interceptor")
.navigation();
}

InterceptorService继承IProvider,可见interceptorService也是一个服务,在 ARouter初始化后 获取,用于处理拦截器的逻辑。具体的实现类是InterceptorServiceImpl

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
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
...
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
//有拦截器
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
...
LogisticsCenter.executor.execute(new Runnable() { //放入线程池异步执行
@Override
public void run() { //interceptorCounter 用于保证所有拦截器都走完,并且设置了超时
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {//执行第一个拦截器,如果没有中断 则递归调用继续后面的拦截器
_execute(0, interceptorCounter, postcard);
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
if (interceptorCounter.getCount() > 0) { // count>0说明超时了,拦截器还没处理完.
callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
} else if (null != postcard.getTag()) { //Tag!=null说明被某个拦截器回调中断了
callback.onInterrupt((Throwable) postcard.getTag());
} else {
callback.onContinue(postcard); // 所有拦截器处理完、没超时、也没异常,则继续路由
}
}...
}
});
} else {
//没有拦截器则继续路由
callback.onContinue(postcard);
}
}

private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
//从Warehouse.interceptors中获取第index个拦截器,走process方法,如果回调到onContinue就继续下一个;
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
counter.countDown();
_execute(index + 1, counter, postcard); // 继续下一个
}
@Override
public void onInterrupt(Throwable exception) {
postcard.setTag(null == exception ? new HandlerException("No message.") : exception); // save the exception message for backup.
counter.cancel();
...
}
});
}
}

@Override //此init方法会在服务被创建后调用。这里就是反射创建所有的拦截器实例
public void init(final Context context) {
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
//遍历Warehouse.interceptorsIndex ,使用存储与其中的拦截器class对象反射创建拦截器实例
for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
Class<? extends IInterceptor> interceptorClass = entry.getValue();
try {
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
iInterceptor.init(context);
//存入 Warehouse.interceptors
Warehouse.interceptors.add(iInterceptor);
}...
}
interceptorHasInit = true;
...
}
}
});
}...
}

doInterceptions()方法中判断如果有拦截器,就放入线程池异步执行第一个拦截器,且使用interceptorCounter 保证所有拦截器都走完,同时也设置了超时。如果第一个拦截器没有回调中断 则递归调用继续后面的拦截器。

拦截器的执行,是从Warehouse.interceptors中获取第index个拦截器,走process方法,如果回调到onContinue就继续下一个;若回调onInterrupt就中断路由。

那么拦截器是怎么获取的呢:InterceptorServiceImpl的 init方法:init()方法会在服务被创建后立即调用,如上所示就是遍历Warehouse.interceptorsIndex ,使用存储在其中的拦截器class对象 反射创建拦截器实例,然后存在存入 Warehouse.interceptors。 也即是说, ARouter初始化完成后就获取到了所有拦截器实例

那么Warehous是什么?interceptorsIndex是如何存储的所有拦截器的class的?

路由元信息的收集

Warehouse意为仓库,用于存放被 @Route、@Interceptor注释的 路由相关的信息,也就是我们关注的destination等信息。既然是仓库,那么就是有存有取

前面举的例子:moduleB发起路由跳转到moduleA的activity,moduleB没有依赖moduleA,只是在moduleA的activity上增加了@Route注解。 由于进行activity跳转需要目标Activity的class对象来构建intent,所以必须有一个 中间人,把路径”/test/activity”翻译成Activity的class对象,然后moduleB才能实现跳转。(因此在ARouter的使用中 moduleA、moduleB 都是需要依赖 arouter-api的)

这个中间人那就是ARouter了,而这个翻译工所作用到的词典就是 Warehouse,它存着所有路由信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Warehouse {
//所有IRouteGroup实现类的class对象,是在ARouter初始化中赋值,key是path第一级
//(IRouteGroup实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
//所有路由元信息,是在completion中赋值,key是path
//首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务
static Map<String, RouteMeta> routes = new HashMap<>();

//所有服务provider实例,在completion中赋值,key是IProvider实现类的class
static Map<Class, IProvider> providers = new HashMap<>();
//所有provider服务的元信息(实现类的class对象),是在ARouter初始化中赋值,key是IProvider实现类的全类名。
//主要用于使用IProvider实现类的class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
static Map<String, RouteMeta> providersIndex = new HashMap<>();

//所有拦截器实现类的class对象,是在ARouter初始化时收集到,key是优先级
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("...");
//所有拦截器实例,是在ARouter初始化完成后立即创建
static List<IInterceptor> interceptors = new ArrayList<>();
...
}

Warehouse存了哪些信息呢?

  • groupsIndex所有路由组元信息。是所有IRouteGroup实现类的class对象,是在ARouter初始化中赋值,key是path第一级。IRouteGroup实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)。
  • routes所有路由元信息。是在LogisticsCenter.completion中赋值,key是path。首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务
  • providers所有服务provider实例。在LogisticsCenter.completion中赋值,key是IProvider实现类的class
  • providersIndex所有provider服务元信息(实现类的class对象)。是在ARouter初始化中赋值,key是IProvider实现类的全类名。 用于使用IProvider实现类class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
  • interceptorsIndex所有拦截器实现类class对象。是在ARouter初始化时收集到,key是优先级
  • interceptors所有拦截器实例。是在ARouter初始化完成后立即创建

其中groupsIndex、providersIndex、interceptorsIndex是ARouter初始化时就准备好的基础信息,为业务中随时发起路由操作(Activity跳转、服务获取、拦截器处理)做好准备。

那么 Warehouse的信息是如何收集到的呢?

1
2
3
4
5
6
7
8
9
10
final class _ARouter {
...

protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
...

return true;
}

_ARouter的init方法中 调用了LogisticsCenter的init方法。 LogisticsCenter意为物流中心,上面提到的完善postcard的completion操作也是此类提供。

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
//LogisticsCenter.java
//LogisticsCenter初始化,加载所有的路由元信息
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {...
try {
long startInit = System.currentTimeMillis();
//先尝试使用AGP transform 收集 根帮助类 后 写好的注入代码(要先引入插件才行 apply plugin: 'com.alibaba.arouter')
loadRouterMap();
if (registerByPlugin) {
//registerByPlugin为true说明使用AGP加载ok了(通常都会用AGP,即registerByPlugin为true)
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
//若没有使用 AGP transform,就用ClassUtils.getFileNameByPackageName来搜集dex中ROUTE_ROOT_PAKCAGE包下的所有类,即编译时生成的所有帮助类
//这样的话,就是运行时 遍历搜集 会比较耗时,也就是init会较为耗时;而AGP transform 是在编译时完成收集的。
//当前app是新安装时才会走(收集到的帮助类会缓存到SP文件)
Set<String> routerMap;
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// 这写帮助类是在编译时由arouter-compiler生成
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context);
} else {
//不是新安装的版本,就从SP文件中读取
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}...
//遍历帮助类,区分是哪种帮助类,然后反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
//类名开头:com.alibaba.android.arouter.routes.ARouter$$Root
//填充Warehouse.groupsIndex,即所有IRouteGroup实现类的class对象
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
//类名开头:com.alibaba.android.arouter.routes.ARouter$$Interceptors
//填充Warehouse.interceptorsIndex,即所有IInterceptor实现类的class对象
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
//类名开头:com.alibaba.android.arouter.routes.ARouter$$Providers
//填充Warehouse.providersIndex,即所有provider的RouteMeta
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}...
}
}

LogisticsCenter初始化 就是加载所有的路由 元信息的过程,有两种方式:

  1. 走loadRouterMap()方法:直接使用在编译时收集好的帮助类信息,然后反射创建 帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。这需要开发者要先引入插件才行 apply plugin: ‘com.alibaba.arouter’。如果加载成功,registerByPlugin这个值就为true,否则false。

  2. 若第一步没有加载成功,即registerByPlugin为false:就会使用ClassUtils.getFileNameByPackageName在运行时搜集dex中” com.alibaba.android.arouter.routes“包下的所有类(即帮助类),然后遍历,区分是哪种帮助类。接着反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。

两种方式对比:

  • 相同点:都是使用帮助类信息反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。
  • 不同点:在于帮助类信息的收集方式。前者是在编译时搜集就完毕了,后者是运行时。

一般都是使用第一种,因为 运行时遍历dex搜集会比较耗时,而第一种在编译时已经收集好了

先看两个问题:

  • 上面提到的帮助类是个啥? 如何使用帮助类填充Warehouse的?
  • 为啥帮助类还要收集?还分 编译时收集、运行时收集?

拦截器元信息

我们先来看拦截器元信息(拦截器class信息)是如何通过帮助类填充的:

image

上图是ARouter工程编译后module-java的build目录, ARouter$$ 开头的这些类都是在ARouter在编译过程中生成,它们就是所谓的帮助类:

ARouter$$Interceptors$$modulejava 这个类就是一个帮助类,帮助WareHouse填充WareHouse.interceptorsIndex。它实现接口IInterceptorGroup,loadInfo方法接受一个 Map<integer, class<? extends iinterceptor>></integer,>,也就是WareHouse.interceptorsIndex的类型。 loadInfo方法体内,是用接收的map来put当前module所有拦截器的class,即使用 @Interceptor 注解并实现 IInterceptor 接口的类。

在上面LogisticsCenter的init方法中第二种加载方式中看到,确实是遍历收集到的帮助类,然后使用类名判断是 ARouter$$Interceptors$$modulejava,接着就调用loadInfo方法,这就实现了对WareHouse.interceptorsIndex的赋值。 也就是说,有了 ARouter$$Interceptors$$modulejava ,我们就能在ARouter初始化时对WareHouse.interceptorsIndex进行赋值,就为创建所有拦截器实例做好了准备。

ARouter$$Interceptors$$modulejava的loadInfo方法中 拦截器实现类class是如何获取的呢?—— 编译时对注解 @Interceptor 的解析,解析过程将在下篇中介绍。

路由组元信息

路由组元信息的收集是通过 —— ARouter$$Root$$xxx —— 根帮助类:即用来帮助对 WareHouse.groupsIndex 赋值。这样就会把path第一级相同的所以路由分到同一个组中。 一个module对应一个根帮助类。xxx是module名,就是在build.gradle中配置的 AROUTER_MODULE_NAME 。

image

如上图, ARouter$$Root$$modulejava 就是根帮助类,帮助WareHouse填充 Warehouse.groupsIndex。实现自 IRouteRoot接口,loadInfo方法接受一个 Map<string, class<? extends iroutegroup>></string,>,也就是WareHouse.groupsIndex 的类型。 loadInfo方法体内,是用接收的map来put当前module所有路由组帮助类的class

在上面LogisticsCenter的init方法中同样 对遍历收集到的帮助类判断类名,接着就调用loadInfo方法,这就实现了对WareHouse.groupsIndex 的赋值。

根帮助类,目的就是对路由进行分组,分组的好处是避免一次性加载所有路由,减少反射耗时和内存占用的性能问题。

根帮助类也是在编译时生成

路由元信息

路由元信息的收集是通过 —— ARouter$$Group$$xxx —— 组帮助类:即用来帮助对 WareHouse.routes 赋值。也就是把同组的路由put到WareHouse.routes。 一个module可能有多个组,即对应有多个根帮助类,xxx是组名,即path第一级。

image

如上图, ARouter$$Group$$test 就是组帮助类,帮助WareHouse填充 Warehouse.routes。实现自 IRouteGroup接口,loadInfo方法接受一个 Map<string, routemeta></string,>,也就是WareHouse.routes 的类型。 loadInfo方法体内,是用接收的map来put 当前组 的所有路由元信息其中最重要的就是 每个路由的目标类class

在上面LogisticsCenter的init方法中 没有看到对组帮助类的处理。ARouter的设计是:在使用时才进行加载,即首次使用某个组的路由时,才会使用组帮助类对 WareHouse.routes 进行填充。

组帮助类,目的就是 首次使用时 一次性加载本组所有路由元信息。这比较符合实际使用场景:一般同组的路由都是同业务的内容,当前用户进入此业务时,就把本组路由元信息准备好,是比较合理的。

组帮助类也是在编译时生成。

provider元信息

provider元信息 其实在上面 路由元信息 中已经包含了,为啥还要单独拎出来呢?我们回头看下 _ARouter的navigation方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected  T navigation(Class service) {
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
if (null == postcard) {
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}
if (null == postcard) {
return null;
}
postcard.setContext(mContext);
LogisticsCenter.completion(postcard);
return (T) postcard.getProvider();
...

}

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
...

}

前面介绍了4个参数的方法,而上面这个传服务class的重载方法 就是单独给获取provider服务使用的。看到通过LogisticsCenter使用服务类name获取到了PostCard,然后经过完善PostCard,直接获取provider服务。

1
2
3
4
5
6
7
8
9
10
//LogisticsCenter.java
public static Postcard buildProvider(String serviceName) {
RouteMeta meta = Warehouse.providersIndex.get(serviceName);
if (null == meta) {
return null;
} else {
return new Postcard(meta.getPath(), meta.getGroup());
}
}

其中Postcard其实就是从 Warehouse.providersIndex 中获取到的RouteMeta后构建的。而Warehouse.providersIndex的赋值就是通过 ——ARouter$$Providers$$xxx —— Provider帮助类

image

帮助类是用来帮助填充WareHouse中的元数据的。

AGP方式加载路由

路由信息收集的第一种方式,这是一般都会使用到的方式,也就是loadRouterMap()方法:

1
2
3
4
5
6
7
8
9
10
public class LogisticsCenter {
private static boolean registerByPlugin;

private static void loadRouterMap() {
registerByPlugin = false;
//主动注册插件 会在此处插入代码。调用此方法就注册了全部的 Routers、Interceptors、Provider

}
...

反编译 ARouter demo APK后,查看LogisticsCenter:

image

看到编译后的loadRouterMap()方法,多了几行register()方法的调用,而参数就是 所有的根帮助类、拦截器帮助类、provider帮助类。

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
...

//LogisticsCenter.java
...
private static void register(String className) {
if (!TextUtils.isEmpty(className)) {
try {
Class<?> clazz = Class.forName(className);
Object obj = clazz.getConstructor().newInstance();
if (obj instanceof IRouteRoot) {
registerRouteRoot((IRouteRoot) obj);
} else if (obj instanceof IProviderGroup) {
registerProvider((IProviderGroup) obj);
} else if (obj instanceof IInterceptorGroup) {
registerInterceptor((IInterceptorGroup) obj);
} ...
} ...
}
}
private static void registerRouteRoot(IRouteRoot routeRoot) {
markRegisteredByPlugin();
if (routeRoot != null) {
routeRoot.loadInto(Warehouse.groupsIndex);
}
}
private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
markRegisteredByPlugin();
if (interceptorGroup != null) {
interceptorGroup.loadInto(Warehouse.interceptorsIndex);
}
}
private static void registerProvider(IProviderGroup providerGroup) {
markRegisteredByPlugin();
if (providerGroup != null) {
providerGroup.loadInto(Warehouse.providersIndex);
}
}
private static void markRegisteredByPlugin() {
if (!registerByPlugin) {
registerByPlugin = true; //标记通过AGP加载成功了
}
}

register()方法很简单,就是反射创建帮助类实例,调用loadInto方法对Warehouse进行填充,和第二种路由信息收集方式的是一致的。 而第二种是在运行时遍历dex才找到的帮助类

路由信息的完善

路由元信息是如何使用的

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
//LogisticsCenter.java
public synchronized static void completion(Postcard postcard) {
//完善postcard信息(目前只有path、group,还需要知道具体目的地,例如要跳转到的activity信息)
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
//没有从Warehouse.routes获取到:要么不存在、要么还没有加载本组路由
//先看路由仓库中是否有这个组帮助类,没就异常。(仓库里的已有 组帮助类 是谁放进仓库的呢?就是 在 ARouter.init中调用 LogisticsCenter.init的时候。它里面的 loadRouterMap() 中执行代码是 transform时收集到的 APT 生成 根帮助类 的load方法。)
if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
try {
//这里,仓库中有这个组的帮助类,那么就可以 加载 这个组的所有路由 到内存
addRouteGroupDynamic(postcard.getGroup(), null);
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
//仓库有了这个组的路由信息,再重新完善
completion(postcard);
}
} else {
//有路由信息,就完善postcard
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
...
switch (routeMeta.getType()) {
case PROVIDER: //provider, 获取实例
// 要实现自IProvider
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) { // 没有,就反射创建
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider); //实例存入仓库
instance = provider;
} catch (Exception e) {...
}
}
postcard.setProvider(instance); //实例通过PostCard带出去
postcard.greenChannel(); // Provider 不用经过拦截器
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment 不用经过拦截器
}
}
}


public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) {
if (Warehouse.groupsIndex.containsKey(groupName)){//这里,仓库中 有这个组的帮助类
//拿到这个 组帮助类,实例化,调loadInfo 把 这个组所有的路由信息加载 到 仓库中的routes。
Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(groupName);
}...
}

  1. 尝试通过path从仓库中获取对应的路由元信息,如果没有获取到:要么不存在、要么还没有加载本组路由。
  2. 先看路由仓库中是否有这个组帮助类,没就抛出异常;有就通过addRouteGroupDynamic()加载这个组的所有路由,然后再调completion
  3. 有了path对应的路由元信息,就同步到postCard中,其中最重要的就是 目标类class——routeMeta.getDestination()。并且判断如是provider就创建服务实例并存入仓库。

ARouter最为核心的内容——如何获取 无直接依赖的模块的 class对象

  • 编译时ARouter根据注解 @Route 生成了各个帮助类,帮助类的loadInfo方法中包含了路由目标信息,最重要的是注解的类class,然后在ARouter初始化时根据 根帮助类、provider帮助类、拦截器帮助类 对仓库WareHouse的groupsIndex进行赋值(以及providersIndex、interceptorsIndex),然后在路由发起后,根据path通过WareHouse的groupsIndex 加载 同组的所有路由元信息,也就是拿到了目标class。
  • 抽象一下就是:moduleA先把目标class存入第三方仓库——ARouter的WareHouse,然后muduleB发起路由时从仓库中根据path获取目标class,ARouter就是这个仓库的管理者。 就好比 邮政是信件的管理者,它是两方通信者的中间人。

流程图

以上分析内容梳理成流程图:

image

总结

moduelA通过中间人ARouter把路由信息的存到仓库WareHouse;moduleB发起路由时,再通过中间人ARouter从仓库WareHouse取出路由信息,这要就实现了没有依赖的两者之间的跳转与通信。其中涉及Activity的跳转、服务provider的获取、拦截器的处理等。

其中ARouter在编译时生成的帮助类,是用于对所有使用@Route、@Interceptor注解的类信息的分组和收集,编译运行时对路由信息仓库Warehouse的填充和使用。这里涉及到的是 Annotation Process ToolAPT)技术,即注解处理工具。

如何使用编译时生成的帮助类:除了运行时查找dex,还可以在编译时扫描帮助类信息,并且直接在物流中心LogisticsCenter loadRouterMap()方法中直接插入使用帮助类的代码,这里涉及 Android Gradle PluginAGP)技术,即Android的gradle插件相关技术。


APT技术

APT介绍

APT的理解

APT(Annotation Processing Tool),即 注解处理器,是javac中提供的 编译时扫描和处理注解的工具,它对源代码文件进行检测找出其中的注解,然后使用注解进行额外的处理。

注解就像是一个标签,有很多类型,可以贴在某些元素上面进行标记,并且标签上可以写一些信息。APT就是用来处理标签的工具,在编译开始后,可以拿到自己所关心的类型的所有标签,然后根据标签信息和被标记的元素信息,做一些事情。做那些事呢,这就看你如何写APT了,你让他干啥他就干啥,通常都是会生成一些帮助类——帮助完成你的目的的类。 后面无论对这种标签的使用是增加、减少了, 每次编译都会重新走这一过程,而上一次的处理结果会被清空。

宏观上理解,APT就是javac提供给开发者在编译时处理注解的一种技术;微观上,具体到实例中就是指 继承自javax.annotation.processing. AbstractProcessor 的实现类,即一个处理特定注解的处理器。(下文提到的APT都是宏观上理解,具体的处理器简称为Processor)

那不使用APT能否完成目的呢? 也是可以的,上篇提到的帮助类,目的就是为了收集路由元信息(路由目标类class),我们如果不使用ARouter,那么就需要自己定义一个moduleA、moduelB共同依赖的muduleX,把需要进行跳转的XXXActivity这些类的class手工写代码保存到muduleX的Map中,key可以用XXXActivity的类名,value就是XXXActivity.class,这样moduleA、moduelB之间发起跳转时 就通过想要跳转的Activity的类名 从muduleX的Map中获取目标Activity的class,这样也是能完成 无相互依赖的moduleA、moduelB之前进行页面跳转的。

而使用了APT,只要使用注解进行标记即可,无论使用者怎么标记, 每次编译时都由APT统一处理,不会出错、也不担心有遗漏

通常用到APT技术的都是像ARouter这样的通用能力框架:

  • 提供定义好的注解,例如@Route
  • 提供简洁的API接口,例如ARouter.getInstance().build(“/module/1”).navigation()

这样上层业务使用极为方便,只需要使用注解进行标记,然后调用API即可。信息如何收集和存储、如何寻找和使用,框架使用者是不用关心的。

APT还有两个特点:

  • 获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。
  • 注意APT并不能对源文件进行修改,只能获取注解信息和被注解对象的信息,然后做一些自定义的处理,例如生成java类。

APT的原理

image

在Java源码到class文件之间,需要经过 注解处理器的处理,注解处理器生成的代码也同样会经过这一过程,最终一起生成class文件。在Android中,class文件还会被打进Dex文件中,最后生成APK文件。

注解处理器的执行是在编译的初始阶段,并且会有多个processor(查看所有注册的processor:intermediates/annotation_processor_list/debug/annotationProcessors.json)。

自定义的注解处理器如何注册进去:

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
@AutoService(Processor.class) //把 TestProcessor注册到编译器中
@SupportedAnnotationTypes({"com.hfy.test_annotations.TestAnnotation"})//TestProcessor 要处理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8)//设置jdk环境为java8
//@SupportedOptions() //一些支持的可选配置项
public class TestProcessor extends AbstractProcessor {
public static final String ACTIVITY = "android.app.Activity";
//Filer 就是文件流输出路径,当我们用AbstractProcess生成一个java类的时候,我们需要保存在Filer指定的目录下
Filer mFiler;
//类型 相关的工具类。当process执行的时候,由于并没有加载类信息,所以java文件中的类信息都是用element来代替了。
//类型相关的都被转化成了一个叫TypeMirror,其getKind方法返回类型信息,其中包含了基础类型以及引用类型。
Types types;
//Elements 获取元素信息的工具,比如说一些类信息继承关系等。
Elements elementUtils;
//来报告错误、警告以及提示信息
//用来写一些信息给使用此注解库的第三方开发者的
Messager messager;

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnv.getFiler();
types = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
if (annotations == null || annotations.size() == 0){
return false;
}
//获取所有包含 @TestAnnotation 注解的元素
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(TestAnnotation.class);

//每个Processor的独自的逻辑,其他的写法一般都是固定的
parseAnnotation(elements)

return true;
}

//解析注解并生成java文件
private boolean parseAnnotation(Set<? extends Element> elements) {
...
}

}

上面的TestProcessor就是一个典型的注解处理器,继承自javax.annotation.processing. AbstractProcessor。注意到TestProcessor重写了init()和process()两个方法,并且添加了几个注解。

几个注解是 处理器的注册和配置

  1. 使用注解 @AutoService进行注册,这样在编译这段就会执行这个处理器了。需要依赖com.google.auto.service:auto-service:1.0-rc4’ 才能使用@AutoService
  2. 使用注解 @SupportedAnnotationTypes 设置 TestProcessor 要处理的注解,要使用目标注解全类名
  3. 使用注解 @SupportedSourceVersion设置支持的Java版本、使用注解@SupportedOptions()配置一些支持的可选配置项

重写的两个方法:init()、process()是Processor的初始化和处理过程:

  1. init() 方法是初始化处理器,每个注解处理器被初始化的时候都会被调用,通常是这里使用 处理环境- ProcessingEnvironment 获取一些工具实例,如上所示是一般通用的写法:
  • mFiler,就是文件流输出路径,当我们用AbstractProcess生成一个java类的时候,我们需要保存在Filer指定的目录下
  • types,类型 相关的工具类。当process执行的时候,由于并没有加载类信息,所以java文件中的类信息都是用element来代替了。类型相关的都被转化成了一个叫TypeMirror,其getKind方法返回类型信息,其中包含了基础类型以及引用类型。
  • elementUtils,获取元素信息的工具,比如说一些类信息继承关系等。
  • messager,来报告错误、警告以及提示信息,用来写一些信息给使用此注解库的第三方开发者的
  1. process() 方法,注解处理器实际处理方法,在这里写处理注解的代码,以及生成Java文件。它有两个参数:
  • annotations,是@SupportedAnnotationTypes声明的注解 和 未被其他Processor消费的注解的子集,也就是剩下的且是本Processor关注的注解,类型是 TypeElement
  • roundEnvironment,有关当前和之前处理器的环境信息,可以让你查询出包含特定注解的被注解元素
  • 返回值,true表示@SupportedAnnotationTypes中声明的注解 由此 Processor 消费掉,不会传给下个Processor。注解不断向下分发,每个processor都可以决定是否消费掉自己声明的注解。

在编译流程进入Processor前,APT会对整个Java源文件进行扫描,这样就会获取到 所有添加了的注解和对应被注解的类。 注解和被注解的类,一起被视为一个元素,即TypeElement,就是process()方法参数annotations的数据类型。 通过TypeElement,我们可以获取注解的所有信息、被注解类的所有信息,这样就可以根据这些信息来生成 我们需要的帮助类了

process()方法的实现:拿到所有关注的注解元素后,就是每个Processor的独自的逻辑——解析注解并生成需要的java文件。

ARouter的APT

ARouter工程介绍

image

ARouter是一个典型的 APT+ AGP 框架,有4个module:

  • annotation,定义共业务侧使用的注解,例如@Route
  • api,框架的核心逻辑实现 并提供便于业务侧使用的Api,即上一篇介绍的内容
  • compiler,定义注解处理器,用于生Java类,就是对APT的使用,即本篇的内容
  • gradle-plugin,定义Android Gradle 插件,用于编译时在class转为dex文件前扫描目标帮助类 并进行代码注入,将在第三篇中介绍

ARouter是如何解析注解并生成帮助类的

RouteProcessor

BaseProcessor

image

有4个Processor,其中 BaseProcessor 是直接继承自 AbstractProcessor 的,其他都是继承BaseProcessor:

  • RouteProcessor,处理注解@Route、@Autowired注解,用于生成路由帮助类
  • InterceptorProcessor,处理@Interceptor注解,用于生成拦截器帮助列
  • AutowiredProcessor,处理@Autowired注解,用户生成处理activity/fragment变量的帮助类

BaseProcessor:

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
public abstract class BaseProcessor extends AbstractProcessor {
Filer mFiler;
Logger logger;
Types types;
Elements elementUtils;
TypeUtils typeUtils;
//此Module的名字
String moduleName = null;
// 是否生成router doc
boolean generateDoc;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
types = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
typeUtils = new TypeUtils(types, elementUtils);
logger = new Logger(processingEnv.getMessager());

// 获取配置: moduleName、generateDoc
Map<String, String> options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
moduleName = options.get(KEY_MODULE_NAME);
generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
}

if (StringUtils.isNotEmpty(moduleName)) {
moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
...
} else {
//没有配置 moduleName,就报错!
logger.error(NO_MODULE_NAME_TIPS);
throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
}
}
...

//设置关注的配置项,其中KEY_MODULE_NAME就是在.gradle中配置的
@Override
public Set<String> getSupportedOptions() {
return new HashSet<String>() {{
this.add(KEY_MODULE_NAME);
this.add(KEY_GENERATE_DOC_NAME);
}};
}
}

BaseProcessor主要是在init()中做了各种工具的初始化,同时获取了key为 AROUTER_MODULE_NAME 的配置项 moduleName——module名字。重写的getSupportedOptions()方法,和前面的@SupportedOptions作用是一致的。 AROUTER_MODULE_NAME 就是路由所在module的gradle文件中配置的:

image

拿到moduleName有什么作用呢?上一篇中提到的 根帮助类Provider帮助类拦截器帮助类 的类名就需要moduleName构成:

image

整体流程

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
@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {
private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta.
private Map<String, String> rootMap = new TreeMap<>(); // Map of root metas, used for generate class file in order.

private TypeMirror iProvider = null;
private Writer docWriter; // Writer used for write doc

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
...
iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType();
...
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isNotEmpty(annotations)) {
Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
try {
this.parseRoutes(routeElements);
} catch (Exception e) {
logger.error(e);
}
return true;
}

return false;
}

定义了两个Map:

  • groupMap,此module所有路由组的信息。key为路由的group,value是此group下所有路由元信息。用于生成 组帮助类
  • rootMap,key是group,value是组帮助类的类名,用于生成 根帮助类

在init()中使用elementUtils获取了 IProvider接口的类型,用于后面判断一个类元素是否是IProvider的实现类。

在process()中获取了添加了@Route的所有Element,然后调用parseRoutes()开始解析。

核心逻辑

根帮助类的loadInfo方法体内,是用接收的map来put当前module所有路由 组帮助类的class。先创建了所有的组帮助类之后,才创建的根帮助类,并且一个module只有一个根帮助类。而组帮助类,则应该是先遍历此module所有 @Route注解类,找到相同group的路由创建组帮助类。

Java文件要如何生成呢?javepoet

  • JavaPoet是一款可以自动生成Java文件的第三方依赖
  • 简洁易懂的API,上手快
  • 让繁杂、重复的Java文件,自动化生成,提高工作效率,简化流程

JavaPoet的常用类:

  • TypeSpec,用于生成类、接口、枚举对象的类
  • MethodSpec,用于生成方法对象的类
  • ParameterSpec,用于生成参数对象的类
  • FieldSpec,用于配置生成成员变量的类
  • ClassName,通过包名和类名生成的对象,在JavaPoet中相当于为其指定Class
  • ParameterizedTypeName,通过MainClass和IncludeClass生成包含泛型的Class
  • JavaFile,控制生成的Java文件的输出的类

下面就来看**parseRoutes()**方法:

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {

//------------ 一、准备工作:遍历routeElements并创建对应RouteMeta,接着按group分组后 存入groupMap ---------------

if (CollectionUtils.isNotEmpty(routeElements)) {
// 清空rootMap
rootMap.clear();

//准备好会用到的元素类型,用于后面的判断
TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType();
TypeMirror type_Service = elementUtils.getTypeElement(SERVICE).asType();
TypeMirror fragmentTm = elementUtils.getTypeElement(FRAGMENT).asType();
TypeMirror fragmentTmV4 = elementUtils.getTypeElement(Consts.FRAGMENT_V4).asType();
// ARouter的相关接口
TypeElement type_IRouteGroup = elementUtils.getTypeElement(IROUTE_GROUP);
TypeElement type_IProviderGroup = elementUtils.getTypeElement(IPROVIDER_GROUP);
ClassName routeMetaCn = ClassName.get(RouteMeta.class);
ClassName routeTypeCn = ClassName.get(RouteType.class);

//帮助类:就是使用javaPoet生成的所有用于 收集所有路由等信息的类。

//创建一个参数类型:用于存所有 组帮助类class 的Map,Map<String, Class<? extends IRouteGroup>>
ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
)
);

//创建一个参数类型:用于存某个分组内的所有路由,Map<String, RouteMeta>
ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class)
);

//创建输入参数的名字,也就是几种帮助类loadInto方法的参数名
ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();

//创建方法builder:根帮助类loadInto方法,loadInto(Map<String, Class<? extends IRouteGroup>> routes)
MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(rootParamSpec);

//遍历routeElements,创建对应类型的RouteMeta,并分组后存入 groupMap 中,统计后最后生成根帮助类
for (Element element : routeElements) {
TypeMirror tm = element.asType();
Route route = element.getAnnotation(Route.class);
RouteMeta routeMeta;

//是 Activity 或者 Fragment
if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
// 获取被@Autowired注解的变量
Map<String, Integer> paramsType = new HashMap<>();
Map<String, Autowired> injectConfig = new HashMap<>();
injectParamCollector(element, paramsType, injectConfig);

//对应类型的RouteMeta
if (types.isSubtype(tm, type_Activity)) {
routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} else {
routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
}
routeMeta.setInjectConfig(injectConfig);
} else if (types.isSubtype(tm, iProvider)) {
//是IProvider实现类
routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
} else if (types.isSubtype(tm, type_Service)) {
//是Service
routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
} else {
throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
}

//按group分类 然后存入groupMap,一个value就是同group的所有路由
categories(routeMeta);
}

//Provider帮助类的 的 loadInto 方法:loadInto(Map<String,RouteMeta> providers)
MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(providerParamSpec);

Map<String, List<RouteDoc>> docSource = new HashMap<>();

//------------ 二、遍历groupMap:创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句 ---------------

//遍历groupMap:创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
String groupName = entry.getKey();
//组帮助类的 的 loadInto 方法
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
...
Set<RouteMeta> groupData = entry.getValue(); //groupData,一个组 的所有路由

//遍历同组路由:构建Provider帮助类loadInto方法体语句、组帮助类 的方法体语句
for (RouteMeta routeMeta : groupData) {
ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());

switch (routeMeta.getType()) {
case PROVIDER:
List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
for (TypeMirror tm : interfaces) {
if (types.isSameType(tm, iProvider)) {//直接实现自IProvider接口
loadIntoMethodOfProviderBuilder.addStatement(
"providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
(routeMeta.getRawType()).toString(),
routeMetaCn,
routeTypeCn,
className,
routeMeta.getPath(),
routeMeta.getGroup());
} else if (types.isSubtype(tm, iProvider)) {
// //直接实现自IProvider的子接口
loadIntoMethodOfProviderBuilder.addStatement(
"providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
tm.toString(), // So stupid, will duplicate only save class name.
routeMetaCn,
routeTypeCn,
className,
routeMeta.getPath(),
routeMeta.getGroup());
}
}
break;
default:
break;
}

//参数
...

//组帮助类 的方法体内的语句
loadIntoMethodOfGroupBuilder.addStatement(
"atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
routeMeta.getPath(),
routeMetaCn,
routeTypeCn,
className,
routeMeta.getPath().toLowerCase(),
routeMeta.getGroup().toLowerCase());
...
}

//创建 实现自IRouteGroup的 组帮助类文件(有多个,每个分组一个帮助类,每个帮助内 含有同组的activity、fragment、IProvider)
String groupFileName = NAME_OF_GROUP + groupName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(groupFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(type_IRouteGroup))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()
).build().writeTo(mFiler);

//rootMap 存入了所有的组帮助类
rootMap.put(groupName, groupFileName);
}

//--------------------- 三、创建根帮助类、创建Provider帮助类 ------------------------

//遍历rootMap,创建 根帮助类 方法体语句
if (MapUtils.isNotEmpty(rootMap)) {
for (Map.Entry<String, String> entry : rootMap.entrySet()) {
loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
}
}
...

//创建 Provider帮助类 文件(只有一个,这个是包含所有服务的帮助类)
String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(providerMapFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(type_IProviderGroup))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfProviderBuilder.build())
.build()
).build().writeTo(mFiler);

//创建 根帮助类文件(只有一个)
String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(rootFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfRootBuilder.build())
.build()
).build().writeTo(mFiler);

}
}

(以上定义参数、创建方法体语句、生成Java类 就是使用的javapoet相关API)

总共有三个步骤:

  • 准备工作,遍历routeElements并创建对应RouteMeta,接着按group分组后 存入 groupMap
  • 遍历groupMap,创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句
  • 遍历rootMap生成方法体语句后创建根帮助类,创建Provider帮助类

注解的用法、Element相关知识、javapoet用法

AGP/Transform/ASM—动态代码注入

Arouter中的核心技术

Gradle

  • 一个自动化构建框架
  • 在Android开发中,用来编译打包输出apk,还可以用来添加三方依赖

Gradle官网中的介绍: Gradle是一种开源构建自动化工具,足够灵活,可以 构建几乎任何类型的软件。Gradle对您要构建什么或如何构建它 几乎没有任何限制

也就是说,Gradle这个构建工具,可以被用来构建任何东西,具体如何构建也是由使用者制定。 在Android领域,AndroidStudio中的项目默认会引入Gradle,就是被用来构建Android应用包——apk文件。

AndroidStudio中只有Gradle还不够,还得告诉它如何构建。Apk打包流程包括很多环节:源码文件经过JavaCompiler转为class文件、class经过dex操作变为dex文件、dex和资源等打包成apk、签名 等等一系列步骤,这就需要Google使用Gradle把这些操作都串联起来。

image

在项目根目录的build.gradle文件中的这一句,就会引入「Google使用Gradle制定的这一系列打包流程」,也就是Google自定义的各种 Gradle 的任务。每个任务负责某个具体的事情,按照依赖关系排好执行顺序,先后执行,最后输出Apk文件。

在AndroidStudio的Terminal中,输入打包命令: ./gradlew app:assembleDebug --console=plain,输出如下:

1
2
3
4
5
6
7
8
9
10
11
:app:preBuild UP-TO-DATE
...

:app:compileDebugJavaWithJavac
...

:app:transformClassesWithDexBuilderForDebug
...

:app:packageDebug
:app:assembleDebug

这些就是Google官方使用Gradle定义的相互有依赖关系的各种任务,这些任务执行完就完成了Apk的打包。

这些任务是如何被创建的呢?在app模块和arouter-api模块的build.gradle文件中,第一句分别是

1
2
3
//app的build.gradle
apply plugin: 'com.android.application'

1
2
3
//arouter-api的build.gradle
apply plugin: 'com.android.library'

意思是,app引入了名为 com.android.application的gradle插件、arouter-api引入的是名为 com.android.library的gradle插件。上面介绍的各种任务就是在这两种插件中创建的,引入了插件,也就引入了插件对应的任务。

定义了各种gradle任务,为啥还要定义gradle插件呢?一是可以对任务进行管理,例如排好任务的依赖和执行顺序;二是可以复用,模块引入某个插件,就拥有了对应的能力。即gradle插件就是 将构建逻辑的可重用部分封装起来,可以应用到不同的项目和构建中。

gradle中,除了任务( Task)、插件( Plugin),还有一个概念—— Project,共同组成了gradle的核心内容:

  • Project:Gradle工程,Project包含一个构建脚本,通常名为 build.gradle。 构建脚本为该 Project定义Task、依赖项、plugin和其他配置。一个Android工程以及每个Android module都是一个Project (都有一个build.gradle文件),一个build.gradle文件编译后就是一个Project对象
  • Task:Gradle任务,包含执行某些工作的逻辑,Task本身包括:
    • 动作(Actions):具体某中的操作,例如复制文件或编译源代码
    • 输入(Inputs):操作使用的值、文件、目录
    • 输出(Outputs):操作修改或生成的文件、目录
  • Plugin:Gradle插件,用于创建和封装Task,介入编译构建过重要达到扩展功能目的。Plugin使得相同逻辑和配置可以被多个Project复用

再来看下 Gradle的生命周期,有三个阶段:

  • 初始化:确定哪些Project将参与构建。即读取settings.gradle文件中的include的所有模块
  • 配置:确认参与构建的所有任务及其顺序。读取参与构建的Project的build.gradle文件,确定需要运行哪些Task以及确定Task间依赖关系和执行顺序(包含直接在build.gradle中定义的Task以及引入的Plugin中定义的Task)
  • 执行:运行在配置阶段确定的Task,这里是真正执行所有的打包操作

以上对Gradle的基本认知非常重要,足以理解本篇内容。至于Gradle脚本编写、Task和Plugin详细用法这种固定知识点可自行到Gradle官网学习即可,这里不再搬运。

Android Gradle Plugin

AGP(Android Gradle Plugin),即 Android Gradle 插件,是Android开发中使用的Gradle插件。

上面提到的,在项目根目录的build.gradle文件中引入了 com.android.tools.build:gradle:4.0.0,也就引入了 Google官方写好的两个Gradle插件: com.android.applicationcom.android.library,这两个插件内部会创建各种Task用于完成Apk编译打包流程。

Gradle就像是工厂的流水线一样,负责定义流程和规则,而具体的工作都是通过插件(及其包含的Task)实现的。如果想在流水线上增加一个操作,例如给原材料贴标签,那么就在最开头的位置增加一个贴标签的机器即可。对应的,想要在编译过程中动态插入代码,我们就需要新增一个AGP。

原本只有Google官方定义好的AGP,现在 需要做的就是自定义一个插件,并且插在编译过程中合适的位置上

Transform

搜集帮助类信息、动态注入代码, 要在编译过程的什么时机执行呢? 这个时机要能够搜集到所有帮助类(包括依赖的三方AAR中的)并且还可以向目标文件中插入代码。

在官方定义的AGP中提供了这样一个时机,如下打包流程图中箭头位置:

image

在class文件转为dex文件之前,这个节点可以拿到参与构建的所有class文件,包括本项目java文件/kotln文件经javac生产的class文件和依赖的第三方库中的class文件。

代码上如何使用拿到这个时机呢?Android官方的AGP中提供了一个API—— Transform

需要自定义Transform,在Transform的 transform方法中就可以获取到所有的class文件。

  • transform方法被调用的时机就是在class被打进dex文件之前
  • 需要在创建的gradle插件中注册自定义的Transform
  • 事实上每添加一个Transform,在Grade的配置阶段就会创建一个gradle Task

ASM

在Transform的中拿到所有class文件后, 如何识别帮助类和 LogisticsCenter 类呢?识别后如何在 LogisticsCenter 类中插入代码呢? 如下图这样:

image

上图是class文件反编译后的样子,如果使用文本软件直接打开class文件,就是十六进制内容:

image

class文件又称为 字节码文件,可以使用javap命令把class文件转为大概 能看懂的字节码文件(或者直接使用AndroidStudio插件 ASM Bytecode Viewer来查看):

在Transform的中拿到了class文件后,想要识别类信息并且还能修改内容,这个在代码上如何实现先呢?方法就是ASM。

ASM :字节码操作框架,是一种基于java字节码层面的代码分析和修改工具,ASM的目标是生成、转换、分析已编译的java class文件,可使用ASM工具读、写、转换JVM指令集。通俗点讲就是来处理javac编译之后的class文件。

ASM通过 访问者模式依次遍历class字节码中的各个部分(属性、属性、注解等),并不断回调到上层(这有点像SAX解析xml的过程),上层可在业务关心的某个访问点,修改原有逻辑。之所以可以这么做,是因为java字节码是按照严格的JVM规范生成的二进制字节流,ASM是照这个规范对java字节码的一次解释,将晦涩难懂的字节码背后对应的JVM指令一条条的转换成ASM API。其中核心类如下:

  • ClassReader:用于读取已经编译好的class文件,读取到内容(属性/方法等)后会调用ClassWriter对应的方法
  • ClassWriter:是ClassVisitor的子类,主要负责将ClassVisitor处理好后的class转换成byte数组,有了byte数组就能通过OutputStream写入文件,也就完成了对class文件的修改
  • ClassVisitor:class文件访问者,可用于修改类型西,如修改类名、属性、方法,也可生成类的class文件。对于字节码文件中不同的区域有不同的Visitor:用于访问方法的 MethodVisitor、用于访问类属性的 FieldVisitor、用于访问注解的 AnnotationVisitor
  • MethodVisitor:是ASM中最为重要的类,它是对帧栈中「操作数栈」进行操作的核心类,无论实现什么功能都离不开对操作数栈的操作

使用ASM识别一个类,具体怎么操作呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 扫描class
* @param inputStream class文件的输入流(从transform中获取的)
*/
static void scanClass(InputStream inputStream) {
ClassReader cr = new ClassReader(inputStream)
ClassWriter cw = new ClassWriter(cr, 0)
ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5, cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
inputStream.close()
}

static class ScanClassVisitor extends ClassVisitor {
...
//该方法主要提供对java版本,类权限访问符、全限定名、泛型、父类、接口相关信息
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces)
...
}
}

以上是读取/扫描 class文件的固定写法,重点是自定的ClassVisitor——ScanClassVisitor类,在它的visit()方法中可以获取到 类名、类实现的接口等等。通过对比 类名、接口等信息,就能确认是否是帮助类了。

使用ASM在类方法插入代码,具体怎么操作呢?

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
/**
* 修改class方法
* @param inputStream class文件的输入流(从transform中获取的)
*/
private byte[] handleClassMethod(InputStream inputStream) {
ClassReader cr = new ClassReader(inputStream)
ClassWriter cw = new ClassWriter(cr, 0)
ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
return cw.toByteArray()//这里是
}

class MyClassVisitor extends ClassVisitor {
...
@Override
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
//要插入代码的目标方法method 被访问到
if (name == method) {
mv = new MyMethodVisitor(Opcodes.ASM5, mv)
}
return mv
}
}

class MyMethodVisitor extends MethodVisitor {
...
//visitInsn,执行零操作指令,意味着操作码后无需跟任何字节的操作数,例如下面if种判断的return指令
@Override
void visitInsn(int opcode) {
//确保在方法return之前插入代码
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
...//此处是需要写自己的插入代码的逻辑
}
super.visitInsn(opcode)//插入代码之后再走return
}
...
}

MethodVisitor——MyMethodVisitor,visitInsn()方法中确保在 方法return之前插入代码

插入代码的逻辑

  1. 使用javap命令查看要插入的代码的字节码形式,然后使用ASM的API来写字节指令。
  2. 使用AndroidStudio插件 ASM Bytecode Viewer,可以帮助查看字节码, 并直接生成ASM代码。这样要插入的代码 通过 AndroidStudio插件 就可以直接生成ASM API代码了:

LogisticsCenter的loadRouterMap()查看字节码文件和对应的ASM字节码文件:

image

image

帮助类的搜集

Arouter项目中,有一个Gradle插件模块,就是使用了上面那介绍的技术点来实现 帮助类的收集路由加载代码插入

image

核心类作用如下:

  • PluginLaunch,自定义Gradle 插件,并注册自定义的Transform
  • RegisterTransform,自定义Transform,在class文件被打进dex文件之前拿到所有的class文件
  • ScanUtil,使用ASM对源码生产class文件和三方jar中的class文件进行扫描,找到所有帮助类
  • RegisterCodeGenerator,找到类 LogisticsCenter,使用ASM实现路由加载代码的插入

先来看帮助类的搜集过程:PluginLaunch

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
//ARouter自动注册插件
public class PluginLaunch implements Plugin<Project> {

//apply()方法,对应着 "apply plugin:",即在gradle的配置阶段就会执行,把RegisterTransform这个task挂接到编译流程中
@Override
public void apply(Project project) {
def isApp = project.plugins.hasPlugin(AppPlugin)
//只有application模块才需要本插件去生成注册代码
if (isApp) {
Logger.make(project)
Logger.i('Project enable arouter-register plugin')

def android = project.extensions.getByType(AppExtension)
def transformImpl = new RegisterTransform(project)

//要扫描的接口:根帮助类接口、拦截器帮助类接口、服务帮助类接口
ArrayList<ScanSetting> list = new ArrayList<>(3)
list.add(new ScanSetting('IRouteRoot'))
list.add(new ScanSetting('IInterceptorGroup'))
list.add(new ScanSetting('IProviderGroup'))
RegisterTransform.registerList = list
//注册transform
android.registerTransform(transformImpl)
}
}
}

PluginLaunch,就是自定义的Gradle插件,然后注册了自定义的RegisterTransform。RegisterTransform中的registerList保存着的是各个帮助类的接口。

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
//1. 扫描class找到 实现了指定接口的class
//2. 生成[帮助类注册代码]插入到 LogisticsCenter
class RegisterTransform extends Transform {

Project project
//用于存各个帮助类接口及对应的所有帮助类
static ArrayList<ScanSetting> registerList
//包含LogisticsCenter.class的jar文件
static File fileContainsInitClass;
...
//transform方法,会在gradle的执行阶段执行(Transform本身会被封装在一个Task中-TransformTask)
//执行阶段具体什么时候呢?--源码生成class文件之后,class被打进dex文件之前
@Override
void transform(Context context, Collection<TransformInput> inputs
, Collection<TransformInput> referencedInputs
, TransformOutputProvider outputProvider
, boolean isIncremental) throws IOException, TransformException, InterruptedException {
...
inputs.each { TransformInput input ->
//一、扫描全部jar(三方库中的class文件)
input.jarInputs.each { JarInput jarInput ->
String destName = jarInput.name
//重命名jar文件
def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath)
if (destName.endsWith(".jar")) {
destName = destName.substring(0, destName.length() - 4)
}
File src = jarInput.file
File dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR)

if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) {
//-----扫描jar以找到目标class文件 begin----
ScanUtil.scanJar(src, dest)
//-----扫描jar以找到目标class文件 end ----
}

FileUtils.copyFile(src, dest)
}

//二、扫描全部目录(源码生成的class文件)
input.directoryInputs.each { DirectoryInput directoryInput ->
File dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)

String root = directoryInput.file.absolutePath
if (!root.endsWith(File.separator))
root += File.separator
directoryInput.file.eachFileRecurse { File file ->
def path = file.absolutePath.replace(root, '')
if (!leftSlash) {
path = path.replaceAll("\\", "/")
}

//-----扫描目录以找到目标包路径的class文件 begin----
//类是在arouter APT生成类的包,'com/alibaba/android/arouter/routes/'
if(file.isFile() && ScanUtil.shouldProcessClass(path)){
ScanUtil.scanClass(file)
}
//-----扫描目录以找到目标包路径的class文件 end----
}

FileUtils.copyDirectory(directoryInput.file, dest)
}
}

//扫描结束,拿到生成的所有目标帮助类(APT生成的IRouteRoot、IInterceptorGroup、IProviderGroup的实现类)
if (fileContainsInitClass) {
registerList.each { ext ->
Logger.i('Insert register code to file ' + fileContainsInitClass.absolutePath)

if (ext.classList.isEmpty()) {
Logger.e("No class implements found for interface:" + ext.interfaceName)
} else {
//在 LogisticsCenter#loadRouterMap()中插入代码
RegisterCodeGenerator.insertInitCodeTo(ext)
}
}
}
}
}

RegisterTransform是自定义的Transformtransform方法会在gradle的执行阶段执行——源码生成class文件之后、class被打进dex文件之前。

具体逻辑就是

  1. 扫描全部jar(三方库中的class文件)
  2. 扫描全部目录(源码生成的class文件)
  3. 拿到所有目标帮助类
  4. 在 LogisticsCenter#loadRouterMap()中插入代码

具体的扫描过程是使用ScanUtil类,插入代码是RegisterCodeGenerator类。

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
class ScanUtil {
//扫描jar
static void scanJar(File jarFile, File destFile) {
if (jarFile) {
def file = new JarFile(jarFile)
Enumeration enumeration = file.entries()
while (enumeration.hasMoreElements()) { //遍历jar里面的所有class文件
JarEntry jarEntry = (JarEntry) enumeration.nextElement()
String entryName = jarEntry.getName()
if (entryName.startsWith(ScanSetting.ROUTER_CLASS_PACKAGE_NAME)) {//找到包名是'com/alibaba/android/arouter/routes/'的class
InputStream inputStream = file.getInputStream(jarEntry)
scanClass(inputStream) //也是走scanClass方法
inputStream.close()
} else if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {//LogisticsCenter,物流中心,是在jar中
// 记录包含 LogisticsCenter.class 的jar文件,扫描完成后会向其中插入代码
RegisterTransform.fileContainsInitClass = destFile
}
}
file.close()
}
}
...

//扫描class文件
static void scanClass(File file) {
scanClass(new FileInputStream(file))
}

//扫描class,inputStream class文件的输入流(从transform中获取的)
static void scanClass(InputStream inputStream) {
ClassReader cr = new ClassReader(inputStream)
ClassWriter cw = new ClassWriter(cr, 0)
ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5, cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
inputStream.close()
}

static class ScanClassVisitor extends ClassVisitor {
...
//该方法主要提供对java版本,类权限访问符、全限定名、泛型、父类、接口相关信息
void visit(int version, int access, String name, String signature,String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces)
//按类实现的接口,分别存到 RegisterTransform.registerList对应ScanSetting#classList中
RegisterTransform.registerList.each { ext ->
if (ext.interfaceName && interfaces != null) {
interfaces.each { itName ->
if (itName == ext.interfaceName) {
if (!ext.classList.contains(name)) {
ext.classList.add(name)
}
}
}
}
}
}
}
}

无论是jar还是目录,都是找到包名是 com/alibaba/android/arouter/routes/的class文件,然后通过class文件的输入流,使用ASM读取class文件内容,再自定义ScanClassVisitor在visit()方法,按类实现的接口找到所有的帮助类,存到 RegisterTransform.registerList对应的ScanSetting.classList中。

动态代码注入

代码插入的过程:

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
83
84
85
86
87
88
class RegisterCodeGenerator {
...
static void insertInitCodeTo(ScanSetting registerSetting) {
if (registerSetting != null && !registerSetting.classList.isEmpty()) {
RegisterCodeGenerator processor = new RegisterCodeGenerator(registerSetting)
//包含LogisticsCenter.class的jar文件
File file = RegisterTransform.fileContainsInitClass
if (file.getName().endsWith('.jar'))
processor.insertInitCodeIntoJarFile(file)
}
}

//插入代码到jar
private File insertInitCodeIntoJarFile(File jarFile) {
if (jarFile) {
...
def file = new JarFile(jarFile)
Enumeration enumeration = file.entries()
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))

while (enumeration.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enumeration.nextElement()
String entryName = jarEntry.getName()
ZipEntry zipEntry = new ZipEntry(entryName)
InputStream inputStream = file.getInputStream(jarEntry)
jarOutputStream.putNextEntry(zipEntry)

//找到类 LogisticsCenter
if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
//拿到LogisticsCenter.class文件的输入流,开始插代码!!!
def bytes = referHackWhenInit(inputStream)

jarOutputStream.write(bytes)
} else {
jarOutputStream.write(IOUtils.toByteArray(inputStream))
}
...
}
...
optJar.renameTo(jarFile)
}
return jarFile
}

//使用ASM插入代码
private byte[] referHackWhenInit(InputStream inputStream) {
ClassReader cr = new ClassReader(inputStream)
ClassWriter cw = new ClassWriter(cr, 0)
ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
return cw.toByteArray()
}

class MyClassVisitor extends ClassVisitor {
...
@Override
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
//找到要插入代码的方法-loadRouterMap
if (name == ScanSetting.GENERATE_TO_METHOD_NAME) {
mv = new RouteMethodVisitor(Opcodes.ASM5, mv)
}
return mv
}
}

class RouteMethodVisitor extends MethodVisitor {
..
@Override
void visitInsn(int opcode) {
//在return之前插入代码
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
extension.classList.each { name ->
name = name.replaceAll("/", ".")//把帮助类路径中的"/"换成"."
mv.visitLdcInsn(name)//帮助类类名
//在LogisticsCenter.loadRouterMap()中添加代码:register(name),name就是 根帮助类/拦截器帮助类/服务帮助类 类名
mv.visitMethodInsn(Opcodes.INVOKESTATIC
, ScanSetting.GENERATE_TO_CLASS_NAME
, ScanSetting.REGISTER_METHOD_NAME
, "(Ljava/lang/String;)V"
, false)
}
}
super.visitInsn(opcode)
}
...
}
}
  1. 首先是拿到jar中LogisticsCenter.class文件的输入流
  2. 接着使用ASM自定义MyClassVisitorvisitMethod()方法中找到要插入代码的方法-loadRouterMap()
  3. 然后在自定义RouteMethodVisitor的visitInsn()方法中,确保在return之前插入代码
  4. 遍历所有帮助类,把帮助类路径中的”/“换成”.”

到这里,源码也就是梳理完了。

Powered by Hexo & Theme Keep
Unique Visitor Page View