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

在组件化之前的一种业务业务划分架构是一种单一分层的结构,整个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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344