Android组件化开发(一)

697 阅读10分钟

为什么学习开源框架

1.学习优秀开源框架 设计思想 2.学习优秀开源框架 实现的技术 3.实战手写 优秀开源框架 来深刻理解

早期的单一分层模式

  • 问题一:无论分包怎么做,随着项目增大,项目失去层次感,后面接手的人扑街
  • 问题二:包名约束太弱,稍有不注意,就会不同业务包直接互相调用,代码高耦合
  • 问题三:多人开发在版本管理中,容易出现代码覆盖冲突等问题

所有的代码都写在app模块中不同的包里面

1.什么是组件化,为什么需要组件化

组件化的意义:不相互依赖,可以相互交互,任意组合,高度解耦,自由拆卸,自由组装,重复利用,分层独立化

此时:app不在是老大,子模块都是小弟

组件化后,所有的module都平起平坐,有人说app的module在组件化中是个壳子,这也是为什么成为 app壳工程的原因

同学们思考,如果没有组件化,还是单一:

  • 1.彻底砍掉order子模块,需要多少工作?
  • 2.如何给项目增 加一个子模块(钱包)? ........

2.集成环境/组件环境自动部署配置。

环境切换和独立运行

  • 我们在项目的开发过程 中,一定要去优化我们的gradle文件,例如:把公用的内容抽取
  • 项目的开发过程中,关于一切与 “正式环境”,“测试环境” 相关的应该用Gradle进行配置

组件化需要考虑的点:

原理

Phone Module和Android Library区别、切换:

Gradle配置

1.抽离公共管理:manager.gradle

// Groovy语言 面向对象 if  for

// 扩展快
ext {

    // 现在 Gradle
    // 正式环境 和 测试环境
    isRelease = false

    // 正式环境 和 测试环境 服务器 URL 配置
    url = [
            "debug"  : "https://192.188.22.99/debug",
            "release": "https://192.188.22.99/release"
    ]

    // 建立Map存储,  key 和 value  都是自定义的
    androidID = [
            compileSdkVersion        : 30,
            buildToolsVersion        : "30.0.1",

            applicationId            : "com.derry.derry",
            minSdkVersion            : 16,
            targetSdkVersion         : 30,
            versionCode              : 1,
            versionName              : "1.0",

            testInstrumentationRunner: "androidx.test.runner.AndroidJUnitRunner"
    ]

    // 建立Map存储,  key 和 value  都是自定义的
    appID = [
            app: "com.derry.modularproject",
            login: "com.derry.login",
            register: "com.derry.register"
    ]

    // 300 行  MAP  key  value
    dependenciesID = [
            "appcompat"       : "androidx.appcompat:appcompat:1.2.0",
            "constraintlayout": "androidx.constraintlayout:constraintlayout:2.0.1",
            "material"        : "com.google.android.material:material:1.1.0",
            "vectordrawable"  : "androidx.vectordrawable:vectordrawable:1.1.0",
            "fragment"        : "androidx.navigation:navigation-fragment:2.2.2",
            "ui"              : "androidx.navigation:navigation-ui:2.2.2",
            "extensions"      : "androidx.lifecycle:lifecycle-extensions:2.2.0",
    ]

}

2.工程项目的build.gradle

// 根目录下的build.gradle 引入   公共的一份 引入过来
apply from : 'manager.gradle'

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.1"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

3.app.gradle

apply plugin: 'com.android.application'

println("Derry ---> app Student hao 1")

println "Derry ---> app Student hao 2"

// 完整的方式  性能
def androidID = rootProject.ext.androidID

android {
    compileSdkVersion androidID.compileSdkVersion
    buildToolsVersion androidID.buildToolsVersion

    defaultConfig {
        applicationId appID.app
        minSdkVersion androidID.minSdkVersion
        targetSdkVersion androidID.targetSdkVersion
        versionCode androidID.versionCode
        versionName androidID.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        // 让我的Java代码也可以用
        // 给Java代码暴漏,标记,正式环境 和 测试环境 的标记
        // 组件化 和 集成化 的时候需要
        buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
    }

    buildTypes {
        debug {
            buildConfigField("String", "debug", "\"${url.debug}\"")
        }
        release {
            buildConfigField("String", "release", "\"${url.release}\"")

            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    // 300 行
   /* implementation "androidx.appcompat:appcompat:1.2.0"
    implementation "androidx.constraintlayout:constraintlayout:2.0.1"
    implementation "com.google.android.material:material:1.1.0"
    implementation "androidx.vectordrawable:vectordrawable:1.1.0"
    implementation "androidx.navigation:navigation-fragment:2.2.2"
    implementation "androidx.navigation:navigation-ui:2.2.2"
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"*/

    // 一行搞定300行 循环搞定
    dependenciesID.each {k,v -> implementation v}

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    if (isRelease) {
        // 依附app壳
        implementation project(':login')
        implementation project(':register')
    } else {
        // 不能依附,因为 login register 都能独立运行啊,依附不了,否则报错
    }
}

4.library模块的build.gradle

apply plugin: 'com.android.library'

println "Derry ---> lib Student hao 2"

android {
    compileSdkVersion androidID.compileSdkVersion
    buildToolsVersion androidID.buildToolsVersion

    defaultConfig {
        minSdkVersion androidID.minSdkVersion
        targetSdkVersion androidID.targetSdkVersion
        versionCode androidID.versionCode
        versionName androidID.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    /*implementation "androidx.appcompat:appcompat:1.2.0"
    implementation "androidx.constraintlayout:constraintlayout:2.0.1"
    implementation "com.google.android.material:material:1.1.0"
    implementation "androidx.vectordrawable:vectordrawable:1.1.0"
    implementation "androidx.navigation:navigation-fragment:2.2.2"
    implementation "androidx.navigation:navigation-ui:2.2.2"
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"*/

    // 一行搞定300行 循环搞定
    dependenciesID.each {k,v -> implementation v}

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}

5.某个业务模块的build.gradle

// apply plugin: 'com.android.application'
if (isRelease) { // 如果是发布版本时,各个模块都不能独立运行
    apply plugin: 'com.android.library' // 正式环境  library不能独立运行
} else {
    apply plugin: 'com.android.application' // 测试环境 application独立运行
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.1"

    defaultConfig {
        // applicationId "" // 有appid 能够独立运行

        if (!isRelease) {  // 能够独立运行 必须要有appID
            applicationId appID.login // 组件化模式能独立运行才能有applicationId
        }

        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            if (!isRelease) {
                // 如果是组件化模式,需要单独运行时 Debug
                manifest.srcFile 'src/main/debug/AndroidManifest.xml' // 生效
            } else { // 正式环境下
                // 集成化模式,整个项目打包apk
                manifest.srcFile 'src/main/AndroidManifest.xml' // 让我们之前 默认的路径下的清单文件再次生效

                java {
                    // release 时 debug 目录下文件不需要合并到主工程
                    exclude "**/debug/**"
                }
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}

3.组件化中 子模块交互方式(非ARouter版)。

组件化之间通信的方式

方式一 使用 EventBus的方式,缺点是:EventBean维护成本太高,不好去管理:

方式二 使用广播的方式,缺点是:不好管理,都统一发出去了

方式三 使用隐士意图方式,缺点是:在AndroidManifest.xml里面配置xml写的太多了

方式四 使用类加载方式,缺点就是,容易写错包名类名,缺点较少(我们尝试写写这种方式

 // todo 方式一 类加载
// 类加载跳转,可以成功。维护成本较高且容易出现人为失误
try {
        Class targetClass = Class.forName("com.xiangxue.personal.Personal_MainActivity");
        Intent intent = new Intent(this, targetClass);
        intent.putExtra("name", "derry");
        startActivity(intent);
} catch (ClassNotFoundException e) {
        e.printStackTrace();
}

方式五 使用全局Map的方式,缺点是,要注册很多的对象(我们尝试写写这种方式

// personal/Personal_MainActivity getMap
// todo 方式二 全局Map
Class<?> targetActivity =
RecordPathManager.startTargetActivity("personal", "Personal_MainActivity");
startActivity(new Intent(this, targetActivity));
public class RecordPathManager {

    /**
     * 先理解成 仓库
     * group: app,order,personal
     *
     * order:
     *      OrderMainActivity1
     *      OrderMainActivity2
     *      OrderMainActivity3
     */
    private static Map<String, List<PathBean>> maps = new HashMap<>();

    /**
     * 将路径信息加入全局Map
     *
     * @param groupName 组名,如:"personal"
     * @param pathName  路劲名,如:"Personal_MainActivity"
     * @param clazz     类对象,如:Personal_MainActivity.class
     */
    public static void addGroupInfo(String groupName, String pathName, Class<?> clazz) {
        List<PathBean> list = maps.get(groupName);

        if (null == list) {
            list = new ArrayList<>();
            list.add(new PathBean(pathName, clazz));
            // 存入仓库
            maps.put(groupName, list);
        } else {
            // 存入仓库
            maps.put(groupName, list);
        }
        // maps.put(groupName, list);
    }

    /**
     * 只需要告诉我,组名 ,路径名,  就能返回 "要跳转的Class"
     * @param groupName 组名 oder
     * @param pathName 路径名  OrderMainActivity1
     * @return 跳转目标的class类对象
     */
    public static Class<?> startTargetActivity(String groupName, String pathName) {
        List<PathBean> list = maps.get(groupName);
        if (list == null) {
            Log.d(Config.TAG, "startTargetActivity 此组名得到的信息,并没有注册进来哦...");
            return null;
        }
        // 遍历 寻找 去匹配 “PathBean”对象
        for (PathBean pathBean : list) {
            if (pathName.equalsIgnoreCase(pathBean.getPath())) {
                return pathBean.getClazz();
            }
        }
        return null;
    }
}

先注册再获取

;

分析组件化与模块化之间的区别:www.jianshu.com/p/cac0beae8…

4.APT(Annotation Processing Tool)

APT是什么?

是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行处理。也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器才能执行。

通俗理解:根据规则,帮我们生成代码、生成类文件

APT中用到的重要元素

PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问 ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例) TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。 VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数

APT中用到API

属性名说明
getEnclosedElements()返回该元素直接包含的子元素
getEnclosingElement()返回包含该element的父element,与上一个方法相反
getKind()返回element的类型,判断是哪种element
getModifiers()获取修饰关键字,入public static final等关键字
getSimpleName()获取名字,不带包名
getQualifiedName()获取全名,如果是类的话,包含完整的包名路径
getParameters()获取方法的参数元素,每个元素是一个VariableElement
getReturnType()获取方法元素的返回值
getConstantValue()如果属性变量被final修饰,则可以使用该方法获取它的 值

APT技术

APT 编译的时候 ---> 处理注解 APT 传统方式 ---> 生成 java文件 APT JavaPoet方式 --> 生成Java文件

传统方式 那些 开源项目有用到? 答:看看EventBus源码就知道了 传统方式:优点(编程的流程写下去) 缺点(没有oop思想加入进来

5.高级用法JavaPoet

JavaPoet是什么?

  • JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件

  • 这个框架功能非常实用,也是我们习惯的Java面向对象OOP语法

  • 可以很方便的使用它根据注解生成对应代码

  • 通过这种自动化生成代码的方式

  • 可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作

项目主页及源码:github.com/square/java…

JavaPoet相关

类对象说明
MethodSpec代表一个构造函数或方法声明
TypeSpec代表一个类,接口,或者枚举声明
FieldSpec代表一个成员变量,一个字段声明
JavaFile包含一个顶级类的Java文件
ParameterSpec用来创建参数
AnnotationSpec用来创建注解
ClassName用来包装一个类
TypeName类型,如在添加返回值类型是使用 TypeName.VOID
S字符串,如:S 字符串,如:S, ”hello”
T类、接口,如:T 类、接口,如:T, MainActivity

高级用法JavaPoet

JavaPoet到底是什么? oop思想方式:优点(加入oop思想) 缺点(不习惯,倒序)

JavaPoet真的比传统方式好吗? 并不是这样的,如果复杂的代码生成,反而效率低下 发展趋势,oop思想,真正的掌握JavaPoet,爱不释手

6.组件化项目部署,ARouter原理

compiler的build.gradle

compiler是一个java工程

apply plugin: 'java-library'

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

    // 背后的服务 能够监听 你是否在编译中.....
    // AS3.4.1 + Gradle 5.1.1 + auto-service:1.0-rc4
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

    // 帮助我们通过类调用的形式来生成Java代码 [JavaPoet]
    implementation "com.squareup:javapoet:1.9.0"

    // 依赖注解
    implementation project(":arouter-annotations")



}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

注解ARouter

@Target(TYPE) // 类上
@Retention(CLASS) // 编译期    XUtil==运行期
public @interface ARouter {

    String path();

    String group() default "";

}

生成代码

package com.derry.compiler;

import com.derry.arouter_annotations.ARouter;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

@AutoService(Processor.class) // 启用服务
@SupportedAnnotationTypes({"com.derry.arouter_annotations.ARouter"}) // 注解
@SupportedSourceVersion(SourceVersion.RELEASE_7) // 环境的版本


// 接收 安卓工程传递过来的参数
@SupportedOptions("student")

public class ARouterProcessor extends AbstractProcessor {

    // 操作Element的工具类(类,函数,属性,其实都是Element)
    private Elements elementTool;

    // type(类信息)的工具类,包含用于操作TypeMirror的工具方法
    private Types typeTool;

    // Message用来打印 日志相关信息
    private Messager messager;

    // 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        elementTool = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();

        String value = processingEnvironment.getOptions().get("student");
        // 这个代码已经下毒了
        // 如果我想在注解处理器里面抛出异常 可以使用Diagnostic.Kind.ERROR
        messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>>>>>>>>"+value);
    }

    // 服务:在编译的时候干活
    // 坑:如果没有在任何地方使用,次函数是不会工作的
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 这个代码已经下毒了
        messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>> Derry run...");

        if (set.isEmpty()) {
            return false; // 不干活
        }

        // 循环?
        // 获取被 ARouter注解的 "类节点信息"
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        for (Element element : elements) { // for 3    // 1 element == MainActivity    2 element == MainActivity2

            /**
             模块一
             package com.example.helloworld;

             public final class HelloWorld {

             public static void main(String[] args) {
                 System.out.println("Hello, JavaPoet!");
             }
             }

             */
            // Java 万物皆对象
            // C  万物皆指针
            /*// 1.方法
            MethodSpec mainMethod = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class, "args")

                    // 增加main方法里面的内容
                    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")

                    .build();

            // 2.类
            TypeSpec testClass = TypeSpec.classBuilder("DerryTest")
                    .addMethod(mainMethod)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .build();

            // 3.包
            JavaFile packagef = JavaFile.builder("com.xiangxue.test", testClass).build();

            // 生成文件
            try {
                packagef.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
                messager.printMessage(Diagnostic.Kind.NOTE, "生成Test文件时失败,异常:" + e.getMessage());
            }*/

            // 包信息
            String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();

            // 获取简单类名,例如:MainActivity  MainActivity2  MainActivity3
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被@ARetuer注解的类有:" + className);

            // String className = element.getSimpleName().toString();

            // 目标:要生成的文件名称  MainActivity$$$$$$$$$ARouter
            String finalClassName = className + "$$$$$$$$$ARouter";

            /**
             模板:
             public class MainActivity3$$$$$$$$$ARouter {

                public static Class findTargetClass(String path) {
                    return path.equals("/app/MainActivity3") ? MainActivity3.class : null;
                }

             }
             */

            ARouter aRouter = element.getAnnotation(ARouter.class);

            // 1.方法
            MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(Class.class)
                    .addParameter(String.class, "path")
                    // 方法里面的内容 return path.equals("/app/MainActivity3") ? MainActivity3.class : null;

                    // 需要JavaPoet包装转型
                    .addStatement("return path.equals($S) ? $T.class : null",
                            aRouter.path(),
                            ClassName.get((TypeElement) element))
                    .build();

            // 2.类
            TypeSpec myClass = TypeSpec.classBuilder(finalClassName)
                    .addMethod(findTargetClass)
                    .addModifiers(Modifier.PUBLIC)
                    .build();

            // 3.包
            JavaFile packagef = JavaFile.builder(packageName, myClass).build();

            // 开始生成
            try {
                packagef.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
                messager.printMessage(Diagnostic.Kind.NOTE, "生成" + finalClassName + "文件时失败,异常:" + e.getMessage());
            }
        }

        return true; // false不干活了      true干完了
    }
}

app的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.1"

    defaultConfig {
        applicationId "com.derry.new_modular_javapoet"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        // 传递参数
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [student: 'hello ni hao student javapoet']
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation project(path: ':arouter-annotations')
    implementation project(path: ':arouter-annotations')
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    // 依赖注解
    implementation project(":arouter-annotations")

    // 依赖注解处理器 注解处理器才能工作
    annotationProcessor project(":compiler")
}