美文网首页Android架构师
组件化中路由框架学习笔记

组件化中路由框架学习笔记

作者: 秀儿2020 | 来源:发表于2020-08-30 10:37 被阅读0次

    在组件化之前的一种业务业务划分架构是一种单一分层的结构,整个APP是一个Module,不同的业务拆分在不同的包下:

    • 不管分包做的多好,随着项目的增大,项目会失去层次感,导致接受项目时会比较吃力。(臃肿)
    • 包名约束是一种弱约束,一不小心就会导致不同的包名之间相互引用,导致包名之间耦合度高。(耦合度高)
    • 多人联合开发中,在版本管理中很容易出现代码冲突和代码覆盖的问题。(版本管理困难)

    组件化是一个化繁为简的过程,将多个功能模块进行拆分、重组的过程。即将APP按照业务划分为不同的模块,最后在打包为完整的APP时再整合为一起。


    image.png

    如上图所示:可分为APP壳工程、业务组件层、功能组件层和基础库层。

    APP壳工程负责管理各个业务组件和打包APK,没有具体的业务功能。

    业务组件层是根据不同的业务拆分的不同的业务组件。

    功能组件层是为上层提供基础的功能服务。

    基础库中包含了各种开源库以及和业务无关的各种自研工具库。

    比如可以新建项目如下:
    image.png

    切换到 project视图,与app同目录,建立business文件夹,然后在其中建立两个Android Library,分比为food和waimai。

    新建config.gradle文件,在其中定义变量:

    ext {
        //标识是以组件化模式运行还是集成化模式运行,
        // 如果是true,以集成化方式运行,如果是false,以组件化方式运行
        isModule = false
    }
    

    isModule用来标识food是以Android 项目运行还是作为一个普通的Android Module。

    在project 的build.gradle文件中引入该文件:

    apply from : "config.gradle"
    

    分别修改app、food和waimai的build.gradle文件:

    app的build.gradle文件:

    ///根据变量的取值来决定运行方式
    if(rootProject.ext.isModule){
        apply plugin: 'com.android.library'
    }else{
        apply plugin: 'com.android.application'
    }
    
    ....
    
    
        defaultConfig {
            if(!rootProject.ext.isModule){
                applicationId "com.example.zujianhuapro"
            }
            minSdkVersion 16
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
        
    ....
    

    food的build.gradle文件:

    if (rootProject.ext.isModule) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    ...
        defaultConfig {
            if (rootProject.ext.isModule) {
                applicationId "com.example.food"
            }
            minSdkVersion 16
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            consumerProguardFiles 'consumer-rules.pro'
        }
    ...
    

    waimai的同上。

    可自行改变isModule 的值查看效果。

    为什么要使用组件化
    • 各个组件专注自身功能的实现,模块中代码高度内聚,只负责一项任务,也就是常说的单一职责原则。
    • 各业务研发可以互不干扰,提升协作效率。
    • 业务组件可以进行插拔,灵活多变。
    • 业务组件之间将不再直接应用和依赖,各个业务模块组件更加独立,降低耦合。
    • 加快编译速度,提高开发效率。

    最简化最核心的就是动态的切换library和application。

    组件化最需要解决的问题--页面跳转

    路由跳转一般可采用隐式跳转和显示跳转两种方式,但是在组件化结构中,因为不同组件不会相互依赖,所以无法采用显示跳转的方式,只可以采用隐式跳转,但是隐式跳转也存在问题,使用隐式跳转时,必须先在生命文件中用intent-filter来限定隐式Action的启动,其他的Module才可以使用隐式的Action跳转到响应的Activity。但是在组件化中使用这种方式并不友好,不仅多人开发困难,还存在安全隐患。

    而路由组件正是为此而存在的。
    image.png

    首先需要得到目标的页面地址,然后在路由表中寻址,找到目标页后,得到Activity的Class对象,然后启动目标页。

    可以看出,路由组件的关键在于路由表,而路由表就是一系列特定的URL和特定的Activity之间的映射集合,是一个Map结构,key是一个字符串即URL,value是Activity的Class对象。

    路由表需要保证只有一份,所以需要使用单例模式,而路由框架需要被所有的组件依赖到。

    在项目中新建路由Module,如下所示


    image.png

    在app、food和waimai中引入改Module,

        implementation project(':router-api')
    

    在路由Module中新建如下类

    /**
     * 项目名称 zujianhuaPro
     * 创建人 xiaojinli
     * 创建时间 2020/8/29 9:31 AM
     * 路由表,需要被所有的组件依赖,所以需要使用单例模式
     **/
    public class Router {
        private static Router mRouter;
        private static Context mContext;
        //路由表
        private static Map<String,Class<? extends Activity>> routers = new HashMap<>();
        
        public void init(Application application){
            mContext = application;
        }
    
        public static Router getInstance(){
            if(mRouter == null){
                synchronized (Router.class){
                    if(mRouter == null){
                        mRouter = new Router();
                    }
                }
            }
            return mRouter;
        }
        
        //向路由表中注册一个Activity
        public void register(String path,Class<? extends Activity> cls){
            routers.put(path,cls);
        }
    
        //启动路由表中的一个Activity
        public void startActivity(String path){
            Class<? extends Activity> cls = routers.get(path);
            if(cls == null){
                return;
            }
            Intent intent = new Intent(mContext,cls);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(intent);
        }
    }
    

    关键方法有三个,一个是实现单例模式方法,因为需要被所有的业务Module引用,所以需要保持唯一性。二是向路由表中注册一个页面的register方法,三是启动一个路由表中页面的方法。

    使用时如下所示:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Router.getInstance().init(getApplication());
    
            Router.getInstance().register("/food/FoodActivity", FoodActivity.class);
            Router.getInstance().register("/waimai/WaiMaiActivity", WaiMaiActivity.class);
    
        }
    
        public void jumpToFood(View view) {
            Router.getInstance().startActivity("/food/FoodActivity");
        }
    }
    

    在壳Activity中初始化路由组件以及注册页面信息,就可实现跳转。

    在food组件中跳转到waimai组件可使用如下方式

    Router.getInstance().startActivity("/waimai/WaiMaiActivity");
    

    在waimai组件中跳转到food组件可使用如下方式

    Router.getInstance().startActivity("/food/FoodActivity");
    

    但是这种方式存在一个明显的缺点,即我们需要自己在路由表中注册所有的页面,不易维护。

    下面我们会通过APT来进行优化。

    什么是APT

    APT是注解处理器,是javac处理注解的一种工具,他用来在编译时扫描和处理注解,简单来说就是在编译期间,通过注解采集信息,生成.java文件,减少重复代码的编写。很多框架都是用了APT,包括Butterknife和Glide等。

    通过上面的路由表可以看出,我们需要的关键信息是Activity的一个字符串参数以及Activity的Class对象,我们都可以通过注解来采集到。

    创建JavaLib,依赖下面的依赖监控到编译期

        //构建  -----》》》》----【编译时期】------》》》打包-------》》》安装
        // As-3.4.1  +  gradle-5.1.1-all + auto-service:1.0-rc4
        //在编译器可以通过以下依赖让我们自定义的AbstractProcessor工作,即可以通过这两个服务让我们在编译器做一些事情
        compileOnly'com.google.auto.service:auto-service:1.0-rc4'
        annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
    

    首先我们需要创建一个JavaLib,来创建一个注解

    @Target(ElementType.TYPE) //作用在类上
    @Retention(RetentionPolicy.CLASS) //保留到CLASS,因为我们在编译器需要,所以需要保留到Class
    public @interface Route {
        String value(); //详细路径名,比如/main/MainActivity,接收一个字符串参数
        String group() default "";//路由组名,比如main
    }
    

    其次我们需要再定义一个JavaLib,来创建一个处理注解的类,需要继承AbstractProcessor,默认实现process方法。需要注意添加如下注解:

    //自动注册,以便该类可以在编译器干活
    @AutoService(Processor.class)
    //允许支持的注解类型,让注解处理器处理
    @SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE)
    //指定JDK编译版本,必须写
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    //注解处理器接收的参数
    @SupportedOptions({ProcessorConfig.OPTIONS,ProcessorConfig.APT_PACKAGE})
    

    此外我们需要在其他的Module中引入该处理器Module,注意需要使用annotationProcessor关键字

    annotationProcessor project(path: ':route_compile')
    

    待一切都准备完成之后,点击锤子符号即Make Project按钮,如果一切都没有问题,处理器文件夹下会出现build文件夹,在其中可以找到如下类,意味着注解处理器配置成功。

    image.png

    此时当我们项目进入编译器时,会在APP工程中扫描引入该注解处理器module的组件中的类,查看是否用使用了Route注解。下面就是在我们自定义的注解处理器内添加相应 的逻辑生成需要的java文件。

    完整的项目地址:

    https://github.com/lxj-helloworld/zujianhuaPro

    相关文章

      网友评论

        本文标题:组件化中路由框架学习笔记

        本文链接:https://www.haomeiwen.com/subject/hmossktx.html