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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容