Android 组件化项目模式管理、模式切换、注解与注解处理器、组件通讯

前言

  在进行安装开发的时候我们经常会对项目进行不同业务逻辑处理分包,例如专门处理网络、数据库、业务逻辑代码的,如果我们不分包则所有东西写在一起,势必在开发过程中会浪费很多时间在寻找文件上,而分包的思想源于模块化,例如一些utils类,专门放到一个包下面,这样在使用的时候就能很快找到。节省开发时间,降低后期维护成本,那么组件化是什么?

正文

  在上面我们提到模块化,可以根据不同的功能进行进一步的封装,放到一个新建的模块下面,例如登录注册模块,个人中心模块之类的,但是模块化时就会涉及到一个分体,不同模块之间存在业务逻辑上的耦合,这种耦合开发过程中还不是最痛苦的,痛苦的是你需要复用其中某一个模块的时候,你需要一点一点的剥离出去,工作量甚至不亚于重新写,因为这时你的脑海里需要有原来项目的逻辑,还有的新项目的逻辑,还要想一下怎么迁移过去合理,为了解决模块化耦合严重的问题,就出现了组件化。

  模块化是一个App主模块依赖多个Android子模块,只能运行App主模块;组件化是多个App主模块,各个模块都能独立运行测试。那么是否要使用模块化呢?这取决于你的项目体量了,如果一开始你不确定项目体量。那么就只用模块化,确定的化就使用组件化,也可以模块化转组件化。这个看实际情况而定,而如果项目太小了,你甚至都不需要用什么模块化组件化,一个MainActivity就搞定了,何必搞那么复杂呢?

  总而言之,言而总之。这个组件化知识,你可能现在用不上,但是你学会了总有用武之地,正所谓,技多不压身嘛!下面开始实操环节,可能比你想象的还要简单呢?

一、创建项目

  首先我们创建一个名为StudyComponent的项目,项目创建好之后就可以看到app模块了。

  这就是我们的app主模块,也就是所谓的App的壳,它里面要有一个个组件构成,那么下面我们需要创建组件,这里做一个假设,我的App有登录注册和个人中心两个内容,怎么把这两个内容变成两个组件呢?首先要做的就是创建这两个组件,首先创建login组件,先将项目从Android模式切换到Project模式,然后鼠标右键点击StudyComponent → New → Module,出现弹窗,这里选择的是Phone & Tablet的方式。

点击Next,会需要你创建一个Activity,选择Empty Activity。

  点击Next,给你的Activity命名,这里要将Activity的名字改变一下,因为组件化,在打包的时候是不允许有重复资源的,MainActivity我们在app组件中已经有了,所以其他组件中要避免重复。

  点击Next,等待项目创建完成,创建完成之后你会看到login组件,同时可以自由的切换当前所运行的是app组件还是login组件,现在可以试试看。

参考创建login组件的方式,我们再创建一个personal组件,里面的Activity就改成PersonalActivity即可,其他的都差不多。

现在就有三个组件了,三个组件目前各自独立,那么我们调试运行的时候可以通过切换不同的组件进行,打包要怎么办呢?

二、项目模式切换

  我们希望在打包的时候app组件依赖login和personal组件,那么我们就需要对这两个组件进行切换,切换之前我们需要知道它们当前是什么模式,在哪里看呢?

  这里的application就表示你这是工程项目,而如果要被app组件依赖的话,则需要变成library,那么怎么变呢?在思考怎么变之前,我们首先应该知道library是什么样子,所以我们应该创建一个基础模块,所有的组件又依赖这个基础模块。

#mermaid-svg-jyp4XTHIWtEPZNCC {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-jyp4XTHIWtEPZNCC .error-icon{fill:#552222;}#mermaid-svg-jyp4XTHIWtEPZNCC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jyp4XTHIWtEPZNCC .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-jyp4XTHIWtEPZNCC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jyp4XTHIWtEPZNCC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jyp4XTHIWtEPZNCC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jyp4XTHIWtEPZNCC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jyp4XTHIWtEPZNCC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jyp4XTHIWtEPZNCC .marker.cross{stroke:#333333;}#mermaid-svg-jyp4XTHIWtEPZNCC svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jyp4XTHIWtEPZNCC .label{font-family:”trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-jyp4XTHIWtEPZNCC .cluster-label text{fill:#333;}#mermaid-svg-jyp4XTHIWtEPZNCC .cluster-label span{color:#333;}#mermaid-svg-jyp4XTHIWtEPZNCC .label text,#mermaid-svg-jyp4XTHIWtEPZNCC span{fill:#333;color:#333;}#mermaid-svg-jyp4XTHIWtEPZNCC .node rect,#mermaid-svg-jyp4XTHIWtEPZNCC .node circle,#mermaid-svg-jyp4XTHIWtEPZNCC .node ellipse,#mermaid-svg-jyp4XTHIWtEPZNCC .node polygon,#mermaid-svg-jyp4XTHIWtEPZNCC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jyp4XTHIWtEPZNCC .node .label{text-align:center;}#mermaid-svg-jyp4XTHIWtEPZNCC .node.clickable{cursor:pointer;}#mermaid-svg-jyp4XTHIWtEPZNCC .arrowheadPath{fill:#333333;}#mermaid-svg-jyp4XTHIWtEPZNCC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jyp4XTHIWtEPZNCC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jyp4XTHIWtEPZNCC .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-jyp4XTHIWtEPZNCC .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-jyp4XTHIWtEPZNCC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jyp4XTHIWtEPZNCC .cluster text{fill:#333;}#mermaid-svg-jyp4XTHIWtEPZNCC .cluster span{color:#333;}#mermaid-svg-jyp4XTHIWtEPZNCC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jyp4XTHIWtEPZNCC :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;}

依赖

依赖

依赖

依赖

App

login

personal

basic

通过这个流程图就能很好的理解这个思想了,下面我们创建basic模块。

创建模块的方式和之前一样,只不过这里我们就要选择Android Library进行创建了,它里面是没有让你去创建Activity的,点击Finish即可。

① 对比build.gradle

下面要做的就是对比一下组件和模块下的build.gradle中的区别,看看那些相同,那些不同,先看login组件的build.gradle。

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.llw.login'
    compileSdk 32

    defaultConfig {
        applicationId "com.llw.login"
        minSdk 23
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

再看base下的build.gralde

plugins {
    id 'com.android.library'
}

android {
    namespace 'com.llw.base'
    compileSdk 32

    defaultConfig {
        minSdk 23
        targetSdk 32

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

那么现在就能得出结论了,从上到下都有三个闭包,分别是plugins{}、android{}、dependencies{},先从plugins{}开始分析。

(一)plugins{}

组件中:

id 'com.android.application'

模块中:

id 'com.android.library'

那么我们需要根据一个变量来更改是application还是library。

(二)android{}

再分析android{}:

组件中:

android {
    namespace 'com.llw.login'
    compileSdk 32

    defaultConfig {
        applicationId "com.llw.login"
        minSdk 23
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    ...
}

模块中:

android {
    namespace 'com.llw.base'
    compileSdk 32

    defaultConfig {
        minSdk 23
        targetSdk 32

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }
    ...
}

  这个闭包中,相同的内容就不看了,看那些不同的,首先namespace不用改动,compileSdk 、minSdk 、targetSdk 可以集中用一个变量管理,这样改的时候也方便一步到位。然后就是不同的地方,applicationId、versionCode、versionName在模块中没有,组件中有,这个需要注意。dependencies{}就没有啥好说的,可以说完全一样,集中处理就可以了。

② 对比AndroidManifest.xml

还有一处不同就是组件下的AndroidManifest.xml中是有内容的:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.StudyComponent">
        <activity
            android:name=".LoginActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

</manifest>

而在模块下的AndroidManifest.xml中的内容:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>

  这意味中我们需要在组件中再准备一个模块的AndroidManifest.xml文件,根据当前是组件还是模块进行不同的引用。到目前为止我们都没有写过代码,下面开始写代码,写代码之前。你可以切换app、login、personal分别运行一下,你就会发现有三个应用,每个应用你都可以单独运行。

③ 管理项目参数

  先梳理一下,我们目前有三个组件:app、login、personal,一个模块:basic。模块和组件里面有很多内容是一样的,例如版本号、编译SDK版本什么的,还有一些依赖库版本,jdk版本,如果我没有每一个的去改无疑很麻烦,所以我们将这些信息定义到一个文件中,如果有依赖库的版本改变了只要改这个文件就可以了,那就很方便了,右键点击你的StudyComponent → New → File ,输入config.gradle。

  回车,在我们的工程目录下创建了一个config.gradle文件,这也是一个gradle文件,里面的代码需要我们自己去写,注意看这个文件的所在位置和你的工程build.gradle是同一级别的,如果你不是这样的,说明你创建文件的位置错了,需要重新创建。

gradle里面是有一种编程语言的,是Groovy,感兴趣的话可以去了解一下。先把里面的代码写进去再来说明一下,config.gradle的代码如下:

//项目工程配置
ext {
    //基本信息配置
    android = [
            compileSdk   : 32,    //编译SDK版本
            minSdk       : 23,    //最低运行SDK版本
            targetSdk    : 32,    //目标SDK版本
            versionCode  : 1,     //项目版本号
            versionName  : "1.0", //项目版本名
            isApplication: true   //是否为Application
    ]

    //编译JDK配置
    compileOptions = [
            sourceCompatibility: JavaVersion.VERSION_11,
            targetCompatibility: JavaVersion.VERSION_11,
    ]

    //依赖配置
    dependencies = [
            publicImplementation : [
                    'androidx.appcompat:appcompat:1.4.1',
                    'com.google.android.material:material:1.6.0',
                    'androidx.constraintlayout:constraintlayout:2.1.4'
            ],
            publicTestImplementation : [
                    'junit:junit:4.13.2'
            ],
            publicAndroidTestImplementation: [
                    'androidx.test.ext:junit:1.1.3',
                    'androidx.test.espresso:espresso-core:3.4.0'
            ],
            other : [
                    ':basic'
            ]
    ]
}

  这里的代码是Groovy的语法,ext你可以看做一个类名,android、compileOptions 、dependencies可以看做ext的内部类,中括号里面的就是类的属性值,属性名不能重复。属性值这里我们就定义了一些项目中需要用的属性,通过注释你应该明白这些属性是什么意思了,要使用它,首先需要让我们的工程知道有这样一个文件,在工程的build.gradle中新增如下所示代码:

apply from: "config.gradle"

添加位置如下图所示:

然后Sync Now,下面使用这个文件,

(一)修改app的build.gradle

修改一下app下的build.gradle中代码,如下所示:

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.llw.component'
    compileSdk rootProject.ext.android.compileSdk

    defaultConfig {
        applicationId "com.llw.component"
        minSdk rootProject.ext.android.minSdk
        targetSdk rootProject.ext.android.targetSdk
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility rootProject.ext.compileOptions.sourceCompatibility
        targetCompatibility rootProject.ext.compileOptions.targetCompatibility
    }
}

dependencies {
    implementation rootProject.ext.dependencies.publicImplementation
    implementation rootProject.ext.dependencies.publicTestImplementation
    implementation rootProject.ext.dependencies.publicAndroidTestImplementation
    rootProject.ext.dependencies.other.each {
        implementation project(it)
    }
    //不是组件时才依赖
    if (!rootProject.ext.android.isApplication) {
        implementation project(path: ':login')
        implementation project(path: ':personal')
    }
}

  app模块就一直是application,不需要切换到library,然后修改compileSdk 的值,rootProject表示这个工程,这里引用ext,再引用android,最后找到compileSdk,实际的值就是32,其他的属性设置也是这个道理,这里面的dependencies 中需要引用basic模块,这样写的好处就是,有一天我的basic模块改成了network模块,我只需要修改config.gradle中的值就可以了,道理和其他全局属性一样。而最后这里的依赖其他的组件,需要在他们是library的时候才依赖,不是则不依赖。

(二)修改basic模块

basic作为其他组件依赖的基础模块,任何时候都是library模式,这一点和app模块恰恰相反,修改一下basic的build.gradle中代码,如下所示:

plugins {
    id 'com.android.library'
}

android {
    namespace 'com.llw.basic'
    compileSdk rootProject.ext.android.compileSdk

    defaultConfig {
        minSdk rootProject.ext.android.minSdk
        targetSdk rootProject.ext.android.targetSdk

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            buildConfigField 'boolean', 'isApplication',
                    rootProject.ext.android.isApplication.toString()
        }
        advanced {
            buildConfigField 'boolean', 'isApplication',
                    rootProject.ext.android.isApplication.toString()
        }
        debug {
            buildConfigField 'boolean', 'isApplication',
                    rootProject.ext.android.isApplication.toString()
        }
    }
    compileOptions {
        sourceCompatibility rootProject.ext.compileOptions.sourceCompatibility
        targetCompatibility rootProject.ext.compileOptions.targetCompatibility
    }
}

dependencies {
    implementation rootProject.ext.dependencies.publicImplementation
    implementation rootProject.ext.dependencies.publicTestImplementation
    implementation rootProject.ext.dependencies.publicAndroidTestImplementation
}

  很多内容是相似的,不过这里有一点不一样,那就是定义了一个变量isApplication,这个变量的作用就是用来告知当前这个模块是application模式还是library模式,这很重要,因为app、login、personal在application模式下是各自独立的,而他们都需要继承basic,所以这个区分模块模式的工作就在basic完成,而basic需要做的任务还有别的,在com.llw.basic包下新建一个BaseActivity类,代码如下:

public class BaseActivity extends AppCompatActivity {

    protected boolean isApplication;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        isApplication = BuildConfig.isApplication;
    }

    protected void showMsg(CharSequence msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
}

还有Application的处理,在com.llw.basic包下新建一个BaseApplication类,代码如下:

public class BaseApplication extends Application {

    public static boolean isApplication;

    @Override
    public void onCreate() {
        super.onCreate();
        isApplication = BuildConfig.isApplication;
    }
}

其他组件依赖了basic之后可以通过继承这两个类得知当前属于什么模式。如果出现BuildConfig报红,编译一下,如果还爆红,就重新导包,basic模块中的改动就完成了。

(三)修改login组件

修改这个login组件就有一些不一样了,因为它既有可能是application也有可能是library。当前我们的login还是application,那么对应的AndroidManifest.xml就是常规的,而如果变成了library模式时,这个AndroidManifest.xml也需要有变化,所以这里我们就需要两个AndroidManifest.xml,一个在application时用,一个在library时用。所以在login组件的main文件夹下新建一个manifest文件夹,然后再创建一个AndroidManifest.xml

这个AndroidManifest.xml的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>

    </application>
</manifest>

你再把application标签去掉就和basic中的AndroidManifest.xml完全一样了,这里改好之后,我们再去修改login组件的build.gradle,代码如下:

if (rootProject.ext.android.isApplication) {//修改插件的类型
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    namespace 'com.llw.login'
    compileSdk rootProject.ext.android.compileSdk

    defaultConfig {
        if (rootProject.ext.android.isApplication) {//application时需要id,版本号、版本名
            applicationId "com.llw.login"
            versionCode rootProject.ext.android.versionCode
            versionName rootProject.ext.android.versionName
        }
        minSdk rootProject.ext.android.minSdk
        targetSdk rootProject.ext.android.targetSdk

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            if (rootProject.ext.android.isApplication) {//修改使用的AndroidManifest
                manifest.srcFile 'src/main/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            }
        }
    }

    compileOptions {
        sourceCompatibility rootProject.ext.compileOptions.sourceCompatibility
        targetCompatibility rootProject.ext.compileOptions.targetCompatibility
    }
}

dependencies {
    implementation rootProject.ext.dependencies.publicImplementation
    implementation rootProject.ext.dependencies.publicTestImplementation
    implementation rootProject.ext.dependencies.publicAndroidTestImplementation
    rootProject.ext.dependencies.other.each {
        implementation project(it)
    }
}

  上面的修改代码主要就是组件是application时怎么做,library时怎么做,相信你能够看懂,最后我们修改一下activity_login.xml中TextView显示的文本内容,由Hello World!改成Login。

同时我们修改一下LoginActivity中的代码,让它继承自basic模块中的BaseActivity,使用父类的方法showMsg,弹出Toast。

(四)修改personal组件

  这里的修改方式大致是一样的,我还是重复描述一遍吧,因为我怕你跟着操作到这里就不会了,首先同样需要一个AndroidManifest.xml,你可以直接把login组件中所写的复制过来。

然后修改personal的build.gradle,代码如下:

if (rootProject.ext.android.isApplication) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    namespace 'com.llw.personal'
    compileSdk rootProject.ext.android.compileSdk

    defaultConfig {
        if (rootProject.ext.android.isApplication) {
            applicationId "com.llw.personal"
            versionCode rootProject.ext.android.versionCode
            versionName rootProject.ext.android.versionName
        }
        minSdk rootProject.ext.android.minSdk
        targetSdk rootProject.ext.android.targetSdk

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            if (rootProject.ext.android.isApplication) {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            }
        }
    }

    compileOptions {
        sourceCompatibility rootProject.ext.compileOptions.sourceCompatibility
        targetCompatibility rootProject.ext.compileOptions.targetCompatibility
    }
}

dependencies {
    implementation rootProject.ext.dependencies.publicImplementation
    implementation rootProject.ext.dependencies.publicTestImplementation
    implementation rootProject.ext.dependencies.publicAndroidTestImplementation
    rootProject.ext.dependencies.other.each {
        implementation project(it)
    }
}

除了有包名不一样以外,其他都和login组件的build.gralde一样,下面同样修改一下activity_personal.xml。

然后修改PersonalActivity的代码。

现在基本上就改完了。

④ 组件运行

目前app、login、personal都是组件,我们分别运行一下试试看。

通过这里切换需要运行的项目,下面我们依次运行一下app、login、personal,运行效果如下图所示。

从这几个图来看,我们的组件依赖basic模块没有问题,其次就是组件各自为一个项目,你现在手机上应该有三个应用才对。

⑤ 切换模式

  现在login和personal在application下可以正常运行,那如果在library时,app就依赖了login和personal,此时就只有app这一个组件了,那么这个时候app能不能正常运行呢?

下面我们把config.gradle中的isApplication的值从ture改成false,然后Sync Now。

  就可以看到login和personal,现在不能够正常独立运行了,有一个 × ,然后注意看图标也变了,不是和app组件一样了,而是和basic一样了,这说明我们切换组件的项目模式是可行了,那么下面我们运行一下app组件,看能否正常运行。我这边是可以正常运行,不知道你那边怎么样。

一、注解

  还是之前的StudyComponent项目,这里我们再创建一个Module,这里要注意创建的是java Module,注意我选择的模式。

创建Java Module的时候需要创建一个默认的类,这里我们改变一下类名为BindPath,稍后还将改成注解类。

① 创建注解类

Module创建好之后修改这个BindPath,代码如下:

@Target(ElementType.TYPE)   
@Retention(RetentionPolicy.CLASS)   
public @interface BindPath {

    String value();
}

这个库里面代码其实就这么一点,那么我们怎么使用这个注解呢?

② 使用注解类

  要使用这个注解类,首先要依赖这个注解库,那么我们之前所写的config.gradle就排上用场了,还记得它的作用吗?管理工程中所有的gradle,那么添加依赖库自然是可以的,何况我们之前还添加过,还记得吗?帮你回忆一下,我们的app、login、personal都需要依赖basic库,之前通过config.gradle中配置可以一步到位,那么这个注解库也是一样的道理,所以我们只需要改动一个地方就可以完成所有组件对于注解库的依赖,

现在Sync Now同步一下就可以了,我们分别在app、login、personal组件中使用这个注解,如下图所示:

  注意看,这里在Activity上面添加注解,然后里面的值就是当前的模块名斜杠再加上当前的类名,好了下面你可以暂且运行一下,看看报不报错,无论报不报错都继续往后走。

二、路由

  这里我们做注解是要标记一个Activity,然后保存到路由中,那么这个路由就负责组件之间通讯,这里的路由,你可以单独创建一个library库,也可以写在basic中,这里我就写在basic模块中,在com.llw.basic包下新建一个router包,router包下新建IRouter接口,代码如下:

public interface IRouter {

    void putActivity();
}

然后我们再创建一个ARouter类,代码如下:

public class ARouter {

    @SuppressLint("StaticFieldLeak")
    private static final ARouter aRouter = new ARouter();
    private final Map> map;
    private Context context;

    private ARouter() {
        map = new HashMap<>();
    }

    public static ARouter getInstance() {
        return aRouter;
    }

    /**
     * 初始化
     */
    public void init(Context context) {
        this.context = context;
        //执行生成的工具类中的方法  将Activity的类对象加入到路由表中
        List classNames = getClassName();
        for (String className : classNames) {
            try {
                Class> utilClass = Class.forName(className);
                if (IRouter.class.isAssignableFrom(utilClass)) {
                    IRouter iRouter = (IRouter) utilClass.newInstance();
                    iRouter.putActivity();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 添加Activity
     * @param key 注解中的值 例如  "main/MainActivity"
     * @param clazz 目标Activity
     */
    public void addActivity(String key, Class extends Activity> clazz) {
        //如果Key不会空,activity不为空,且map中没有这个key
        if (key != null && clazz != null && !map.containsKey(key)) {
            map.put(key, clazz);
        }
    }

    /**
     * 跳转Activity
     * @param key 注解中的值 例如  "main/MainActivity"
     */
    public void jumpActivity(String key) {
        jumpActivity(key, null);
    }

    /**
     * 跳转Activity 带参数
     * @param key 注解中的值 例如  "main/MainActivity"
     * @param bundle 参数包
     */
    public void jumpActivity(String key, Bundle bundle) {
        Class extends Activity> aClass = map.get(key);
        if (aClass == null) {
            return;
        }
        Intent intent = new Intent(context, aClass);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (bundle != null) {
            intent.putExtras(bundle);
        }
        context.startActivity(intent);
    }

    /**
     * 通过包名获取这个包下面的所有的类名
     */
    private List getClassName() {
        //创建一个class对象的集合
        List classList = new ArrayList<>();
        try {
            DexFile df = new DexFile(context.getPackageCodePath());
            Enumeration entries = df.entries();
            while (entries.hasMoreElements()) {
                String className = entries.nextElement();
                if (className.contains("com.llw.util")) {
                    classList.add(className);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classList;
    }
}

  这里面的代码就是存放和使用Activity、组件之间跳转Activity操作。现在注解和路由都有了,要使我们的注解能够生效,还需要一个注解处理器,顾名思义就是用来处理被注解的类型。

三、注解处理器

这里我们再创建一个Module,这里要注意创建的是java Module,注意我选择的模式。

这里修改模块名和包名和类名,等待注解处理器这个库创建完成。

① 添加依赖

  这里的注解处理器相较于注解稍稍有一些不同,首先我们改动一下注解处理器模块的build.gradle,添加代码如下:

dependencies {

    implementation 'com.google.auto.service:auto-service:1.0-rc7'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    implementation 'com.squareup:javapoet:1.13.0'
    implementation project(path: ':annotation')
}

添加位置如下图所示

  这里前面两句依赖是添加注解处理器,然后就是生成编译时文件需要用到的库,最后就是依赖注解库,这里和之前稍有不同,我们不使用config.gradle中的配置,这也是注解处理器的特殊之处,添加完依赖之后点击Sync Now。

② 注解处理器编写

同步完成之后我们可以编写AnnotationCompiler类的代码,如下所示:

@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {

    // 定义用来生成APT目录下面的文件的对象(例如:ActivityRouterUtil1668396026324)
    Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }

    /**
     * 支持的注解类型
     */
    @Override
    public Set getSupportedAnnotationTypes() {
        Set types = new HashSet<>();
        types.add(BindPath.class.getCanonicalName());
        return types;
    }

    /**
     * 支持版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    /**
     * 通过注解处理器处理注解,生成代码到build文件夹中
     */
    @Override
    public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //获取注解 例如 :@BindPath("main/MainActivity")
        Set extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
        Map map = new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            TypeElement typeElement = (TypeElement) element;
            //key为注解的Activity 例如:MainActivity
            String key = typeElement.getQualifiedName().toString() + ".class";
            //value为注解方法中的值 例如:"main/MainActivity"
            String value = typeElement.getAnnotation(BindPath.class).value();
            map.put(key, value);
        }
        makefile(map);
        return false;
    }

    private void makefile(Map map) {
        if (map.size() > 0) {
            //定义编译时类生成时的包名
            String packageName = "com.llw.util";
            //定义处理器的包名
            String routerPackageName = "com.llw.basic.router";
            //获取接口名IRouter
            ClassName interfaceName = ClassName.get(routerPackageName, "IRouter");
            //获取类名 ARouter
            ClassName className = ClassName.get(routerPackageName, "ARouter");
            //创建类构造器,例如ActivityRouterUtil  加上时间戳是为了防止生成的编译时类名重复报错
            TypeSpec.Builder classBuilder = TypeSpec.classBuilder("ActivityRouterUtil" + System.currentTimeMillis())
                    //添加修饰符 public
                    .addModifiers(Modifier.PUBLIC)
                    //添加实现接口,例如 implements IArouter
                    .addSuperinterface(interfaceName);
            //创建方法构造器 方法名putActivity()
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("putActivity")
                    //添加注解
                    .addAnnotation(Override.class)
                    //添加修饰符
                    .addModifiers(Modifier.PUBLIC);
            //这里遍历是为了给每一个添加了注解进行代码生成
            for (String activityName : map.keySet()) {
                String value = map.get(activityName);
                //例如 com.llw.arouter.ARouter.getInstance().addActivity("login/LoginActivity",com.llw.login.LoginActivity.class);
                methodBuilder.addStatement("$L.getInstance().addActivity($S, $L)", className, value, activityName);
            }
            //在类构造器中添加方法
            classBuilder.addMethod(methodBuilder.build());
            try {
                //最后写入文件
                JavaFile.builder(packageName, classBuilder.build())
                        .build()
                        .writeTo(filer);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

代码中的注解已经很清楚了,就是生成一个编译时类,编译时类的代码如下图:

③ 注解处理器使用

要使这个注解处理器生效,需要分别在app、login、personal的build.gradle中的denpendencies{}下添加如下所示代码:

annotationProcessor project(path: ':annotation_compiler')

添加的位置如下面三图所示:

添加好之后Sync Now,然后运行一下,运行之后在app模块下会生成一个build文件夹,然后层层打开,最终如下图所示:

我们刚才的AnnotationCompiler中所写的代码就是为了生成这个编译时文件,如果你没有找到这个文件,点击这个刷新按钮,刷新一下项目文件。

  Android Studio有时候文件检查不是很及时,所以手动刷新一下,看有没有生成这个文件。如果文件生成了,那么你再依次检查一下login、personal组件中的build文件夹中有没有生成相关文件。

四、使用路由

  下面要做的就是能够进行组件之间的Activity跳转,例如从app的MainActivity跳转到login的LoginActivity,再从LoginActivity跳转到personal的PersonalActivity,要做到这一步我们需要对路由进行初始化,可以在basic模块中的BaseApplication中完成。

  而为了使BaseApplication生效,我们需要在各自组件中的AndroidManifest.xml进行注册,实际上我们各个组件应该自己写一个Application类继承自BaseApplication,但是目前我们的功能比较简单,所以就不这样写了,直接使用BaseApplication进行注册即可,下面我在app组件的AndroidManifest.xml中注册。

其他的组件自己去注册一下。

① 页面跳转

然后我们在MainActivity中添加这样一行代码。

ARouter.getInstance().jumpActivity("login/LoginActivity");

这里意图很明显,我要跳转到LoginActivity,那么我们在LoginActivity的onCreate()方法中添加

ARouter.getInstance().jumpActivity("personal/PersonalActivity");

我相信你知道怎么添加这行代码,这样就能跳转到PersonalActivity中了,下面我们运行测试一下。

  这里可以看到,直接就进入了PersonalActivity,但是你会发现还有LoginActivity的Toast显示出来,这证明确实是从MainActivity过来的,最终到达PersonalActivity,你要是延时跳转那就会很明显,自行尝试吧。

② 页面带参跳转

修改一下LoginActivity的onCreate()方法,进行传参,代码如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        showMsg("LoginActivity");
        Bundle bundle = new Bundle();
        bundle.putString("data","Very Good!");
        ARouter.getInstance().jumpActivity("personal/PersonalActivity", bundle);
    }

然后修改PersonalActivity中的onCreate()方法,接收参数,代码如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_personal);
        String data = getIntent().getExtras().getString("data");
        if (data != null) {
            showMsg(data);
        }
    }

下面重新运行一下:

OK,现在页面的组件通讯就初步完成了。

资源下载
下载价格VIP专享
仅限VIP下载升级VIP
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/20928,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?