Android架构师

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

2020-08-30  本文已影响0人  秀儿2020

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

上一篇下一篇

猜你喜欢

热点阅读