Android 框架设计与初始化与配置
01.看阿里ARouter整体设计
- ARouter主要由三部分组成,包括对外提供的api调用模块、注解模块以及编译时通过注解生产相关的类模块。
- arouter-annotation注解的声明和信息存储类的模块
- arouter-compiler编译期解析注解信息并生成相应类以便进行注入的模块
- arouter-api核心调用Api功能的模块
- annotation模块
- Route、Interceptor、Autowired都是在开发是需要的注解。
- compiler模块
- AutoWiredProcessor、InterceptorProcessor、RouteProcessor分别为annotation模块对应的Autowired、Interceptor、Route在项目编译时产生相关的类文件。
- api模块
- 主要是ARouter具体实现和对外暴露使用的api。
02.这样设计框架目的
- 路由框架作用的两个时期,第一个时期是编译期,第二个时期是app运行的时期。
- 编译期是在你的项目编译的时候,这个时候还没有开始打包,也就是你没有生成apk呢!路由框架在这个时期根据注解去扫描所有文件,然后生成路由映射文件。这些文件都会统一打包到apk里,app运行时期做的东西也不少,但总而言之都是对映射信息的处理,如执行执行路由跳转等。
03.仿照ARouter设计自己框架
- 将框架从功能角度分成了三个library
- 第一个是注解模块,主要放置注解,
- 第二个是compiler模块,里面存放着注解处理器和处理注解的工具类,
- 第三个是路由框架的核心,存放着路由的api等等。
- 其中,AptCompiler 模块和 AptApi 模块都依赖了 AptAnnotation 模块,因为它们都需要用到注解。AptCompiler 和 AptApi 没有依赖关系。
- 为啥这样设计,因为是参考阿里ARouter路由的框架设计。
04.为何需要初始化操作
首先需要在用户使用路由跳转之前把这些路由映射关系拿到手,拿到这些路由关系最好的时机就是应用程序初始化的时候,通过apt生成的路由映射关系文件,在程序启动的时候扫描这些生成的类文件,然后获取到映射关系信息,保存起来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class ARouter_Root_app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("main", ARouter_Group_main.class);
routes.put("yc", ARouter_Group_yc.class);
}
}
public class ARouter_Group_main implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/main/FiveActivity",RouteMeta.build(RouteMeta.Type.ACTIVITY,FiveActivity.class,"/main/FiveActivity","main"));
}
}
public class ARouter_Group_yc implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/yc/SixActivity",RouteMeta.build(RouteMeta.Type.ACTIVITY,SixActivity.class,"/yc/SixActivity","yc"));
}
}如图所示
得出结论
- 实现了IRouteRoot接口的类都是保存了group分组映射信息,实现了IRouteGroup接口的类都保存了单个分组下的路由映射信息。
05.初始化操作逻辑
上面已经说了,在启动的时候初始化,并且拿到这些类是最好的。看一下初始化的代码
1
2
3
4
5
6
7
8
9public static void init(Application application) {
mContext = application;
try {
loadInfo();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "初始化失败!", e);
}
}在初始化的时候,得到实现IRouteRoot接口的所有类文件,便能通过循环调用它的loadInfo()方法得到所有实现IRouteGroup接口的类,而所有实现IRouteGroup接口的类里面保存了项目的所有路由信息。IRouteGroup的loadInfo()方法,通过传入一个map,便会将这个分组里的映射信息存入map里。
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/**
* 分组表制作
*/
private static void loadInfo() throws PackageManager.NameNotFoundException,
InterruptedException, ClassNotFoundException,
NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
//获得所有 apt生成的路由类的全类名 (路由表)
Set<String> routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PACKAGE);
for (String className : routerMap) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(ROUTE_ROOT_PACKAGE);
stringBuilder.append(".");
stringBuilder.append(SDK_NAME);
stringBuilder.append(SEPARATOR);
stringBuilder.append(SUFFIX_ROOT);
String s = stringBuilder.toString();
Log.d(TAG,"className-----------"+s);
//root中注册的是分组信息 将分组信息加入仓库中
if (className.startsWith(s)) {
//拿到反射字节码
Class<?> aClass = Class.forName(className);
//获取对象
Object o = aClass.getConstructor().newInstance();
((IRouteRoot) o).loadInto(Warehouse.groupsIndex);
}
}
Set<Map.Entry<String, Class<? extends IRouteGroup>>> entries =
Warehouse.groupsIndex.entrySet();
for (Map.Entry<String, Class<? extends IRouteGroup>> stringClassEntry : entries) {
Log.d(TAG, "Root映射表[ " + stringClassEntry.getKey() + " : "
+ stringClassEntry.getValue() + "]");
}
}打印映射表日志可知
- map集合key:”/yc/SixActivity”
- map集合value:RouteMeta.build(RouteMeta.Type.ACTIVITY,SixActivity.class,”/yc/SixActivity”,”yc”)
看一下RouteMeta.build源码
- RouteMeta里面便保存着ActivityClass的所有信息。第一个功能需求,便是在app进程启动的时候进行框架的初始化(或者在你开始用路由跳转之前进行初始化都可以),在初始化中拿到映射关系信息,保存在map里,以便程序运行中可以快速找到路由映射信息实现跳转。
1
2
3
4
5
6
7
8
9
10
11public static RouteMeta build(Type type, Class<?> destination, String path, String group) {
return new RouteMeta(type, null, destination, path, group);
}
private RouteMeta(Type type, Element element, Class<?> destination, String path, String group) {
this.type = type;
this.destination = destination;
this.element = element;
this.path = path;
this.group = group;
}
06.如何得到得到路由表的类名
通过ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE)得到apt生成的所有实现IRouteRoot接口的类文件集合
拿到这些类后,便可以得到所有的routerAddress—activityClass映射关系。
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/**
* 得到路由表的类名
* @param context 上下文
* @param packageName 包名
* @return 集合
* @throws PackageManager.NameNotFoundException
* @throws InterruptedException
*/
public static Set<String> getFileNameByPackageName(Application context,
@NonNull final String packageName)
throws PackageManager.NameNotFoundException, InterruptedException {
//创建一个set集合,set集合元素不会重复
final Set<String> classNames = new HashSet<>();
//获得程序所有的apk
List<String> paths = getSourcePaths(context);
//使用同步计数器判断均处理完成
final CountDownLatch countDownLatch = new CountDownLatch(paths.size());
//创建线程池
ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor
.newDefaultPoolExecutor(paths.size());
for (final String path : paths) {
if (threadPoolExecutor != null) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
DexFile dexFile = null;
try {
//加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类
dexFile = new DexFile(path);
Enumeration<String> dexEntries = dexFile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
if (!TextUtils.isEmpty(className) &&
className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != dexFile) {
try {
dexFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//释放一个
countDownLatch.countDown();
}
}
});
}
}
//等待执行完成
countDownLatch.await();
return classNames;
}