Android组件化架构 —— 基础(三) - ARouter

xwzz.jpg

前篇回顾

  • Android组件化 —— 基础(一) - 组件化与集成化
  • Android组件化 —— 基础(二) - 组件间通讯

上篇文章,我们了解到“路由”是如何进行工作的,并尝试手写了实现代码;

本篇,以ARouter为例,我们将剖析它是如何完成工作的,并通过源码了解其原理。

还是以Activity跳转Fragment获取跨模块功能调用三个方向为出发点,看看ARouter将如何实现这些功能。

集成ARouter

咱们先快速将ARouter集成到项目中,当然你也可以参照官方文档来操作。

在第一篇文章中,我们将业务模块的依赖关系集中到了config.gradle文件中,现修改如下:

  • 1、config.gradle中管理依赖版本
ext { 
    // ARouter 版本号
    arouter_api_version = "1.5.2"
    arouter_compiler_version = "1.5.2"

    //各模块引入的第三方公共库 
    dependenciesImport = [
            ...
            ...
            arouter_api     : "com.alibaba:arouter-api:$arouter_api_version"
    ]

    //注解处理器 
    dependenciesKapt = [
            ...
            ...
            arouter: "com.alibaba:arouter-compiler:$arouter_compiler_version"
    ]
}
  • 2、子模块的build.gradle中,传递APT所需参数,并引入依赖
def dependenciesImport = rootProject.ext.dependenciesImport
def dependenciesKapt = rootProject.ext.dependenciesKapt

android {
    defaultConfig {
        ...
        ...
        // ARouter APT 传参
        kapt {
            arguments {
                arg("AROUTER_MODULE_NAME", project.getName())
            }
        }   
    }
}

dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation project(":lib_comm")

    // 导入依赖库
    dependenciesImport.each { k, v -> implementation(v) }
    // 导入依赖的注解处理器
    dependenciesKapt.each { k, v -> kapt(v) }
}
  • 3、初始化ARouter
class App : Application() {

    override fun onCreate() {
        super.onCreate()
        initRouter()
    }

    private fun initRouter() {
        if (BuildConfig.DEBUG) {
            ARouter.openLog()   // 打印日志
            ARouter.openDebug() // 开启调试模式
        }
        ARouter.init(this) // 初始化
    }
}
  • 4、运行查看Log记录:
2021-09-13 17:47:07.911 10088-10088/com.ljb.component I/ARouter::: ARouter init success![ ] 

ARouter - Activity跳转

根据官方文档,只需2步便可完成功能:

  • 1、注解标记需要跳转的Activity
@Route(path = "/user/UserMainActivity" , group =  "user")
class UserMainActivity : AppCompatActivity() {
      ...
}
  • 2、发起跳转
ARouter.getInstance().build("/user/UserMainActivity").navigation()

很神奇!与前篇我们实现的路由相比,ARouter的代码要简洁很多,完全不需要手动注册路由就可完成跳转,它是怎么做到的呢?

通过跟进navigation()函数调用过程,我们把目光聚焦到两个容器中:

// ARouter源码
class Warehouse {
    // Cache route and metas
    static Map> groupsIndex = new HashMap<>();
    static Map routes = new HashMap<>();
    
    ...
}
  • Warehouse.groupsIndex: 用于存储所有的路由组
public interface IRouteGroup {
    /**
     * Fill the atlas with routes in group.
     * atlas用于存储当前组里的所有路由,实际传入的就是Warehouse.routes
     */
    void loadInto(Map atlas);
}
  • Warehouse.routes:用于存储已注册的所有路由
 // 路由包装类,路由目标的Class对象就存储在这里面
public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map paramsType;  // Param type
    private String name;
    
    ...
}

ARouter对路由提出了分组概念,上面 UserMainActivity就属于user组下,当路由path存在2级及以上时,group字段也可以省略,ARouter默认会使用第一个反斜杠后面的path作为组名。

// group可省略不写
@Route(path = "/user/UserMainActivity")
class UserMainActivity : AppCompatActivity() {
      ...
}

一般情况下,我们会将同一模块的路由划分在同一个组下,例如App模块下的所有路由都在“app”这个分组下 , user模块的路由都在“user”分组下;当然,同一模块拥有多个分组也是完全可行的,只要保证与其它模块中的路由分组不重名即可。

通过翻阅源码,分析这两个容器的作用,大致如下:

  • 1、当传入path进行跳转时,优先从Warehouse.routes中直接获取路由对象;
  • 2、路由对象不存在,就需要通过Warehouse.groupsIndex路由组来完成注册功能;
  • 3、注册成功后,当前path所在组的所有路由都将存储到Warehouse.routes中;
  • 4、回到第1步,获取路由对象;
  • 5、读取路由对象信息;
  • 6、完成跳转。

下方是ARouter源码中的实现代码,我把不重要的部分进行了删减:

//ARouter源码 
public class LogisticsCenter {
    ...

    public synchronized static void completion(Postcard postcard) {
        
        //1、当传入path进行跳转时,优先从Warehouse.routes中直接获取路由包装对象;
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

        if (null == routeMeta) {
            
            //2、包装对象不存在,就需要通过Warehouse.groupsIndex路由组来完成注册功能;
            addRouteGroupDynamic(postcard.getGroup(), null);
            
            //4、回到第1步,获取包装对象;
            completion(postcard);   
        } else {
            // 5、读取包装对象信息;
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            
            ...
        }
    
    
    public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        
        if (Warehouse.groupsIndex.containsKey(groupName)){
            //3、注册成功后,当前path所在组的所有路由都将存储到Warehouse.routes中;
            Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
            Warehouse.groupsIndex.remove(groupName);
        }


        if (null != group) {
            // 注册本组路由
            group.loadInto(Warehouse.routes);
        }
    }

    ...
}

final class _ARouter {

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

            LogisticsCenter.completion(postcard);
            
            ...         
            // 6、完成跳转。
            switch (postcard.getType()) {
                case ACTIVITY:
                    // Build intent
                    final Intent intent = new Intent(currentContext, postcard.getDestination());
                    intent.putExtras(postcard.getExtras());
                    
                    //...
                    // Navigation in main looper.
                    runInMainThread(new Runnable() {
                        @Override
                        public void run() {
                            startActivity(requestCode, currentContext, intent, postcard, callback);
                        }
                    });
                    break;
                
                ...
            }
        ...
    
    }
}

整个跳转的源代码过程不难理解,但关键点在于:

  • Warehouse.groupsIndex容器是何时将路由组加载至内存的?
  • group.loadInto(Warehouse.routes)又是如何完成注册的?

回答这两个问题,需回过头来看ARouter初始化都做了什么。

 ARouter.init(this)   // ARouter初始化

随着代码的跟进,我们会发现下面这几行代码:

//ARouter源码 
public final class Consts {

  public static final String SDK_NAME = "ARouter";
  public static final String SEPARATOR = "$$";
  public static final String SUFFIX_ROOT = "Root";
  public static final String DOT = ".";
  public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
}
  
public class LogisticsCenter {

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    
            // 获取com.alibaba.android.arouter.routes下的所有class文件
            routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
            
    
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {

                    // 将名为ARouter$$Root开头的Class对象创建出来,并调用loadInto(),将分组信息加载进Warehouse.groupsIndex中
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                }
                ...
            }
    }

}

源码中会从com.alibaba.android.arouter.routes包下读取所有的Class文件,并将文件名以ARouter$$Root为开头的Class文件挑选出来,最后通过反射创建对象,并调用其loadInto()函数,注意这里传入参数正是Warehouse.groupsIndex,我们的路由组容器,从而将分组信息载入Warehouse.groupsIndex中。

果不其然,我们在user模块的build文件夹下找到了这些文件:

ARouter生成的文件.png
  • 关于ARouter$$Root 前缀文件,可以看到其后缀是当前模块名module_user,而其loadInto()函数主要是将user模块下涉及到的所有路由组存储到Warehouse.groupsIndex中;
public class ARouter$$Root$$module_user implements IRouteRoot {

  @Override
  public void loadInto(Map> routes) {
    routes.put("user", ARouter$$Group$$user.class);
  }
}
  • 关于ARouter$$Group前缀文件,其后缀是当前路由所属的的分组名user,前面在梳理Activity跳转的过程中,我们知道当路由未注册时,会通过分组名获取到组,并创建组对象调用其loadInto()函数完成对Warehouse.routes的注册,此文件中编写的既是具体的注册过程。
//3、注册成功后,当前path所在组的所有路由都将存储到Warehouse.routes中;
Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);


public class ARouter$$Group$$user implements IRouteGroup {
  @Override
  public void loadInto(Map atlas) {
    // loadInto被调用时,完成对Warehouse.routes的注册
    atlas.put("/user/AppMainActivity", RouteMeta.build(RouteType.ACTIVITY, AppMainActivity.class, "/user/appmainactivity", "user", null, -1, -2147483648));
  }
}

ARouter - APT技术

通过上述分析,我们知道路由的注册离不开ARouter生成的这两个文件,那这两文件又是怎么来的呢?

这里不得不提到APT技术( Annotation Processing Tool),译:注解处理器。如果对该技术有所耳闻,你应该知道在Android中我们常用的EventBusButterknifeDagger2等框架都有它的身影。如果你是第一次听说也没关系,暂时只需知道通过这门技术可以做到“在编译期时,可使编译器为我们生成具有一定规则的模板性代码”即可。

显然,这两个文件就是ARouter通过APT生成的模板代码,至于具体的实现过程,本篇中不做过多阐述,后续处理自定义路由时,我们会尝试编写一个自己的注解处理器,到时再细聊APT技术的相关细节。
不过我把ARouter注解处理器中生成这两个文件的核心代码贴在下方,看懂它的思路即可:

//ARouter 注解处理器源码 
@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {

   @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {

        // 获取所有被 @Route 注解标记的类元素
        Set routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
        
        //解析这些类元素
        this.parseRoutes(routeElements);
        
    }
    
    
    private void parseRoutes(Set routeElements) throws IOException {
    
        // 解析元素
        for (Element element : routeElements) {
        
            if (types.isSubtype(tm, type_Activity)) {
                // Activity              
                routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);  
                        
                Set routeMetas = groupMap.get(routeMete.getGroup()); 
                routeMetaSet.add(routeMete);
                groupMap.put(routeMete.getGroup(), routeMetaSet);               
            }
        }
        
        // 根据解析元素生成Java文件
        for (Map.Entry> entry : groupMap.entrySet()) {
          // 生成ARouter$$Group$$前缀文件
          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);
        }
        
        // 生成ARouter$$Root$$前缀文件
        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);
    }
}

至此,整个Activity跳转涉及到的源码大致就是这些,总结下ARouter都做了什么:

  • 1、在编译期,ARouter注解处理器将所有@Route注解标记的类查找出来,并根据这些类的相关信息生成ARouter$$相关文件;
  • 2、ARouter.init()初始化时,将ARouter$$Group前缀文件涉及到的路由组存储到Warehouse.groupsIndex中;
  • 3、调用ARouter...navigation()跳转页面时,通过Warehouse.groupsIndex查询路由所在组,将路由组中的所有路由注册到Warehouse.routes中,从Warehouse.routes中取出路由信息(包含Activity.class对象),完成跳转。

Fragment获取

依旧根据文档,获取Fragment歩奏也只需2步:

  • 1、注解标记需要获取的Fragemnt
@Route(path = "/user/UserFragment")
class UserFragment : Fragment() {
    ...
}
  • 2、获取Fragment
val f  = ARouter.getInstance().build("/user/UserFragment").navigation() as Fragment

有了对Activity跳转源码分析的铺垫,Fragment的获取原理基本是一样的,只是在最后一步发生跳转的代码,变为返回Fragment对象:

final class _ARouter {

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        ...         
        // 6、完成跳转。
        switch (postcard.getType()) {
            
            // Activity跳转
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
                
                //...
                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });
                break;
            
            // Fragment获取
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                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;
            ...
        }
        ...
    }
}

跨模块功能调用

ARouter的方案和我们之前的实现的方案原理是一样的,都需将对外的服务暴露到lib_comm中,只是如Activity和Fragment一样注册的功能通过APT技术来实现。

  • 1、lib_comm中定义暴露的接口
interface IUserService2 : IProvider {

    /**
     * 是否登录
     */
    fun isLogin(): Boolean
}
  • 2、业务模块实现接口,并通过@Route注解标记
@Route(path = "/user/UserService")
class IUserServiceImpl2 : IUserService2 {

    override fun init(context: Context?) {

    }

    override fun isLogin(): Boolean {
        return UserUtils.isLogin()
    }

}
  • 3、获取服务
ARouter.getInstance().build("/user/UserService").navigation() as IUserService2

和Fragment类似,只不过在获取时,多了一步缓存操作,缓存相关的核心代码我贴在了下方:

class Warehouse {
    // Cache provider
    static Map providers = new HashMap<>();
}

// 创建服务,并缓存
 switch (routeMeta.getType()) {
    case PROVIDER:  // if the route is provider, should find its instance
        // Its provider, so it must implement IProvider
        Class providerMeta = (Class) routeMeta.getDestination();
        IProvider instance = Warehouse.providers.get(providerMeta);
        if (null == instance) { // There's no instance of this provider
            IProvider provider;
            try {
                provider = providerMeta.getConstructor().newInstance();
                provider.init(mContext);
                // 进行缓存
                Warehouse.providers.put(providerMeta, provider);
                instance = provider;
            } catch (Exception e) {
                logger.error(TAG, "Init provider failed!", e);
                throw new HandlerException("Init provider failed!");
            }
        }
        postcard.setProvider(instance);
        postcard.greenChannel();    // Provider should skip all of interceptors
        break;
}

获取时,只需从postcard.getProvider()中取即可:

final class _ARouter {

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        ...         
        // 6、完成跳转。
        switch (postcard.getType()) {
            
            // Activity跳转
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
                
                //...
                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });
                break;
            
            // Fragment获取
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                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;

            // 获取Service
            case PROVIDER:
                return postcard.getProvider();
            ...
        }
        ...
    }
}

OK,ARouter路由核心三剑客功能已摸了个底朝天,与我们自己实现的路由框架相比,其核心思想是一样的,但在细节上ARouter的处理明显优于我们,例如:

  • 通过APT来实现路由注册,省掉手动注册的繁琐,且无需在App启动时注册所有路由(与我们startup方案相比),仅在使用到某组路由时才进行注册,避免不必要的内存开销。
  • 对外提供功能的Service对象,在使用时进行缓存,避免重复创建。

小结

本篇对ARouter的Activity跳转、Fragment获取、扩模块功能调用花费了大幅篇章来讲解源码,是希望读者能了解其基本原理,后续我在处理自定义路由时,会参考ARouter实现来造轮子,也方便读者对后续篇章的理解。

ARouter还支持很多功能,例如:拦截器、标准Scheme URL跳转、全局降级策略、动态路由注册等,这些功能后续篇章中一部分会讲到,一部分也可能使用不到,但并不代表这些功能没用,每个项目或团队对组件化架构的理解都会有所不同,最后落地的项目结构、路由交互方案也就各具特色。无论是怎样的项目,个人认为:

  • 组件化,它是由实际业务开发过程中,开发人员通过经验积累,设计出的一套适用于多人协作开发的架构模式;
  • 具备可复用性、可替代性、热插拔、可独立调试特性的模块,就是组件化模块(组件);
  • 由多个组件化模块组成的项目架构,就是组件化架构。

下篇,聊聊组件通讯之Scheme URL,我们下篇再见!

Android组件化架构 —— 基础(四) URL Scheme

你可能感兴趣的:(Android组件化架构 —— 基础(三) - ARouter)