一 gradle语法
Gradle环境
1.Android studio配置
classpath'com.android.tools.build:gradle:3.5.0'
2.Gradle配置
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
什么是Gradle?
首先Gradle是一种构建工具,它的出现让工程有无限可能
Gradle核心是基于Groovy脚本语言,Groovy脚本基于Java且拓展了Java,因此Gradle需要依赖JDK和Groovy库
和ant、maven构建有区别,gradle是一种编程思想
如何配置公共Gradle
配置公共Gradle的作用:
1.区别生产环境和测试环境
2.抽离Android信息,便于统一管理
3.统一管理依赖的包
步骤
1.在build.gradle同级目录下新建一个gradle文件
2.通过ext代码块添加字典
ext {
username ="sunjchao"
//生产/开发环境
isRelease =true
//gradle共用配置,建立map存储,对象名,key都可以自定义,groovy糖果语法,非常灵活
androidId = [
compileSdkVersion:29,
buildToolsVersion:"29.0.2",
minSdkVersion :23,
targetSdkVersion :29,
versionCode :1,
versionName :"1.0"
]
appId = [
app :"com.example.myapplication",
library:"com.example.library"
]
url = [
debug :"debug",
release:"release"
]
supportLibrary ="28.0.0"
dependencies = [
"appcomppat":"androidx.appcompat:appcompat:1.1.0",
"recycleview":"com.android.support:recyclerview-v7:${supportLibrary}",
"constraint":"androidx.constraintlayout:constraintlayout:1.1.3",
"design":"com.android.support:design:${supportLibrary}"
]
}
3.根目录下的build.gradle头部加入自定义config.gradle,相当于layout布局中加入include
applyfrom:"config.gradle"
4.定义变量引用公共配置文件
定义 def androidId =rootProject.ext.androidId
引用 compileSdkVersion androidId.compileSdkVersion
buildToolsVersion androidId.buildToolsVersion
dependencies {
implementation fileTree(dir:'libs',include: ['*.jar'])
support.each { k, v -> implementation v }
}
一些Gradle用法
def androidId =rootProject.ext.androidId
def appId =rootProject.ext.appId
def support =rootProject.ext.dependencies
def url =rootProject.ext.url
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"
//开启分包
multiDexEnabledtrue
//设置分包配置
multiDexKeepFile file('multidex-config.txt')
//将svg图片生成指定维度的png图片
vectorDrawables.generatedDensities('xxxhdpi','xxhdpi')
//使用support-v7兼容(5.0版本以上)
vectorDrawables.useSupportLibrary =true
//只保留指定和默认资源
resConfigs('zh-rCN')
ndk {
abiFilters('armeabi-v7a')
}
//源集-设置源集的属性,更改源集的Java目录或者自由目录等
sourceSets {
main {
if (isRelease) {
//如果是组件化模式,需要单独运行时
manifest.srcFile'src/main/AndroidManifest.xml'
java.srcDirs = ['src / main / java']
res.srcDirs = ['src / main / res']
resources.srcDirs = ['src / main / resources']
aidl.srcDirs = ['src / main / aidl']
assets.srcDirs = ['src / main / assets']
}else {
//集成化模式,整个项目打包
manifest.srcFile'src/main/AndroidManifest.xml'
}
}
}
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
//signingConfigs一定要在buildTypes签名
signingConfigs {
debug {
//天坑:填错了,编译不通过还找不到问题
storeFile file("C:/Users/Administrator/.android/debug.keystore")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
release {
//签名文件
storeFile file("D:/Sunjichao/.jks")
//签名证书的类型
storeType ""
//签名证书文件的密码
storePassword ""
//签名证书中的密钥别名
keyAlias ""
//签名证书中该密钥的密码
keyPassword ""
//是否开启V2打包
v2SigningEnabledtrue
}
}
buildTypes {
debug {
//对构建类型设置签名信息
signingConfig signingConfigs.debug
buildConfigField("String","debug","\"${url.debug}\"")
}
release {
signingConfig signingConfigs.release
buildConfigField("String","release","\"${url.release}\"")
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),'proguard-rules.pro'
}
}
//AdbOptions可以对adb操作选项添加配置
adbOptions {
//配置操作超时时间,单位毫秒
timeOutInMs =5 *1000_0
//adb install命令的选项配置
installOptions'-r','-s'
}
//对dx操作的配置,接受一个DexOptions类型的闭包,配置由DexOptions提供
dexOptions {
//配置执行dx命令是为其分配最大的对内存
javaMaxHeapSize"4g"
//配置是否预执行dex Libraries工程,开启后会提高增量构建速度,不过会影响clean构建的速度,默认为true
preDexLibraries =false
//配置是否开启jumbo模式,代码方法是超过65535需要强制开启才能构建成功
jumboModetrue
//配置Gradle运行dx命令时使用的线程数量
threadCount8
//配置multidex参数
additionalParameters = [
'--multi-dex',//多dex分包
'--set-max-idx-number=50000',//每个包内方法数量上限
//'--main-dex-list=' + '/multidex-config.txt',//打包到主classes.dex的文件列表
'--minimal-main-dex'
]
}
//执行gradle lint命令即可运行lint检查,默认生成的报告在outputs/lint-results.html中
lintOptions {
//遇到lint检查错误会终止构建,一般设置为false
abortOnErrorfalse
//将警告当作错误来处理,
warningsAsErrorsfalse
//检查新的API
check'NewApi'
}
externalNativeBuild {
cmake {
path"src/main/cpp/CMakeLists.txt"
version"3.10.2"
}
}
}
dependencies {
implementation fileTree(dir:'libs',include: ['*.jar'])
support.each { k, v -> implementation v }
}
二 组件化项目详细部署
组件化项目的意义
1.面试技巧,在最短时间内打动面试官
2.开发需求,不相互依赖,可以相互交互,任意组合,高度解耦
3.团队效率,分模块打包,测试,统一版本管理
Phone module和Android Library的区别
1.Phone module新建出可以独立运行的模块,可以看成是app,配置为"applyplugin:'com.android.application'"
有applicationId 切换"com.android.library"
2. Android Library新建出安桌库,不能独立运行。配置为"applyplugin:'com.android.library'"
无applicationId 切换"com.android.application"
新建comment公共库、order订单库、persional个人信息库
Gradle搭建组件化项目环境
集成化开发模式,组件化开发模式
组件化开发的临时代码,集成化打包动态隔离
组件化模式:子模块可以独立运行
集成化模式:打包整个项目apk,子模块不可独立运行
当需要发布正式版本时,就需要集成化将各个子模块打包成一个apk,而测试时,就分开打包,将各个模块分开打包,通过配置gradle来实现切换
//生产/开发环境 动态切换组件化模式或者集成化模式
1. isRelease =false
2. if (isRelease) {
applyplugin:'com.android.library'
}else {
applyplugin:'com.android.application'
}
3. if (!isRelease) {
applicationId appId.module
}
动态隔离:将正式发布版本与测试版本的打包时隔离,便于开发过程中打包测试和发布。
//源集-设置源集的属性,更改源集的Java目录或者自由目录等,方便测试环境,打包不集成到正式环境
sourceSets {
main {
if (isRelease) {
//集成化模式,整个项目打包
manifest.srcFile'src/main/AndroidManifest.xml'
//release时,debug目录下文件不需要合并到主工程
java{
exclude'**/debug/**'
}
}else {
//如果是组件化模式,需要单独运行时
manifest.srcFile'src/main/debug/AndroidManifest.xml'
}
}
}
三、子模块间交互
module与module之间交互(包括跳转,传参等)
方式
1.eventBus eventBean非常多(一对一),一对多就会混乱不堪、难以维护
2.反射反射技术可以成功,维护成本较高且容易出现高版本@hide限制。
3.隐式意图 维护成本还好,只是比较麻烦,需要维护Manifest中的action
4.BroadcastReceiver 需要动态注册(7.0后),需求方发送广播
5.类加载 需要准确的全类名路径,维护成本较高且容易出现人为失误
6. ........
还有一种比较实用的方案,全局map,首先在公共库里面创建一个PathBean class,里面有两个属性path和class
再创建一个全局路径记录器(RecordPathManager),在这里面创建全局的map,
private static Map>groupMap =new HashMap<>();
两个函数1.将路径信息加入全局mapjoinGroup(),2.根据路径名获得class对象,达到跳转的目的getTargetClass()
/**
* @param groupName 组名,"如order"
* @param pathName 路径名 "如com.example.module"
* @param clazz 类对象,如"OrderActivity.class"
*/
public static void joinGroup(String groupName, String pathName, Class clazz) {
List list =groupMap.get(groupName);
if (list ==null) {
list =new ArrayList<>();
list.add(new PathBean(pathName, clazz));
groupMap.put(groupName, list);
}else {
for (PathBean pathBean:list){
if(!pathName.equals(pathBean.getPath())){
list.add(new PathBean(pathName,clazz));
groupMap.put(groupName,list);
}
}
}
}
/**
* 根据路径名获得class对象,达到跳转的目的
* @param groupName
* @param pathName
* @return
*/
public static ClassgetTargetClass(String groupName,String pathName){
List list=groupMap.get(groupName);
if(list==null)return null;
for (PathBean pathBean:list){
if(pathName.equalsIgnoreCase(pathBean.getPath())){
return pathBean.getClazz();
}
}
return null;
}
在application里面添加路径信息,以便初始化的时候可以加载全部的信息,
@Override
public void onCreate() {
super.onCreate();
RecordPathManager.joinGroup("app","MainActivity", MainActivity.class);
RecordPathManager.joinGroup("module","ModuleActivity", ModuleActivity.class);
RecordPathManager.joinGroup("library","Library_Activity", Library_Activity.class);
}
在需要跳转的地方这样使用:
Class targeClass= RecordPathManager.getTargetClass("module","ModuleActivity");
if(targeClass==null){
Toast.makeText(Library_Activity.this,"targeClass为null",Toast.LENGTH_LONG).show();
}else {
Intent intent=new Intent(Library_Activity.this,targeClass);
startActivity(intent);
}
这种方法同样也有诟病的地方,比如我的项目中有200个activity,在application中岂不是要添加200个map数据?
我们可以使用apt,注解处理器来简化我们的使用过程
四 APT介绍和使用
什么时APT(Annotation Processing Tool)
是一种处理注释的工具,他对源代码文件进行检测找出其中的Annitation,根据注解自动生成代码,如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行,也可以这样理解,只有通过声明APT工具后, 程序在编译期间自定义的注解 才能执行。
通俗理解:根据规则,帮我们生成代码,生成类文件。
开发环境兼容
Android studio 3.3.2+Gradle4.10.1(临界版本)
Android studio 3.4.1+Gradle5.1.1(向下兼容)
Target该注解作用域的目标
Activity使用的布局文件注解
@Target(ElementType.TYPE) //接口,类,枚举,注解
@Target(ElementType.FIELD) //属性、枚举的常量
@Target(ElementType.METHOD)//方法
@Target(ElementType.PARAMETER)//方法参数
@Target(ElementType.CONSTRUCTOR)//构造方法
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//该注解使用在另一个注解中
@Target(ElementType.PACKAGE) //包
@RETENTION(RetentionPolicy.RUNTIME)//注解会在class字节码中存在,jvm加载时,可以通过反射获得该注解中的内容。
生命周期 SOURCE<CLASS<RUNTIME
一般如果需要在运行时去动态获得注解的内容,用RUNTIME注解
要在编译时进行一些预处理工作,如ButterKnife,用CLASS注解,注解会在class文件中存在,但是在运行时会被丢弃。
做一些检查性操作,如@override,用SOURCE注解源码,注解仅存在源码级别,在编译的时候丢弃该注解
1.首先创建一个Java工程存放注解 标注该注解作用域和生命周期。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ARouter {
//详细的路由路径,必填 如"app/MainActivity"
Stringpath();
//从path中取出,规范开发者的编码。
Stringgroup()default "";
}
2.创建注解管理器,主要作用是生成注解文件,返回当前使用注解的类
(1) 在compiler中build.gradle中配置
//不可缺少,注册注解,并且生成文件
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
implementation project(':annotation')
//java控制台乱码
tasks.withType(JavaCompile){
options.encoding="utf-8"
}
}
//jdk编译的版本是1.7
sourceCompatibility ="7"
targetCompatibility ="7"
(2)通过重写AbstractProcessor,获得当前使用该注解的作用的名称等信息,并且动态
生成Library_Activity$$ARouter,返回想要的属性加载到内存中,
@AutoService(Processor.class)//通过AutoService自动生成注解处理器
@SupportedAnnotationTypes({"com.example.annotation.ARouter"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedOptions("content")
public class ARouterProcessorextends AbstractProcessor {
//操作Element工具类
private ElementselementUtils;
//type(类信息)工具类
private TypestypeUtils;
//用来输出错误 警告等信息
private Messagermessager;
//文件生成器
private Filerfiler;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
String content = processingEnvironment.getOptions().get("content");
messager.printMessage(Diagnostic.Kind.NOTE, content);
}
//可以用注解方式
// @Override
// public Set getSupportedAnnotationTypes() {
// return super.getSupportedAnnotationTypes();
// }
// //必填
// @Override
// public SourceVersion getSupportedSourceVersion() {
// return super.getSupportedSourceVersion();
// }
//
// @Override
// public Set getSupportedOptions() {
// return super.getSupportedOptions();
// }
/**
* 相当于main函数,开始处理注解
* 注解处理器的核心方法,处理具体的注解,生成Java文件
*
* @param set 使用了支持处理的节点集合(类 上面写了集合)
* @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到该注解
* @return true表示后续处理器不会再处理(已经处理完成)
*/
@Override
public boolean process(Set set, RoundEnvironment roundEnvironment) {
if (set.isEmpty())return false;
//获取所有使用了Arouter注解的集合
Set elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element : elements) {
//类节点之上就是包节点
String packageName =elementUtils.getPackageOf(element).getQualifiedName().toString();
//获取简单类名
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
//最终我们想要生成的类文件
String finalClassName = className +"$$ARouter";
try {
JavaFileObject sourceFile =filer.createSourceFile(packageName +"." + finalClassName);
Writer writer = sourceFile.openWriter();
//设置包名
writer.write("package " + packageName +";\n");
writer.write("public class " + finalClassName +"{\n");
writer.write("public static Class<?> findTargetClass(String path){\n");
//获取类之上ARouter注解的path值
ARouter aRouter = element.getAnnotation(ARouter.class);
writer.write("if(path.equalsIgnoreCase(\""+aRouter.path()+"\")){\n");
writer.write("return "+className+".class;\n}\n");
writer.write("return null;\n");
writer.write("}\n}");
//非常重要
writer.close();
}catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
}
(3)在使用的包内扫描注解处理器,利用@SupportedOptions({"content"})将Java工程和application工程建立桥梁
//扫描注解处理器
annotationProcessor project(':compiler')
//在gradle中,配置选项参数值(只用于apt传参)
//切记:必须写在defaultConfig节点下
javaCompileOptions{
annotationProcessorOptions{
arguments=[content:'hello apt']
}
}
(4)在activity内使用
//得到注解的信息
Class targeClass= Library_Activity$$ARouter.findTargetClass("/library/Library_Activity");
五 APT高级用法JavaPoet
什么事JavaPoet?
APT+JavaPoet=超级利刃
JavaPoet是square公司推出的开源Java代码生成框架,提供Java api生成.Java源文件,这个框架功能非常实用,也是我们习惯的Java面向对象OOP语法,可以很方便的使用它根据注解生成代码,通过这种自动化生成代码的方式,可以让我们更加简洁优雅的方式替换繁琐冗杂的重复工作。
依赖JavaPoet
implementation' com.squareup:javapoet:1.9.0'
JavaPoet常用库
MethodSpec 代表一个构造函数或方法声明
TypeSpec 代表一个类、接口、枚举的声明
FieldSpec 代表一个成员变量,一个字段的声明
JavaFile 包含一个顶级类的Java文件
ParameterSpec 用来创建参数
AnnotationSpec 用来创建注解
ClassName 用来包装一个类
TypeName 类型(如在添加返回值类型是使用TypeName.VOID)
JavaPoet字符串格式化规则
$L 字面量 如"int value=$L",10
$S 字符串 如 $S ,"hello"
$T 接口 如 $T,MainActiivity
$N 变量 如 user:$N,name
使用步骤同上面一摸一样,只是在AnnotationProcessor生成file的时候有所改变,没有那么多writer.write
//使用JavaPoet
ARouter aRouter=element.getAnnotation(ARouter.class);
MethodSpec methodSpec= MethodSpec.methodBuilder("findTargetClass")
.addModifiers(Modifier.PUBLIC,Modifier.STATIC)
.returns(Class.class)
.addParameter(String.class,"path")
.addStatement("return path.equals($S) ?$T.class : null",aRouter.path(), ClassName.get((TypeElement)element))
.build();
TypeSpec typeSpec=TypeSpec.classBuilder(finalClassName)
.addModifiers(Modifier.PUBLIC,Modifier.FINAL)
.addMethod(methodSpec)
.build();
JavaFile javaFile= JavaFile.builder(packageName,typeSpec).build();
try {
javaFile.writeTo(filer);
}catch (IOException e) {
e.printStackTrace();
}
六 路由架构的设计
在组件化的架构中,我们需要通过APT和JavaPoet技术生成什么样的类文件呢?
为什么需要组名?
避免程序一打开,所有信息全部加载进内存,节省内存,提高性能
生成这些文件做什么用
通过组信息,找到路径文件记录,再在路径文件记录里面匹配,返回class信息。达到跳转的目的。