Android 组件化开发

一 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");

五 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信息。达到跳转的目的。

你可能感兴趣的:(Android 组件化开发)