一 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 一般如果需要在运行时去动态获得注解的内容,用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"); 什么事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信息。达到跳转的目的。五 APT高级用法JavaPoet
六 路由架构的设计