手写实现ARouter

组件化

可以理解为解耦复杂系统时将多个功能模块拆分,重组的过程。在Android工程中,表现就是把app按照其业务的不同,划分为不同的Module。举例一点,比如我在开发58同城时,具有很多个业务,房产,招聘,新车,每一个业务线都是独立的,可以单独运行,相互之间并不依赖,但是在平台中,可以做到横向的互相跳转。所以组件化有点有很多:

  • 1.各组件只关系自己功能开发,模块内代码高度聚合,只负责一项任务;
  • 2.业务之间不干扰,提升协作的效率;
  • 3.业务组件可插拔,比如招聘,可以随时集成到58同镇平台;
  • 4.业务之间不直接依赖,各个业务更独立,降低了耦合;
  • 5.可以单独编译,加快编译速度,提高开发效率;
  • 6.。。。。。。

路由

ARouter,阿里开源,一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

手写实现简单ARouter

项目目录

router实现

具体原理,就是在我们router有一个单例,这个单例保存了一个Map,而这个map存了key-value,key是我们的自定义的字符串,value就是我们的Activity.class。然后我们的业务模块就可以把自己的Activity,put到这个map里,而其他的业务就可以根据key拿到Activity,从而就可以实现Intent跳转了。路由代码:

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
private static volatile CRouter mInstance;
// 存储所有的注册Activity
private static Map<String,Class<? extends Activity>> routers = new HashMap<>();

private CRouter(){
}
public static CRouter getInstance(){
if (mInstance == null){
synchronized (CRouter.class){
if (mInstance == null){
mInstance = new CRouter();
}
}
}
return mInstance;
}

public static void init(Application application){
// new JobRouter().loadRouters(routers);
// new CarRouter().loadRouters(routers);
//这里可以用ClassLoader和DexFile便利拿到所有com.example.router包下的类,再通过反射,拿到Router并调用loadRouters注册所有Activity,// 就不可以不用手动执行new JobRouter().loadRouters(routers);当然我们也拿不到JobRouter,CarRouter;
try {
Set<String> classNames = ClassUtils.getFileNameByPackageName(application,"com.example.routers");
for (String clsName : classNames) {
Class<?> cls = Class.forName(clsName);
if (IRouteLoad.class.isAssignableFrom(cls)){
IRouteLoad iRouteLoad = (IRouteLoad)cls.newInstance();
iRouteLoad.loadRouters(routers);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

public void startActivity(Activity activity,String path){
Class<? extends Activity> cls = routers.get(path);
if (cls != null){
Intent intent = new Intent(activity,cls);
activity.startActivity(intent);
}
}
public void register(String path,Class<? extends Activity> cls ){
routers.put(path,cls);
}
}

工具类如下:

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
public class ClassUtils {
//获取程序所欲apk
public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException {
ApplicationInfo applicationInfo = context.getPackageManager()
.getApplicationInfo(context.getPackageName(),0);
List<String> sourcePaths = new ArrayList<>();
//当前应用的apk
sourcePaths.add(applicationInfo.sourceDir);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
if (null != applicationInfo.splitSourceDirs){
sourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs));
}
}
return sourcePaths;
}
public static Set<String> getFileNameByPackageName(Application context, String packagename) throws PackageManager.NameNotFoundException{
Set<String> classNames = new HashSet<>();
List<String> paths = getSourcePaths(context);
for (String path: paths){
DexFile dexFile = null;
try{
//加载apk中dex,并便利获得所有的packageName的类
dexFile = new DexFile(path);
Enumeration<String> dexEntries = dexFile.entries();
//遍历整个apk中的所有类
while (dexEntries.hasMoreElements()){
String className = dexEntries.nextElement();
if (className.startsWith(packagename)){
classNames.add(className);
}
}
}catch (Exception e){

}finally {
if (null!= dexFile){
try{
dexFile.getClass();
}catch (Exception e){

}
}
}
}
return classNames;
}
}

业务模块注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 车业务
package com.example.router;

public class CarRouter implements IRouteLoad {
public void loadRouters(Map<String,Class<? extends Activity>> routers){
routers.put("/path/car", CarActivity.class);
}
}

// 招聘业务
package com.example.router;

public class JobRouter implements IRouteLoad {
public void loadRouters(Map<String,Class<? extends Activity>> routers){
routers.put("/path/job", JobActivity.class);
}
}

注册好了,这样我们就可以跳转了:在车跳转到招聘业务,完成:

1
CRouter.getInstance().startActivity(this,"/path/job");

然后我们发现,注册的时候,需要每个业务线,都要手动注册put自己的key-value;而且注册的保命,类名不一样外,其他的代码实现都差不多。那有没有一种方式,动态生成呢?

APT注解

注解的两个实现模块,都不能是android module。
注解类:

1
2
3
4
5
6
7
// 用在什么地方
@Target(ElementType.TYPE)
//注解保留到:源码
@Retention(RetentionPolicy.SOURCE)
public @interface CRouterAnnotation {
String value();// 我们需要的时字符串
}

自定义注解实现:自动生成的类,是什么?就是一个.java文件.所以我们其实就是拿到内容,生成一个文件。
写入依赖:

1
2
3
4
5
6
7
dependencies {
implementation "com.google.auto.service:auto-service:1.0-rc7"
annotationProcessor "com.google.auto.service:auto-service:1.0-rc7"
implementation 'com.squareup:javapoet:1.13.0'

implementation project(":apt-annotation")
}

具体实现:

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
@AutoService(Processor.class)
//指定我们自己的注解处理器:CRouterAnnotation,全路径
@SupportedAnnotationTypes("com.example.apt_annotation.CRouterAnnotation")
@SupportedOptions("AROUTER_MODULE_NAME")
public class RouterProcessor extends AbstractProcessor {
private Messager messager;//打印日志
private Filer filer;//文件持有,管理者
private String moduleName;//业务线的名称
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
Map<String,String> options = processingEnv.getOptions();
moduleName = options.get("AROUTER_MODULE_NAME");
}

@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
//使用了@CRouterAnnotation注解类的节点的集合,包括包名、类名、属性、方法等节点。
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(CRouterAnnotation.class);
//拼装类内容
StringBuilder sb = new StringBuilder("package com.example.routers;" +
"\n import android.app.Activity; \n");
for (Element element : elements){
TypeElement typeElement = (TypeElement)element;//获取类信息
sb.append("import ");
sb.append(typeElement.getQualifiedName());// com.example.mycar.CarActivity
sb.append(";\n");
}
sb.append( "import com.example.myrouteapi.IRouteLoad;\n");
sb.append("import java.util.Map;\n");
sb.append("public class "+ moduleName+ "Router implements IRouteLoad {\n");
sb.append(" public void loadRouters(Map<String,Class<? extends Activity>> routers){\n");

for (Element element : elements){
CRouterAnnotation cRouterAnnotation = element.getAnnotation(CRouterAnnotation.class);
String value = cRouterAnnotation.value();
Name clsName = element.getSimpleName();
sb.append("routers.put(\""+value+ "\",").append(clsName+".class);\n");
}
sb.append(" }\n");
sb.append("}\n");



messager.printMessage(Diagnostic.Kind.NOTE,"moduleName--------:"+moduleName);
try {
//生成文件
JavaFileObject sourceFile = filer.createSourceFile("com.example.routers."+moduleName+"Router");
OutputStream os = sourceFile.openOutputStream();
os.write(sb.toString().getBytes());
os.close();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}

注意模块名称需要配置:AROUTER_MODULE_NAME,就是RouterProcessor中需要的类名前缀。这个是在模块业务中配置的:

1
2
3
4
5
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}

还需要配置依赖:

1
2
3
4
5
dependencies {
implementation project(':myrouteapi')
implementation project(':apt-annotation')
annotationProcessor project(":apt-processor")
}

设置协议key:”/path/job”

1
2
3
@CRouterAnnotation("/path/job")
public class JobActivity extends AppCompatActivity {
}

最终在各业务模块生成:

OK,实现完成。

AutoService是什么?RouterProcessor是怎么执行的?看我写的SPI原理

总结

其实如上只是简单的实现了ARouter,还有很多改进的地方,比如可以再次基础上根据业务改进我们的框架;比如我们可以通过Gradle 字节码插桩提升我们的初始化速度,通过URI实现我们的跳转配置,增加协议的参数等等。

参考资料

ARouter中文文档
组件化视频