Android组件化入门:一步步搭建组件化架构

1、前言

最近因为业务需求变更,有考虑采用组件化架构进行开发,这方面我之前没有接触过。关于组件化的文章很多,各方大神更是提出了各种的组件化方案,我也看了很多相关文章。但是学习新东西看的再多,不如动手做一次,先不考虑复杂的东西,先动手做个简单的Demo更有助于理解组件化的思想。

2、搭建组件化Demo

先打开Android Studio新建一个项目。

image.png

步骤一: 新建config.gradle,统一管理build.gradle中的相关内容
然后在项目目录下新建一个config.gradle文件。

image.png
ext {
    //applicationId版本号sdkVersion统一管理
    android = [
            compileSdkVersion        : 28,
            buildToolsVersion        : 28,
            applicationId            : "com.example.componenttestdemo",
            minSdkVersion            : 19,
            targetSdkVersion         : 28,
            versionCode              : 1,
            versionName              : "1.0",
            testInstrumentationRunner: "android.support.test.runner.AndroidJUnitRunner"
    ]

    //版本号
    def APPCOMPAT_V7_VERSION = "28.0.0"
    def CONSTRAINT_LAYOUT_VERSION = "1.1.3"

    //三方库统一管理
    dependencies = [
            appcompatV7     : 'com.android.support:appcompat-v7:' + APPCOMPAT_V7_VERSION,
            constraintLayout: 'com.android.support.constraint:constraint-layout:' + CONSTRAINT_LAYOUT_VERSION
    ]
}

因为我们知道项目使用组件化架构后,单一模块Module可以作为单个Application运行,同时也可以在整个主Application中作为一个Module运行。所以在config.gradle中先定义一个isModule来区别这两种情况,组件化之后可以通过修改这个值来切换这两种情况的使用。剩下就是对applicationId、版本号、sdkVersion和三方库等进行统一管理。

接着修改app下的build.gradle里设置内容

将原来的compileSdkVersion、applicationId、minSdkVersion、versionCode和三方库等替换成对应config.gradle中定义的值。

apply plugin: 'com.android.application'

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    defaultConfig {
        applicationId rootProject.ext.android.applicationId
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner rootProject.ext.android.testInstrumentationRunner
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation rootProject.ext.dependencies.appcompatV7
    implementation rootProject.ext.dependencies.constraintLayout

}

最后还要在项目目录下的build.gradle中添加一行:

apply from : "config.gradle"

image.png

然后点击Sync Now同步。最后在进行下一步前,新建一个MyApplication,在AndroidManifest设置name属性。

步骤二:创建Main模块,搬空app壳工程

我们知道组件化中需要一个app壳工程,这个壳工程中不处理任何业务,就只是一个空壳,由它将所需要的各个组件模块组合起来,构成一个完整的应用。而现在项目中的app还是存在默认的入口Activity的,所以要新建一个ModuleMain将默认的MainActivity和其布局文件搬过去。

image.png

接着进入app的AndroidManifest文件将注册Activity的相关代码也搬到ModuleMain模块的AndroidManifest中去,只留下application标签。

这里注意组件化项目中每个Module都会有自己的AndroidManifest文件,最后打包时会将这些文件合并成一个文件,所以会出现application标签中的属性重复问题,要在app的AndroidManifest文件中添加如下两行代码:

 xmlns:tools="http://schemas.android.com/tools" 
 tools:replace="android:name,android:label,android:icon, android:theme,android:allowBackup"

这里的name、label、icon、theme、allowBackup都可能会有重复,所以全部写上之间用逗号隔开。完整AndroidManifest如下:

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

    <application
        android:name=".application.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        tools:replace="android:name,android:label,android:icon, android:theme,android:allowBackup"
        android:theme="@style/AppTheme">
    </application>

</manifest>

接着app壳工程中只剩刚修改的build.gradle还没删减,在删减前先将app中build.gradle的内容复制覆盖到Main模块的build.gradle中,并且还要做部分修改。因为单个组件可以作为一个组件模块被app壳工程组合使用,也可以单独作为一个application使用。所以要根据config.gradle中定义的isModule来判断是作为Module还是Applicaition。同样还有作为Module是不需要applicationId的而作为应用则是需要的。

//通过isModule来判断是application还是module
if (rootProject.ext.isModule) {
     apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    defaultConfig {
        //是application才需要applicationId
        if (!rootProject.ext.isModule) {
            applicationId "com.example.sy.moduledmain"
        }
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner rootProject.ext.android.testInstrumentationRunner
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation rootProject.ext.dependencies.appcompatV7
    implementation rootProject.ext.dependencies.constraintLayout
}

这时modulemain中的AndroidManifest会提示资源文件的缺少,这时先将app中的对应文件复制到modulemain里来。

image.png

步骤三 :新建Application和AndroidManifest文件
在app壳工程和ModuleMain中分别新建一个Application,因为Moudule也是需要可以单独运行的。
image.png

接着在ModuleMain里新建AndroidManifest文件,因为作为Module和Application是会有不一样的所以要做区分,在main目录下新建module文件夹和application文件夹分别存放两个情况下的AndroidManifest文件,将原来的AndroidManifest文件拖到module下,再拷贝一份到application下。拷贝完了记得在两个AndroidManifest里application标签下设置name属性。
接着再build.gradle中添加如下代码,用来分别在两种情况下指定使用哪个AndroidManifest。

sourceSets {
        main {
            if (rootProject.ext.isModule) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/application/AndroidManifest.xml'
                java {
                    //排除java/module文件夹下的所有文件
                    exclude '*module'
                }
            }
        }
    }

然后再到app的build.gradle中在dependencies内添加以下代码,用来引入ModuleMain模块。

if (rootProject.ext.isModule) {
    implementation project(":modulemain")
}

现在可以再次点击Sync Now等同步结束后,虽然项目中只有一个壳工程和一个主Module,但是已可以看到组件化的雏形。此时已经可以通过修改config.gradle里的isModule的值,进行Application和Module两种模式的切换,将ModuleMain作为app 的模块运行或者是单独作为一个应用运行了。

image.png

步骤四:新建其他组件Module和解决资源文件冲突
接着按照新建ModuleMain的步骤重复新建其他业务Module,这里我新建了3个Module,业务A:ModuleA与业务B:ModuleB和一个BaseModule。其中BaseModule主要存放一些基础类和工具类,只做为Module为上层业务模块提供服务。

image.png

接下来解决资源文件冲突的问题,进入ModuleMain的build.gradle添加下面这行代码,为资源文件命名规范一个统一开头:

resourcePrefix "modulemain_"

添加后起名是没按照规范Android Studio就会有一个提示:


image.png

按要求修改文件名后提示消失。

image.png

步骤五:使用ARouter进行组件间通信

接下来就要处理组件间的通信问题,采用阿里的ARouter。按照文档集成ARouter。 首先在defaultConfig下添加如下代码:

javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }

再引入ARouter依赖:

implementation rootProject.ext.dependencies.arouter
implementation rootProject.ext.dependencies.arouterCompiler

最后在Application中初始化ARouter:

if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化

这样ARouter就集成好了,接着在MoudleA和ModuleB中新建两个Activity,然后使用ARouter进行页面跳转。

ModuleMain中MainActivity.java:

public class MainActivity extends BaseActivity  {
    /**
     * toA
     */
    private Button mModulemainA;
    /**
     * toB
     */
    private Button mModulemainB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.modulemain_activity_main);
        initView();
        initEvent();
    }

    private void initEvent() {
        mModulemainA.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build(ARouterPath.PATH_MOUDULE_A).navigation();
            }
        });

        mModulemainB.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build(ARouterPath.PATH_MOUDULE_B).withString("key","传递的数据").navigation();
            }
        });
    }

    private void initView() {
        mModulemainA = (Button) findViewById(R.id.modulemain_a);
        mModulemainB = (Button) findViewById(R.id.modulemain_b);
    }

}

ModuleA中ModuleAActivity.java:

@Route(path = ARouterPath.PATH_MOUDULE_A)
public class ModuleAActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.modulea_activity_module_a);
    }
}

ModuleB中ModuleBActivity.java:

@Route(path = ARouterPath.PATH_MOUDULE_B)
public class ModuleBActivity extends AppCompatActivity {
    @Autowired(name = "key")
    String data;
    /**
     * TextView
     */
    private TextView mTextViewB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.moduleb_activity_module_b);
        ARouter.getInstance().inject(this);
        initView();
    }
    private void initView() {
        mTextViewB = (TextView) findViewById(R.id.textViewB);
        mTextViewB.setText(data);
    }
}

运行效果:


image.png

image.png

这里看到模块之间界面通信正常,并且单个业务模块也可以单独运行,这样一个最基本的组件化架构Demo差不多就完成了。

3、将Module作为远程maven仓库

在开发中,可能会把一些公用Module传到私有服务器上,然后在项目中直接依赖使用。下面就将Module上传到Github作为远程maven仓库,在项目直接引用。首先新建一个项目,创建一个UtilModule。


image.png

将原来项目中的工具类移到UtilModule中,接着在UtilModule的build.gradle中添加以下代码:

apply plugin: 'maven'
uploadArchives {
    repositories.mavenDeployer {
        def mavenDirPath = file('\\Users\\sy\\AndroidProjects\\UtilModule') // 本地存放地址
        repository(url:"file://${mavenDirPath.absolutePath}")
        pom.project {
            groupId "com.example.utilmodule" // 包名
            artifactId "utilmodule" // module的名字
            version "1.0.0" // 版本号
        }
    }
}

然后点击gradle中的uploadArchives:


image.png

进入设置的目录查看,aar已经打包好了。


image.png

接着打开Github创建一个新仓库:


image.png

按照Github上的命令,将本地打包好的UtilModule上传到Github上:
image.png

image.png

image.png

上传完成后将仓库地址复制下来,将其中的github.com部分修改为raw.githubusercontent.com再在结尾加上/master表示是主分支,添加到项目中的build.gradle中。


image.png

接着在到Module的build.gradle中添加依赖:
接着在到Module的build.gradle中添加依赖:
implementation rootProject.ext.dependencies.utilmodule

这里就是之前设置的包名:Module名:版本号。SyncNow之后删除原来项目中的工具类,然后在代码里使用远程仓库的工具类测试:

image.png

运行打印日志:

D/com.example.modulemain.MainActivity: onCreate:false

这说明远程仓库依赖成功已经能正常使用其中的类和方法。

4、总结

这篇文章主要是记录下我初识组件化,搭建组件化Demo的过程,Demo主要对于我对组件化思想的理解和体验还是很有帮助的,Demo中还有很多没考虑到的地方,比如Application的动态配置合并、Fragment、组件化的混淆等等,也是我正在学习的问题。这篇文章主要供和我一样对组件化这块不太了解的新手做参考,希望能对新手有所帮助。

喜欢请点击+关注哦

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

推荐阅读更多精彩内容

  • MVVMHabitComponent 关于Android的组件化,相信大家并不陌生,网上谈论组件化的文章,多如过江...
    goldze阅读 5,168评论 2 22
  • 问题 在已经开发过几个项目的童鞋,如果这时需要重新开发一个新项目,是否需要自己重新搭建框架呢,还是从老项目中拷贝粘...
    8ba406212441阅读 43,002评论 84 381
  • 不怕跌倒,所以飞翔 组件化开发 参考资源 Android组件化方案 为什么要组件化开发 解决问题 实际业务变化非常...
    笔墨Android阅读 2,964评论 0 0
  • Android 架构系列:Android 架构一:Android 架构浅析Android 架构二:纵向横向结合构建...
    IT前沿技术分享阅读 1,806评论 2 16
  • 蝈蝈与蛐蛐 现代 · 济慈 大地的诗歌从来不会死亡:当所有的鸟儿因骄阳而昏晕, 隐藏在阴凉的林中,就有一种声音 在...
    冰大头阅读 165评论 0 0