为什么学习开源框架
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, ”hello” | |
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")
}